diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2022-12-18 00:22:15 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2022-12-21 01:35:08 +0900 |
commit | 1ba7484d606bf3797b3936651051bb4113dbcad2 (patch) | |
tree | e4452678c4991f113d6178d8f4f215dbecbe52af /src/options.go | |
parent | 51c518da1e981dddda18e55272be482e972d6861 (diff) |
Add --listen=HTTP_PORT option to receive actions
Supersedes #2019
See also:
* #1728
* https://github.com/junegunn/fzf.vim/pull/1044
Diffstat (limited to 'src/options.go')
-rw-r--r-- | src/options.go | 465 |
1 files changed, 222 insertions, 243 deletions
diff --git a/src/options.go b/src/options.go index 64cf5609..1ef8bca0 100644 --- a/src/options.go +++ b/src/options.go @@ -113,6 +113,7 @@ const usage = `usage: fzf [options] --read0 Read input delimited by ASCII NUL characters --print0 Print output delimited by ASCII NUL characters --sync Synchronous search for multi-staged filtering + --listen=HTTP_PORT Start HTTP server to receive actions (POST /) --version Display version information and exit Environment variables @@ -296,6 +297,7 @@ type Options struct { PreviewLabel labelOpts Unicode bool Tabstop int + ListenPort int ClearOnExit bool Version bool } @@ -868,8 +870,9 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme { } var ( - executeRegexp *regexp.Regexp - splitRegexp *regexp.Regexp + executeRegexp *regexp.Regexp + splitRegexp *regexp.Regexp + actionNameRegexp *regexp.Regexp ) func firstKey(keymap map[tui.Event]string) tui.Event { @@ -891,45 +894,216 @@ func init() { executeRegexp = regexp.MustCompile( `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-query|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) splitRegexp = regexp.MustCompile("[,:]+") + actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") } -func parseKeymap(keymap map[tui.Event][]*action, str string) { - masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string { - symbol := ":" - if strings.HasPrefix(src, "+") { - symbol = "+" - } - prefix := symbol + "execute" - if strings.HasPrefix(src[1:], "reload") { - prefix = symbol + "reload" - } else if strings.HasPrefix(src[1:], "change-preview-window") { - prefix = symbol + "change-preview-window" - } else if strings.HasPrefix(src[1:], "change-preview") { - prefix = symbol + "change-preview" - } else if strings.HasPrefix(src[1:], "preview") { - prefix = symbol + "preview" - } else if strings.HasPrefix(src[1:], "unbind") { - prefix = symbol + "unbind" - } else if strings.HasPrefix(src[1:], "rebind") { - prefix = symbol + "rebind" - } else if strings.HasPrefix(src[1:], "change-query") { - prefix = symbol + "change-query" - } else if strings.HasPrefix(src[1:], "change-prompt") { - prefix = symbol + "change-prompt" - } else if src[len(prefix)] == '-' { - c := src[len(prefix)+1] - if c == 's' || c == 'S' { - prefix += "-silent" - } else { - prefix += "-multi" - } - } +func maskActionContents(action string) string { + masked := executeRegexp.ReplaceAllStringFunc(action, func(src string) string { + prefix := src[:1] + actionNameRegexp.FindString(src[1:]) return prefix + "(" + strings.Repeat(" ", len(src)-len(prefix)-2) + ")" }) masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1) masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1) masked = strings.Replace(masked, "+:", string([]rune{escapedPlus, ':'}), -1) + return masked +} + +func parseSingleActionList(str string, exit func(string)) []*action { + // We prepend a colon to satisfy executeRegexp and remove it later + masked := maskActionContents(":" + str)[1:] + return parseActionList(masked, str, []*action{}, false, exit) +} +func parseActionList(masked string, original string, prevActions []*action, putAllowed bool, exit func(string)) []*action { + maskedStrings := strings.Split(masked, "+") + originalStrings := make([]string, len(maskedStrings)) + idx := 0 + for i, maskedString := range maskedStrings { + originalStrings[i] = original[idx : idx+len(maskedString)] + idx += len(maskedString) + 1 + } + actions := make([]*action, 0, len(maskedStrings)) + appendAction := func(types ...actionType) { + actions = append(actions, toActions(types...)...) + } + prevSpec := "" + for specIndex, spec := range originalStrings { + spec = prevSpec + spec + specLower := strings.ToLower(spec) + switch specLower { + case "ignore": + appendAction(actIgnore) + case "beginning-of-line": + appendAction(actBeginningOfLine) + case "abort": + appendAction(actAbort) + case "accept": + appendAction(actAccept) + case "accept-non-empty": + appendAction(actAcceptNonEmpty) + case "print-query": + appendAction(actPrintQuery) + case "refresh-preview": + appendAction(actRefreshPreview) + case "replace-query": + appendAction(actReplaceQuery) + case "backward-char": + appendAction(actBackwardChar) + case "backward-delete-char": + appendAction(actBackwardDeleteChar) + case "backward-delete-char/eof": + appendAction(actBackwardDeleteCharEOF) + case "backward-word": + appendAction(actBackwardWord) + case "clear-screen": + appendAction(actClearScreen) + case "delete-char": + appendAction(actDeleteChar) + case "delete-char/eof": + appendAction(actDeleteCharEOF) + case "deselect": + appendAction(actDeselect) + case "end-of-line": + appendAction(actEndOfLine) + case "cancel": + appendAction(actCancel) + case "clear-query": + appendAction(actClearQuery) + case "clear-selection": + appendAction(actClearSelection) + case "forward-char": + appendAction(actForwardChar) + case "forward-word": + appendAction(actForwardWord) + case "jump": + appendAction(actJump) + case "jump-accept": + appendAction(actJumpAccept) + case "kill-line": + appendAction(actKillLine) + case "kill-word": + appendAction(actKillWord) + case "unix-line-discard", "line-discard": + appendAction(actUnixLineDiscard) + case "unix-word-rubout", "word-rubout": + appendAction(actUnixWordRubout) + case "yank": + appendAction(actYank) + case "backward-kill-word": + appendAction(actBackwardKillWord) + case "toggle-down": + appendAction(actToggle, actDown) + case "toggle-up": + appendAction(actToggle, actUp) + case "toggle-in": + appendAction(actToggleIn) + case "toggle-out": + appendAction(actToggleOut) + case "toggle-all": + appendAction(actToggleAll) + case "toggle-search": + appendAction(actToggleSearch) + case "select": + appendAction(actSelect) + case "select-all": + appendAction(actSelectAll) + case "deselect-all": + appendAction(actDeselectAll) + case "close": + appendAction(actClose) + case "toggle": + appendAction(actToggle) + case "down": + appendAction(actDown) + case "up": + appendAction(actUp) + case "first", "top": + appendAction(actFirst) + case "last": + appendAction(actLast) + case "page-up": + appendAction(actPageUp) + case "page-down": + appendAction(actPageDown) + case "half-page-up": + appendAction(actHalfPageUp) + case "half-page-down": + appendAction(actHalfPageDown) + case "prev-history", "previous-history": + appendAction(actPrevHistory) + case "next-history": + appendAction(actNextHistory) + case "prev-selected": + appendAction(actPrevSelected) + case "next-selected": + appendAction(actNextSelected) + case "toggle-preview": + appendAction(actTogglePreview) + case "toggle-preview-wrap": + appendAction(actTogglePreviewWrap) + case "toggle-sort": + appendAction(actToggleSort) + case "preview-top": + appendAction(actPreviewTop) + case "preview-bottom": + appendAction(actPreviewBottom) + case "preview-up": + appendAction(actPreviewUp) + case "preview-down": + appendAction(actPreviewDown) + case "preview-page-up": + appendAction(actPreviewPageUp) + case "preview-page-down": + appendAction(actPreviewPageDown) + case "preview-half-page-up": + appendAction(actPreviewHalfPageUp) + case "preview-half-page-down": + appendAction(actPreviewHalfPageDown) + case "enable-search": + appendAction(actEnableSearch) + case "disable-search": + appendAction(actDisableSearch) + case "put": + if putAllowed { + appendAction(actRune) + } else { + exit("unable to put non-printable character") + } + default: + t := isExecuteAction(specLower) + if t == actIgnore { + if specIndex == 0 && specLower == "" { + actions = append(prevActions, actions...) + } else { + exit("unknown action: " + spec) + } + } else { + offset := len(actionNameRegexp.FindString(spec)) + var actionArg string + if spec[offset] == ':' { + if specIndex == len(originalStrings)-1 { + actionArg = spec[offset+1:] + actions = append(actions, &action{t: t, a: actionArg}) + } else { + prevSpec = spec + "+" + continue + } + } else { + actionArg = spec[offset+1 : len(spec)-1] + actions = append(actions, &action{t: t, a: actionArg}) + } + if t == actUnbind || t == actRebind { + parseKeyChords(actionArg, spec[0:offset]+" target required") + } + } + } + prevSpec = "" + } + return actions +} + +func parseKeymap(keymap map[tui.Event][]*action, str string, exit func(string)) { + masked := maskActionContents(str) idx := 0 for _, pairStr := range strings.Split(masked, ",") { origPairStr := str[idx : idx+len(pairStr)] @@ -937,7 +1111,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) { pair := strings.SplitN(pairStr, ":", 2) if len(pair) < 2 { - errorExit("bind action not specified: " + origPairStr) + exit("bind action not specified: " + origPairStr) } var key tui.Event if len(pair[0]) == 1 && pair[0][0] == escapedColon { @@ -950,213 +1124,8 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) { keys := parseKeyChords(pair[0], "key name required") key = firstKey(keys) } - - idx2 := len(pair[0]) + 1 - specs := strings.Split(pair[1], "+") - actions := make([]*action, 0, len(specs)) - appendAction := func(types ...actionType) { - actions = append(actions, toActions(types...)...) - } - prevSpec := "" - for specIndex, maskedSpec := range specs { - spec := origPairStr[idx2 : idx2+len(maskedSpec)] - idx2 += len(maskedSpec) + 1 - spec = prevSpec + spec - specLower := strings.ToLower(spec) - switch specLower { - case "ignore": - appendAction(actIgnore) - case "beginning-of-line": - appendAction(actBeginningOfLine) - case "abort": - appendAction(actAbort) - case "accept": - appendAction(actAccept) - case "accept-non-empty": - appendAction(actAcceptNonEmpty) - case "print-query": - appendAction(actPrintQuery) - case "refresh-preview": - appendAction(actRefreshPreview) - case "replace-query": - appendAction(actReplaceQuery) - case "backward-char": - appendAction(actBackwardChar) - case "backward-delete-char": - appendAction(actBackwardDeleteChar) - case "backward-delete-char/eof": - appendAction(actBackwardDeleteCharEOF) - case "backward-word": - appendAction(actBackwardWord) - case "clear-screen": - appendAction(actClearScreen) - case "delete-char": - appendAction(actDeleteChar) - case "delete-char/eof": - appendAction(actDeleteCharEOF) - case "deselect": - appendAction(actDeselect) - case "end-of-line": - appendAction(actEndOfLine) - case "cancel": - appendAction(actCancel) - case "clear-query": - appendAction(actClearQuery) - case "clear-selection": - appendAction(actClearSelection) - case "forward-char": - appendAction(actForwardChar) - case "forward-word": - appendAction(actForwardWord) - case "jump": - appendAction(actJump) - case "jump-accept": - appendAction(actJumpAccept) - case "kill-line": - appendAction(actKillLine) - case "kill-word": - appendAction(actKillWord) - case "unix-line-discard", "line-discard": - appendAction(actUnixLineDiscard) - case "unix-word-rubout", "word-rubout": - appendAction(actUnixWordRubout) - case "yank": - appendAction(actYank) - case "backward-kill-word": - appendAction(actBackwardKillWord) - case "toggle-down": - appendAction(actToggle, actDown) - case "toggle-up": - appendAction(actToggle, actUp) - case "toggle-in": - appendAction(actToggleIn) - case "toggle-out": - appendAction(actToggleOut) - case "toggle-all": - appendAction(actToggleAll) - case "toggle-search": - appendAction(actToggleSearch) - case "select": - appendAction(actSelect) - case "select-all": - appendAction(actSelectAll) - case "deselect-all": - appendAction(actDeselectAll) - case "close": - appendAction(actClose) - case "toggle": - appendAction(actToggle) - case "down": - appendAction(actDown) - case "up": - appendAction(actUp) - case "first", "top": - appendAction(actFirst) - case "last": - appendAction(actLast) - case "page-up": - appendAction(actPageUp) - case "page-down": - appendAction(actPageDown) - case "half-page-up": - appendAction(actHalfPageUp) - case "half-page-down": - appendAction(actHalfPageDown) - case "prev-history", "previous-history": - appendAction(actPrevHistory) - case "next-history": - appendAction(actNextHistory) - case "prev-selected": - appendAction(actPrevSelected) - case "next-selected": - appendAction(actNextSelected) - case "toggle-preview": - appendAction(actTogglePreview) - case "toggle-preview-wrap": - appendAction(actTogglePreviewWrap) - case "toggle-sort": - appendAction(actToggleSort) - case "preview-top": - appendAction(actPreviewTop) - case "preview-bottom": - appendAction(actPreviewBottom) - case "preview-up": - appendAction(actPreviewUp) - case "preview-down": - appendAction(actPreviewDown) - case "preview-page-up": - appendAction(actPreviewPageUp) - case "preview-page-down": - appendAction(actPreviewPageDown) - case "preview-half-page-up": - appendAction(actPreviewHalfPageUp) - case "preview-half-page-down": - appendAction(actPreviewHalfPageDown) - case "enable-search": - appendAction(actEnableSearch) - case "disable-search": - appendAction(actDisableSearch) - case "put": - if key.Type == tui.Rune && unicode.IsGraphic(key.Char) { - appendAction(actRune) - } else { - errorExit("unable to put non-printable character: " + pair[0]) - } - default: - t := isExecuteAction(specLower) - if t == actIgnore { - if specIndex == 0 && specLower == "" { - actions = append(keymap[key], actions...) - } else { - errorExit("unknown action: " + spec) - } - } else { - var offset int - switch t { - case actReload: - offset = len("reload") - case actPreview: - offset = len("preview") - case actChangePreviewWindow: - offset = len("change-preview-window") - case actChangePreview: - offset = len("change-preview") - case actChangePrompt: - offset = len("change-prompt") - case actChangeQuery: - offset = len("change-query") - case actUnbind: - offset = len("unbind") - case actRebind: - offset = len("rebind") - case actExecuteSilent: - offset = len("execute-silent") - case actExecuteMulti: - offset = len("execute-multi") - default: - offset = len("execute") - } - var actionArg string - if spec[offset] == ':' { - if specIndex == len(specs)-1 { - actionArg = spec[offset+1:] - actions = append(actions, &action{t: t, a: actionArg}) - } else { - prevSpec = spec + "+" - continue - } - } else { - actionArg = spec[offset+1 : len(spec)-1] - actions = append(actions, &action{t: t, a: actionArg}) - } - if t == actUnbind || t == actRebind { - parseKeyChords(actionArg, spec[0:offset]+" target required") - } - } - } - prevSpec = "" - } - keymap[key] = actions + putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char) + keymap[key] = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed, exit) } } @@ -1455,7 +1424,7 @@ func parseOptions(opts *Options, allArgs []string) { case "--tiebreak": opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required")) case "--bind": - parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required")) + parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"), errorExit) case "--color": _, spec := optionalNextString(allArgs, &i) if len(spec) == 0 { @@ -1657,6 +1626,10 @@ func parseOptions(opts *Options, allArgs []string) { nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) case "--tabstop": opts.Tabstop = nextInt(allArgs, &i, "tab stop required") + case "--listen": + opts.ListenPort = nextInt(allArgs, &i, "listen port required") + case "--no-listen": + opts.ListenPort = 0 case "--clear": opts.ClearOnExit = true case "--no-clear": @@ -1723,7 +1696,7 @@ func parseOptions(opts *Options, allArgs []string) { } else if match, value := optString(arg, "--color="); match { opts.Theme = parseTheme(opts.Theme, value) } else if match, value := optString(arg, "--bind="); match { - parseKeymap(opts.Keymap, value) + parseKeymap(opts.Keymap, value, errorExit) } else if match, value := optString(arg, "--history="); match { setHistory(value) } else if match, value := optString(arg, "--history-size="); match { @@ -1744,6 +1717,8 @@ func parseOptions(opts *Options, allArgs []string) { opts.Padding = parseMargin("padding", value) } else if match, value := optString(arg, "--tabstop="); match { opts.Tabstop = atoi(value) + } else if match, value := optString(arg, "--listen="); match { + opts.ListenPort = atoi(value) } else if match, value := optString(arg, "--hscroll-off="); match { opts.HscrollOff = atoi(value) } else if match, value := optString(arg, "--scroll-off="); match { @@ -1773,6 +1748,10 @@ func parseOptions(opts *Options, allArgs []string) { errorExit("tab stop must be a positive integer") } + if opts.ListenPort < 0 || opts.ListenPort > 65535 { + errorExit("invalid listen port") + } + if len(opts.JumpLabels) == 0 { errorExit("empty jump labels") } |