summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2023-03-19 15:42:47 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2023-03-19 15:48:39 +0900
commitfcd7e8768dc4a23f6e4f1aec57c9d2236ebe7fae (patch)
tree7d39262b031120ceebf516a0e844b4ac26766edd
parent3c34dd82750ca61a1ee7f329ed47fe01e6d1ee30 (diff)
Omit port number in `--listen` for automatic port assignment
Close #3200
-rw-r--r--CHANGELOG.md14
-rw-r--r--man/man1/fzf.110
-rw-r--r--src/options.go14
-rw-r--r--src/server.go22
-rw-r--r--src/terminal.go25
-rwxr-xr-xtest/test_go.rb16
6 files changed, 77 insertions, 24 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48c2c966..ba10cd1e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,20 @@
CHANGELOG
=========
+0.39.0
+------
+- If you use `--listen` option without a port number fzf will automatically
+ allocate an available port and export it as `$FZF_PORT` environment
+ variable.
+ ```sh
+ # Automatic port assignment
+ fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
+
+ # Say hello
+ curl "localhost:$(cat /tmp/fzf-port)" -d 'preview:echo Hello, fzf is listening on $FZF_PORT.'
+ ```
+- Bug fixes
+
0.38.0
------
- New actions
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 6d6d49bd..a538a91c 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -738,9 +738,12 @@ ncurses finder only after the input stream is complete.
e.g. \fBfzf --multi | fzf --sync\fR
.RE
.TP
-.B "--listen=HTTP_PORT"
+.B "--listen[=HTTP_PORT]"
Start HTTP server on the given port. It allows external processes to send
-actions to perform via POST method.
+actions to perform via POST method. If the port number is omitted or given as
+0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
+environment variable to the child processes started via \fBexecute\fR and
+\fBexecute-silent\fR actions.
e.g.
\fB# Start HTTP server on port 6266
@@ -748,6 +751,9 @@ e.g.
# Send action to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
+
+ # Choose port automatically and export it as $FZF_PORT to the child process
+ fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
\fR
.TP
.B "--version"
diff --git a/src/options.go b/src/options.go
index 9be29107..f2de1d75 100644
--- a/src/options.go
+++ b/src/options.go
@@ -116,7 +116,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 /)
+ --listen[=HTTP_PORT] Start HTTP server to receive actions (POST /)
--version Display version information and exit
Environment variables
@@ -316,7 +316,7 @@ type Options struct {
PreviewLabel labelOpts
Unicode bool
Tabstop int
- ListenPort int
+ ListenPort *int
ClearOnExit bool
Version bool
}
@@ -1756,9 +1756,10 @@ func parseOptions(opts *Options, allArgs []string) {
case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--listen":
- opts.ListenPort = nextInt(allArgs, &i, "listen port required")
+ port := optionalNumeric(allArgs, &i, 0)
+ opts.ListenPort = &port
case "--no-listen":
- opts.ListenPort = 0
+ opts.ListenPort = nil
case "--clear":
opts.ClearOnExit = true
case "--no-clear":
@@ -1849,7 +1850,8 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--listen="); match {
- opts.ListenPort = atoi(value)
+ port := atoi(value)
+ opts.ListenPort = &port
} else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--scroll-off="); match {
@@ -1879,7 +1881,7 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit("tab stop must be a positive integer")
}
- if opts.ListenPort < 0 || opts.ListenPort > 65535 {
+ if opts.ListenPort != nil && (*opts.ListenPort < 0 || *opts.ListenPort > 65535) {
errorExit("invalid listen port")
}
diff --git a/src/server.go b/src/server.go
index 2912b1a4..89a7938c 100644
--- a/src/server.go
+++ b/src/server.go
@@ -19,14 +19,26 @@ const (
maxContentLength = 1024 * 1024
)
-func startHttpServer(port int, channel chan []*action) error {
- if port == 0 {
- return nil
+func startHttpServer(port int, channel chan []*action) (error, int) {
+ if port < 0 {
+ return nil, port
}
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
if err != nil {
- return fmt.Errorf("port not available: %d", port)
+ return fmt.Errorf("port not available: %d", port), port
+ }
+ if port == 0 {
+ addr := listener.Addr().String()
+ parts := strings.SplitN(addr, ":", 2)
+ if len(parts) < 2 {
+ return fmt.Errorf("cannot extract port: %s", addr), port
+ }
+ var err error
+ port, err = strconv.Atoi(parts[1])
+ if err != nil {
+ return err, port
+ }
}
go func() {
@@ -45,7 +57,7 @@ func startHttpServer(port int, channel chan []*action) error {
listener.Close()
}()
- return nil
+ return nil, port
}
// Here we are writing a simplistic HTTP server without using net/http
diff --git a/src/terminal.go b/src/terminal.go
index 0310a9f0..dfc21a3b 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -203,7 +203,7 @@ type Terminal struct {
padding [4]sizeSpec
strong tui.Attr
unicode bool
- listenPort int
+ listenPort *int
borderShape tui.BorderShape
cleanExit bool
paused bool
@@ -538,7 +538,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
}
var previewBox *util.EventBox
// We need to start previewer if HTTP server is enabled even when --preview option is not specified
- if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenPort > 0 {
+ if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenPort != nil {
previewBox = util.NewEventBox()
}
strongAttr := tui.Bold
@@ -694,13 +694,25 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
- if err := startHttpServer(t.listenPort, t.serverChan); err != nil {
- errorExit(err.Error())
+ if t.listenPort != nil {
+ err, port := startHttpServer(*t.listenPort, t.serverChan)
+ if err != nil {
+ errorExit(err.Error())
+ }
+ t.listenPort = &port
}
return &t
}
+func (t *Terminal) environ() []string {
+ env := os.Environ()
+ if t.listenPort != nil {
+ env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
+ }
+ return env
+}
+
func borderLines(shape tui.BorderShape) int {
switch shape {
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
@@ -2248,6 +2260,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
}
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command, false)
+ cmd.Env = t.environ()
t.executing.Set(true)
if !background {
cmd.Stdin = tui.TtyIn()
@@ -2494,17 +2507,17 @@ func (t *Terminal) Loop() {
_, query := t.Input()
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
cmd := util.ExecCommand(command, true)
+ env := t.environ()
if pwindow != nil {
height := pwindow.Height()
- env := os.Environ()
lines := fmt.Sprintf("LINES=%d", height)
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
env = append(env, lines)
env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
- cmd.Env = env
}
+ cmd.Env = env
out, _ := cmd.StdoutPipe()
cmd.Stderr = cmd.Stdout
diff --git a/test/test_go.rb b/test/test_go.rb
index 07a7c195..de81f6e5 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -2629,11 +2629,17 @@ class TestGoFZF < TestBase
end
def test_listen
- tmux.send_keys 'seq 10 | fzf --listen 6266', :Enter
- tmux.until { |lines| assert_equal 10, lines.item_count }
- Net::HTTP.post(URI('http://localhost:6266'), 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
- tmux.until { |lines| assert_equal 100, lines.item_count }
- tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
+ { '--listen 6266' => lambda { URI('http://localhost:6266') },
+ "--listen --sync --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'" =>
+ lambda { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
+ tmux.send_keys "seq 10 | fzf #{opts}", :Enter
+ tmux.until { |lines| assert_equal 10, lines.item_count }
+ Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
+ tmux.until { |lines| assert_equal 100, lines.item_count }
+ tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
+ teardown
+ setup
+ end
end
def test_toggle_alternative_preview_window