From ec9e472797f9012848c3a880ceed568beeaecc96 Mon Sep 17 00:00:00 2001 From: Benaiah Mischenko Date: Thu, 2 Mar 2017 23:05:04 -0800 Subject: [PATCH 1/2] Autocomplete request headers --- request-headers.go | 37 +++++++++++ wuzz.go | 155 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 request-headers.go diff --git a/request-headers.go b/request-headers.go new file mode 100644 index 0000000..2fa9590 --- /dev/null +++ b/request-headers.go @@ -0,0 +1,37 @@ +package main + +var REQUEST_HEADERS = []string{ + "Accept", + "Accept-Charset", + "Accept-Encoding", + "Accept-Language", + "Accept-Datetime", + "Authorization", + "Cache-Control", + "Connection", + "Cookie", + "Content-Length", + "Content-MD5", + "Content-Type", + "Date", + "Expect", + "Forwarded", + "From", + "Host", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Max-Forwards", + "Origin", + "Pragma", + "Proxy-Authorization", + "Range", + "Referer", + "TE", + "User-Agent", + "Upgrade", + "Via", + "Warning", +} diff --git a/wuzz.go b/wuzz.go index 215534e..8e4e08c 100644 --- a/wuzz.go +++ b/wuzz.go @@ -48,6 +48,7 @@ const ( SEARCH_PROMPT_VIEW = "prompt" POPUP_VIEW = "popup_view" + AUTOCOMPLETE_VIEW = "autocomplete_view" ERROR_VIEW = "error_view" HISTORY_VIEW = "history" SAVE_DIALOG_VIEW = "save-dialog" @@ -131,6 +132,11 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.5, -1}, position{0.5, -9999}, // set before usage using len(msg) position{0.5, 1}}, + AUTOCOMPLETE_VIEW: { + position{0, -9999}, + position{0, -9999}, + position{0, -9999}, + position{0, -9999}}, } type viewProperties struct { @@ -177,7 +183,9 @@ var VIEW_PROPERTIES = map[string]viewProperties{ frame: true, editable: true, wrap: false, - editor: &defaultEditor, + editor: &AutocompleteEditor{&defaultEditor, func(str string) []string { + return completeFromSlice(str, REQUEST_HEADERS) + }, []string{}, false}, }, RESPONSE_HEADERS_VIEW: { title: "Response headers", @@ -215,6 +223,13 @@ var VIEW_PROPERTIES = map[string]viewProperties{ wrap: false, editor: nil, }, + AUTOCOMPLETE_VIEW: { + title: "", + frame: false, + editable: false, + wrap: false, + editor: nil, + }, } var METHODS = []string{ @@ -289,6 +304,13 @@ type ViewEditor struct { origEditor gocui.Editor } +type AutocompleteEditor struct { + wuzzEditor *ViewEditor + completions func(string) []string + currentCompletions []string + isAutocompleting bool +} + type SearchEditor struct { wuzzEditor *ViewEditor } @@ -333,6 +355,88 @@ func (e *ViewEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modif e.origEditor.Edit(v, key, ch, mod) } +var symbolPattern = regexp.MustCompile("[a-zA-Z0-9-]+$") + +func getLastSymbol(str string) string { + return symbolPattern.FindString(str) +} + +func completeFromSlice(str string, completions []string) []string { + completed := []string{} + if str == "" || strings.TrimRight(str, " \n") != str { + return completed + } + for _, completion := range completions { + if strings.HasPrefix(completion, str) && str != completion { + completed = append(completed, completion) + } + } + return completed +} + +func (e *AutocompleteEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + if key != gocui.KeyEnter { + e.wuzzEditor.Edit(v, key, ch, mod) + } + + cx, cy := v.Cursor() + line, err := v.Line(cy) + trimmedLine := line[:cx] + + if err != nil { + e.wuzzEditor.Edit(v, key, ch, mod) + return + } + + lastSymbol := getLastSymbol(trimmedLine) + if key == gocui.KeyEnter && e.isAutocompleting { + currentCompletion := e.currentCompletions[0] + shouldDelete := true + if len(e.currentCompletions) == 1 { + shouldDelete = false + } + + if shouldDelete { + for _ = range lastSymbol { + v.EditDelete(true) + } + } + for _, char := range currentCompletion { + v.EditWrite(char) + } + closeAutocomplete(e.wuzzEditor.g) + e.isAutocompleting = false + return + } else if key == gocui.KeyEnter { + e.wuzzEditor.Edit(v, key, ch, mod) + } + + closeAutocomplete(e.wuzzEditor.g) + e.isAutocompleting = false + + completions := e.completions(lastSymbol) + e.currentCompletions = completions + + cx, cy = v.Cursor() + maxX, maxY := e.wuzzEditor.g.Size() + sx, _ := v.Size() + pos := VIEW_POSITIONS[v.Name()] + ox := pos.x0.getCoordinate(maxX) + oy := pos.y0.getCoordinate(maxY) + + maxWidth := sx - cx + maxHeight := 10 + + if len(completions) > 0 { + comps := completions + if len(comps) == 1 { + comps[0] = comps[0][len(lastSymbol):] + } + showAutocomplete(comps, ox+cx, oy+cy, maxWidth, maxHeight, e.wuzzEditor.g) + e.isAutocompleting = true + } +} + func (e *SearchEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { e.wuzzEditor.Edit(v, key, ch, mod) e.wuzzEditor.g.Execute(func(g *gocui.Gui) error { @@ -508,6 +612,55 @@ func popup(g *gocui.Gui, msg string) { } } +func minInt(x, y int) int { + if x < y { + return x + } + return y +} + +func closeAutocomplete(g *gocui.Gui) { + g.DeleteView(AUTOCOMPLETE_VIEW) +} + +func showAutocomplete(completions []string, left, top, maxWidth, maxHeight int, g *gocui.Gui) { + // Get the width of the widest completion + completionsWidth := 0 + for _, completion := range completions { + thisCompletionWidth := len(completion) + if thisCompletionWidth > completionsWidth { + completionsWidth = thisCompletionWidth + } + } + + // Get the width and height of the autocomplete window + width := minInt(completionsWidth, maxWidth) + height := minInt(len(completions), maxHeight) + + newPos := viewPosition{ + x0: position{0, left}, + y0: position{0, top}, + x1: position{0, left + width + 1}, + y1: position{0, top + height + 1}, + } + + VIEW_POSITIONS[AUTOCOMPLETE_VIEW] = newPos + + p := VIEW_PROPERTIES[AUTOCOMPLETE_VIEW] + p.text = strings.Join(append(completions, fmt.Sprint(maxHeight)+"x"+fmt.Sprint(maxWidth)), "\n") + VIEW_PROPERTIES[AUTOCOMPLETE_VIEW] = p + + if v, err := setView(g, AUTOCOMPLETE_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return + } + setViewProperties(v, AUTOCOMPLETE_VIEW) + v.BgColor = gocui.ColorRed + v.FgColor = gocui.ColorDefault + g.SetViewOnTop(AUTOCOMPLETE_VIEW) + } +} + func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { vrb, _ := g.View(RESPONSE_BODY_VIEW) vrb.Clear() From a8baa739d2b4c34c22248b318eec1ec5c49a074f Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Fri, 3 Mar 2017 20:21:24 +0100 Subject: [PATCH 2/2] [fix] autocomplete positioning --- wuzz.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/wuzz.go b/wuzz.go index 8e4e08c..c834170 100644 --- a/wuzz.go +++ b/wuzz.go @@ -418,21 +418,24 @@ func (e *AutocompleteEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod goc e.currentCompletions = completions cx, cy = v.Cursor() - maxX, maxY := e.wuzzEditor.g.Size() sx, _ := v.Size() - pos := VIEW_POSITIONS[v.Name()] - ox := pos.x0.getCoordinate(maxX) - oy := pos.y0.getCoordinate(maxY) + ox, oy, _, _, _ := e.wuzzEditor.g.ViewPosition(v.Name()) maxWidth := sx - cx maxHeight := 10 if len(completions) > 0 { comps := completions + x := ox+cx + y := oy+cy if len(comps) == 1 { comps[0] = comps[0][len(lastSymbol):] + } else { + y += 1 + x -= len(lastSymbol) + maxWidth += len(lastSymbol) } - showAutocomplete(comps, ox+cx, oy+cy, maxWidth, maxHeight, e.wuzzEditor.g) + showAutocomplete(comps, x, y, maxWidth, maxHeight, e.wuzzEditor.g) e.isAutocompleting = true } } @@ -647,7 +650,7 @@ func showAutocomplete(completions []string, left, top, maxWidth, maxHeight int, VIEW_POSITIONS[AUTOCOMPLETE_VIEW] = newPos p := VIEW_PROPERTIES[AUTOCOMPLETE_VIEW] - p.text = strings.Join(append(completions, fmt.Sprint(maxHeight)+"x"+fmt.Sprint(maxWidth)), "\n") + p.text = strings.Join(completions, "\n") VIEW_PROPERTIES[AUTOCOMPLETE_VIEW] = p if v, err := setView(g, AUTOCOMPLETE_VIEW); err != nil { @@ -655,7 +658,7 @@ func showAutocomplete(completions []string, left, top, maxWidth, maxHeight int, return } setViewProperties(v, AUTOCOMPLETE_VIEW) - v.BgColor = gocui.ColorRed + v.BgColor = gocui.ColorBlue v.FgColor = gocui.ColorDefault g.SetViewOnTop(AUTOCOMPLETE_VIEW) }