Add GET endpoints for getting the state of the finder

* GET / (or GET /current)
* GET /query
This commit is contained in:
Junegunn Choi
2022-12-25 16:27:02 +09:00
parent de0da86bd7
commit 750b2a6313
6 changed files with 66 additions and 22 deletions

View File

@@ -113,7 +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 /)
--listen=HTTP_PORT Start HTTP server to receive actions
--version Display version information and exit
Environment variables

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"time"
@@ -13,13 +14,18 @@ import (
const (
crlf = "\r\n"
httpPattern = "^(GET|POST) (/[^ ]*) HTTP"
httpOk = "HTTP/1.1 200 OK" + crlf
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
httpReadTimeout = 10 * time.Second
maxContentLength = 1024 * 1024
)
func startHttpServer(port int, channel chan []*action) error {
var (
httpRegexp *regexp.Regexp
)
func startHttpServer(port int, requestChan chan []*action, responseChan chan string) error {
if port == 0 {
return nil
}
@@ -29,6 +35,7 @@ func startHttpServer(port int, channel chan []*action) error {
return fmt.Errorf("port not available: %d", port)
}
httpRegexp = regexp.MustCompile(httpPattern)
go func() {
for {
conn, err := listener.Accept()
@@ -39,7 +46,7 @@ func startHttpServer(port int, channel chan []*action) error {
continue
}
}
conn.Write([]byte(handleHttpRequest(conn, channel)))
conn.Write([]byte(handleHttpRequest(conn, requestChan, responseChan)))
conn.Close()
}
listener.Close()
@@ -54,12 +61,14 @@ func startHttpServer(port int, channel chan []*action) error {
// * No --listen: 2.8MB
// * --listen with net/http: 5.7MB
// * --listen w/o net/http: 3.3MB
func handleHttpRequest(conn net.Conn, channel chan []*action) string {
func handleHttpRequest(conn net.Conn, requestChan chan []*action, responseChan chan string) string {
contentLength := 0
body := ""
response := func(header string, message string) string {
return header + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
}
bad := func(message string) string {
message += "\n"
return httpBadRequest + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
return response(httpBadRequest, strings.TrimSpace(message)+"\n")
}
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
scanner := bufio.NewScanner(conn)
@@ -80,8 +89,13 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
text := scanner.Text()
switch section {
case 0:
if !strings.HasPrefix(text, "POST / HTTP") {
return bad("invalid request method")
httpMatch := httpRegexp.FindStringSubmatch(text)
if len(httpMatch) != 3 {
return bad("invalid HTTP request: " + text)
}
if httpMatch[1] == "GET" {
requestChan <- []*action{{t: actEvaluate, a: httpMatch[2][1:]}}
return response(httpOk, <-responseChan)
}
section++
case 1:
@@ -120,7 +134,6 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
if len(actions) == 0 {
return bad("no action specified")
}
channel <- actions
requestChan <- actions
return httpOk
}

View File

@@ -201,7 +201,8 @@ type Terminal struct {
sigstop bool
startChan chan fitpad
killChan chan int
serverChan chan []*action
serverRequestChan chan []*action
serverResponseChan chan string
slab *util.Slab
theme *tui.ColorTheme
tui tui.Renderer
@@ -276,6 +277,7 @@ const (
actDeleteChar
actDeleteCharEOF
actEndOfLine
actEvaluate
actForwardChar
actForwardWord
actKillLine
@@ -599,7 +601,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
theme: opts.Theme,
startChan: make(chan fitpad, 1),
killChan: make(chan int),
serverChan: make(chan []*action),
serverRequestChan: make(chan []*action),
serverResponseChan: make(chan string),
tui: renderer,
initFunc: func() { renderer.Init() },
executing: util.NewAtomicBool(false)}
@@ -621,7 +624,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
}
if err := startHttpServer(t.listenPort, t.serverChan); err != nil {
if err := startHttpServer(t.listenPort, t.serverRequestChan, t.serverResponseChan); err != nil {
errorExit(err.Error())
}
@@ -2531,7 +2534,7 @@ func (t *Terminal) Loop() {
select {
case event = <-eventChan:
needBarrier = true
case actions = <-t.serverChan:
case actions = <-t.serverRequestChan:
event = tui.Invalid.AsEvent()
needBarrier = false
}
@@ -2614,6 +2617,15 @@ func (t *Terminal) Loop() {
t.executeCommand(a.a, false, a.t == actExecuteSilent)
case actExecuteMulti:
t.executeCommand(a.a, true, false)
case actEvaluate:
response := ""
switch a.a {
case "", "current":
response = t.currentItem().AsString(t.ansi)
case "query":
response = string(t.input)
}
t.serverResponseChan <- response
case actInvalid:
t.mutex.Unlock()
return false