From f39864cbfee76fb5d365cb581bba40e5f31c00c6 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Fri, 17 Feb 2017 00:22:58 +0100 Subject: [PATCH 01/15] Moved most view properties to the top, leaving Layout function much smaller --- wuzz.go | 247 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 133 insertions(+), 114 deletions(-) diff --git a/wuzz.go b/wuzz.go index 8038ce7..cb3b5bc 100644 --- a/wuzz.go +++ b/wuzz.go @@ -31,6 +31,7 @@ const VERSION = "0.1.0" const TIMEOUT_DURATION = 5 // in seconds const WINDOWS_OS = "windows" +const SEARCH_PROMPT = "search> " const ( ALL_VIEWS = "" @@ -44,31 +45,23 @@ const ( RESPONSE_HEADERS_VIEW = "response-headers" RESPONSE_BODY_VIEW = "response-body" - PROMPT = "prompt" - POPUP_VIEW = "popup_view" - ERROR_VIEW = "error_view" - HISTORY_VIEW = "history" - SAVE_DIALOG_VIEW = "save-dialog" - SAVE_RESULT_VIEW = "save-result" - METHOD_LIST_VIEW = "method-list" - HELP_VIEW = "help" + SEARCH_PROMPT_VIEW = "prompt" + POPUP_VIEW = "popup_view" + ERROR_VIEW = "error_view" + HISTORY_VIEW = "history" + SAVE_DIALOG_VIEW = "save-dialog" + SAVE_RESULT_VIEW = "save-result" + METHOD_LIST_VIEW = "method-list" + HELP_VIEW = "help" ) var VIEW_TITLES = map[string]string{ - URL_VIEW: "URL - press F1 for help", - URL_PARAMS_VIEW: "URL params", - REQUEST_METHOD_VIEW: "Method", - REQUEST_DATA_VIEW: "Request data (POST/PUT)", - REQUEST_HEADERS_VIEW: "Request headers", - SEARCH_VIEW: "search> ", - RESPONSE_HEADERS_VIEW: "Response headers", - RESPONSE_BODY_VIEW: "Response body", - POPUP_VIEW: "Info", ERROR_VIEW: "Error", HISTORY_VIEW: "History", SAVE_DIALOG_VIEW: "Save Response (enter to submit, ctrl+q to cancel)", METHOD_LIST_VIEW: "Methods", + HELP_VIEW: "Help", } type position struct { @@ -127,7 +120,7 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.0, 0}, position{1.0, -2}, position{1.0, -2}}, - PROMPT: { + SEARCH_PROMPT_VIEW: { position{0.0, -1}, position{1.0, -3}, position{0.0, 8}, @@ -139,6 +132,80 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.5, 1}}, } +type viewProperties struct { + title string + frame bool + editable bool + wrap bool + editor gocui.Editor +} + +var VIEW_PROPERTIES = map[string]viewProperties{ + URL_VIEW: { + title: "URL - press F1 for help", + frame: true, + editable: true, + wrap: true, + editor: &singleLineEditor{&defaultEditor}, + }, + URL_PARAMS_VIEW: { + title: "URL params", + frame: true, + editable: true, + wrap: true, + editor: &defaultEditor, + }, + REQUEST_METHOD_VIEW: { + title: "Method", + frame: true, + editable: true, + wrap: true, + editor: &singleLineEditor{&defaultEditor}, + }, + REQUEST_DATA_VIEW: { + title: "Request data (POST/PUT)", + frame: true, + editable: true, + wrap: true, + editor: &defaultEditor, + }, + REQUEST_HEADERS_VIEW: { + title: "Request headers", + frame: true, + editable: true, + wrap: false, + editor: &defaultEditor, + }, + RESPONSE_HEADERS_VIEW: { + title: "Response headers", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, + RESPONSE_BODY_VIEW: { + title: "Response body", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, + SEARCH_VIEW: { + title: "", + frame: false, + editable: true, + wrap: true, + editor: &singleLineEditor{&SearchEditor{&defaultEditor}}, + }, + SEARCH_PROMPT_VIEW: { + title: "", + frame: false, + editable: false, + wrap: true, + editor: nil, + }, +} + var METHODS []string = []string{ http.MethodGet, http.MethodPost, @@ -278,26 +345,31 @@ func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui. // +func (a *App) getResponseViewEditor(g *gocui.Gui) gocui.Editor { + return &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + return + })} +} + func (p position) getCoordinate(max int) int { return int(p.pct*float32(max)) + p.abs } -func setView(g *gocui.Gui, maxX, maxY int, viewName string) (*gocui.View, error) { +func setView(g *gocui.Gui, viewName string) (*gocui.View, error) { + maxX, maxY := g.Size() position := VIEW_POSITIONS[viewName] return g.SetView(viewName, - position.x0.getCoordinate(maxX), - position.y0.getCoordinate(maxY), - position.x1.getCoordinate(maxX), - position.y1.getCoordinate(maxY)) + position.x0.getCoordinate(maxX+1), + position.y0.getCoordinate(maxY+1), + position.x1.getCoordinate(maxX+1), + position.y1.getCoordinate(maxY+1)) } func (a *App) Layout(g *gocui.Gui) error { maxX, maxY := g.Size() - maxX++ - maxY++ - if maxX-1 < MIN_WIDTH || maxY-1 < MIN_HEIGHT { - if v, err := setView(g, maxX, maxY, ERROR_VIEW); err != nil { + if maxX < MIN_WIDTH || maxY < MIN_HEIGHT { + if v, err := setView(g, ERROR_VIEW); err != nil { if err != gocui.ErrUnknownView { return err } @@ -314,95 +386,45 @@ func (a *App) Layout(g *gocui.Gui) error { a.setView(g) } - if v, err := setView(g, maxX, maxY, URL_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Title = VIEW_TITLES[URL_VIEW] - v.Editable = true - v.Overwrite = false - v.Editor = &singleLineEditor{&defaultEditor} - setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") + for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { + vp := VIEW_PROPERTIES[name] + vp.editor = a.getResponseViewEditor(g) + VIEW_PROPERTIES[name] = vp } - if v, err := setView(g, maxX, maxY, URL_PARAMS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[URL_PARAMS_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, REQUEST_METHOD_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_METHOD_VIEW] - v.Editor = &singleLineEditor{&defaultEditor} - setViewTextAndCursor(v, DEFAULT_METHOD) - } - if v, err := setView(g, maxX, maxY, REQUEST_DATA_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_DATA_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, REQUEST_HEADERS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Wrap = false - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_HEADERS_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, RESPONSE_HEADERS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err + for _, name := range []string{ + URL_VIEW, + URL_PARAMS_VIEW, + REQUEST_METHOD_VIEW, + REQUEST_DATA_VIEW, + REQUEST_HEADERS_VIEW, + RESPONSE_HEADERS_VIEW, + RESPONSE_BODY_VIEW, + SEARCH_PROMPT_VIEW, + SEARCH_VIEW, + } { + if v, err := setView(g, name); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = VIEW_PROPERTIES[name].title + v.Frame = VIEW_PROPERTIES[name].frame + v.Editable = VIEW_PROPERTIES[name].editable + v.Wrap = VIEW_PROPERTIES[name].wrap + v.Editor = VIEW_PROPERTIES[name].editor } - setViewDefaults(v) - v.Title = VIEW_TITLES[RESPONSE_HEADERS_VIEW] - v.Editable = true - v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} } - if v, err := setView(g, maxX, maxY, RESPONSE_BODY_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Title = VIEW_TITLES[RESPONSE_BODY_VIEW] - v.Editable = true - v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} + + if v, err := g.View(URL_VIEW); err == nil { + setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") } - if v, err := setView(g, maxX, maxY, PROMPT); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Frame = false - v.Wrap = true - setViewTextAndCursor(v, VIEW_TITLES[SEARCH_VIEW]) + if v, err := g.View(REQUEST_METHOD_VIEW); err == nil { + setViewTextAndCursor(v, DEFAULT_METHOD) } - if v, err := setView(g, maxX, maxY, SEARCH_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Frame = false - v.Editable = true - v.Editor = &singleLineEditor{&SearchEditor{&defaultEditor}} - v.Wrap = true + if v, err := g.View(SEARCH_PROMPT_VIEW); err == nil { + setViewTextAndCursor(v, SEARCH_PROMPT) } + return nil } @@ -435,15 +457,12 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { func popup(g *gocui.Gui, msg string) { var popup *gocui.View var err error - maxX, maxY := g.Size() - maxX++ - maxY++ p := VIEW_POSITIONS[POPUP_VIEW] p.x0.abs = -len(msg)/2 - 1 p.x1.abs = len(msg)/2 + 1 VIEW_POSITIONS[POPUP_VIEW] = p - if popup, err = setView(g, maxX, maxY, POPUP_VIEW); err != nil { + if popup, err = setView(g, POPUP_VIEW); err != nil { if err != gocui.ErrUnknownView { return } @@ -756,7 +775,7 @@ func (a *App) SetKeys(g *gocui.Gui) error { if err != nil { return err } - help.Title = "Help" + help.Title = VIEW_TITLES[HELP_VIEW] help.Highlight = false fmt.Fprint(help, "Keybindings:\n") a.printViewKeybindings(help, "global") From 54ef832dea7be3ae67ab00c734e4a907e515c997 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Fri, 17 Feb 2017 00:29:37 +0100 Subject: [PATCH 02/15] [fix] disable wrapping of editable views - related to #61 --- wuzz.go | 248 ++++++++++++++++++++++++++------------------------------ 1 file changed, 114 insertions(+), 134 deletions(-) diff --git a/wuzz.go b/wuzz.go index 6bacee0..19f0171 100644 --- a/wuzz.go +++ b/wuzz.go @@ -32,7 +32,6 @@ const VERSION = "0.1.0" const TIMEOUT_DURATION = 5 // in seconds const WINDOWS_OS = "windows" -const SEARCH_PROMPT = "search> " const ( ALL_VIEWS = "" @@ -46,23 +45,31 @@ const ( RESPONSE_HEADERS_VIEW = "response-headers" RESPONSE_BODY_VIEW = "response-body" - SEARCH_PROMPT_VIEW = "prompt" - POPUP_VIEW = "popup_view" - ERROR_VIEW = "error_view" - HISTORY_VIEW = "history" - SAVE_DIALOG_VIEW = "save-dialog" - SAVE_RESULT_VIEW = "save-result" - METHOD_LIST_VIEW = "method-list" - HELP_VIEW = "help" + PROMPT = "prompt" + POPUP_VIEW = "popup_view" + ERROR_VIEW = "error_view" + HISTORY_VIEW = "history" + SAVE_DIALOG_VIEW = "save-dialog" + SAVE_RESULT_VIEW = "save-result" + METHOD_LIST_VIEW = "method-list" + HELP_VIEW = "help" ) var VIEW_TITLES = map[string]string{ + URL_VIEW: "URL - press F1 for help", + URL_PARAMS_VIEW: "URL params", + REQUEST_METHOD_VIEW: "Method", + REQUEST_DATA_VIEW: "Request data (POST/PUT/PATCH)", + REQUEST_HEADERS_VIEW: "Request headers", + SEARCH_VIEW: "search> ", + RESPONSE_HEADERS_VIEW: "Response headers", + RESPONSE_BODY_VIEW: "Response body", + POPUP_VIEW: "Info", ERROR_VIEW: "Error", HISTORY_VIEW: "History", SAVE_DIALOG_VIEW: "Save Response (enter to submit, ctrl+q to cancel)", METHOD_LIST_VIEW: "Methods", - HELP_VIEW: "Help", } type position struct { @@ -121,7 +128,7 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.0, 0}, position{1.0, -2}, position{1.0, -2}}, - SEARCH_PROMPT_VIEW: { + PROMPT: { position{0.0, -1}, position{1.0, -3}, position{0.0, 8}, @@ -133,80 +140,6 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.5, 1}}, } -type viewProperties struct { - title string - frame bool - editable bool - wrap bool - editor gocui.Editor -} - -var VIEW_PROPERTIES = map[string]viewProperties{ - URL_VIEW: { - title: "URL - press F1 for help", - frame: true, - editable: true, - wrap: true, - editor: &singleLineEditor{&defaultEditor}, - }, - URL_PARAMS_VIEW: { - title: "URL params", - frame: true, - editable: true, - wrap: true, - editor: &defaultEditor, - }, - REQUEST_METHOD_VIEW: { - title: "Method", - frame: true, - editable: true, - wrap: true, - editor: &singleLineEditor{&defaultEditor}, - }, - REQUEST_DATA_VIEW: { - title: "Request data (POST/PUT/PATCH)", - frame: true, - editable: true, - wrap: true, - editor: &defaultEditor, - }, - REQUEST_HEADERS_VIEW: { - title: "Request headers", - frame: true, - editable: true, - wrap: false, - editor: &defaultEditor, - }, - RESPONSE_HEADERS_VIEW: { - title: "Response headers", - frame: true, - editable: true, - wrap: true, - editor: nil, // should be set using a.getViewEditor(g) - }, - RESPONSE_BODY_VIEW: { - title: "Response body", - frame: true, - editable: true, - wrap: true, - editor: nil, // should be set using a.getViewEditor(g) - }, - SEARCH_VIEW: { - title: "", - frame: false, - editable: true, - wrap: true, - editor: &singleLineEditor{&SearchEditor{&defaultEditor}}, - }, - SEARCH_PROMPT_VIEW: { - title: "", - frame: false, - editable: false, - wrap: true, - editor: nil, - }, -} - var METHODS []string = []string{ http.MethodGet, http.MethodPost, @@ -346,31 +279,26 @@ func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui. // -func (a *App) getResponseViewEditor(g *gocui.Gui) gocui.Editor { - return &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} -} - func (p position) getCoordinate(max int) int { return int(p.pct*float32(max)) + p.abs } -func setView(g *gocui.Gui, viewName string) (*gocui.View, error) { - maxX, maxY := g.Size() +func setView(g *gocui.Gui, maxX, maxY int, viewName string) (*gocui.View, error) { position := VIEW_POSITIONS[viewName] return g.SetView(viewName, - position.x0.getCoordinate(maxX+1), - position.y0.getCoordinate(maxY+1), - position.x1.getCoordinate(maxX+1), - position.y1.getCoordinate(maxY+1)) + position.x0.getCoordinate(maxX), + position.y0.getCoordinate(maxY), + position.x1.getCoordinate(maxX), + position.y1.getCoordinate(maxY)) } func (a *App) Layout(g *gocui.Gui) error { maxX, maxY := g.Size() + maxX++ + maxY++ - if maxX < MIN_WIDTH || maxY < MIN_HEIGHT { - if v, err := setView(g, ERROR_VIEW); err != nil { + if maxX-1 < MIN_WIDTH || maxY-1 < MIN_HEIGHT { + if v, err := setView(g, maxX, maxY, ERROR_VIEW); err != nil { if err != gocui.ErrUnknownView { return err } @@ -387,45 +315,94 @@ func (a *App) Layout(g *gocui.Gui) error { a.setView(g) } - for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { - vp := VIEW_PROPERTIES[name] - vp.editor = a.getResponseViewEditor(g) - VIEW_PROPERTIES[name] = vp + if v, err := setView(g, maxX, maxY, URL_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewDefaults(v) + v.Title = VIEW_TITLES[URL_VIEW] + v.Editable = true + v.Overwrite = false + v.Editor = &singleLineEditor{&defaultEditor} + setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") } - - for _, name := range []string{ - URL_VIEW, - URL_PARAMS_VIEW, - REQUEST_METHOD_VIEW, - REQUEST_DATA_VIEW, - REQUEST_HEADERS_VIEW, - RESPONSE_HEADERS_VIEW, - RESPONSE_BODY_VIEW, - SEARCH_PROMPT_VIEW, - SEARCH_VIEW, - } { - if v, err := setView(g, name); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Title = VIEW_PROPERTIES[name].title - v.Frame = VIEW_PROPERTIES[name].frame - v.Editable = VIEW_PROPERTIES[name].editable - v.Wrap = VIEW_PROPERTIES[name].wrap - v.Editor = VIEW_PROPERTIES[name].editor + if v, err := setView(g, maxX, maxY, URL_PARAMS_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err } + setViewDefaults(v) + v.Editable = true + v.Title = VIEW_TITLES[URL_PARAMS_VIEW] + v.Editor = &defaultEditor } + if v, err := setView(g, maxX, maxY, REQUEST_METHOD_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewDefaults(v) + v.Editable = true + v.Title = VIEW_TITLES[REQUEST_METHOD_VIEW] + v.Editor = &singleLineEditor{&defaultEditor} - if v, err := g.View(URL_VIEW); err == nil { - setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") - } - if v, err := g.View(REQUEST_METHOD_VIEW); err == nil { setViewTextAndCursor(v, DEFAULT_METHOD) } - if v, err := g.View(SEARCH_PROMPT_VIEW); err == nil { - setViewTextAndCursor(v, SEARCH_PROMPT) + if v, err := setView(g, maxX, maxY, REQUEST_DATA_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewDefaults(v) + v.Editable = true + v.Title = VIEW_TITLES[REQUEST_DATA_VIEW] + v.Editor = &defaultEditor + } + if v, err := setView(g, maxX, maxY, REQUEST_HEADERS_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewDefaults(v) + v.Editable = true + v.Title = VIEW_TITLES[REQUEST_HEADERS_VIEW] + v.Editor = &defaultEditor + } + if v, err := setView(g, maxX, maxY, RESPONSE_HEADERS_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewDefaults(v) + v.Wrap = true + v.Title = VIEW_TITLES[RESPONSE_HEADERS_VIEW] + v.Editable = true + v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + return + })} + } + if v, err := setView(g, maxX, maxY, RESPONSE_BODY_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewDefaults(v) + v.Wrap = true + v.Title = VIEW_TITLES[RESPONSE_BODY_VIEW] + v.Editable = true + v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + return + })} + } + if v, err := setView(g, maxX, maxY, PROMPT); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Frame = false + setViewTextAndCursor(v, VIEW_TITLES[SEARCH_VIEW]) + } + if v, err := setView(g, maxX, maxY, SEARCH_VIEW); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Frame = false + v.Editable = true + v.Editor = &singleLineEditor{&SearchEditor{&defaultEditor}} } - return nil } @@ -458,12 +435,15 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { func popup(g *gocui.Gui, msg string) { var popup *gocui.View var err error + maxX, maxY := g.Size() + maxX++ + maxY++ p := VIEW_POSITIONS[POPUP_VIEW] p.x0.abs = -len(msg)/2 - 1 p.x1.abs = len(msg)/2 + 1 VIEW_POSITIONS[POPUP_VIEW] = p - if popup, err = setView(g, POPUP_VIEW); err != nil { + if popup, err = setView(g, maxX, maxY, POPUP_VIEW); err != nil { if err != gocui.ErrUnknownView { return } @@ -781,7 +761,7 @@ func (a *App) SetKeys(g *gocui.Gui) error { if err != nil { return err } - help.Title = VIEW_TITLES[HELP_VIEW] + help.Title = "Help" help.Highlight = false fmt.Fprint(help, "Keybindings:\n") a.printViewKeybindings(help, "global") @@ -1219,7 +1199,7 @@ func getViewValue(g *gocui.Gui, name string) string { func setViewDefaults(v *gocui.View) { v.Frame = true - v.Wrap = true + v.Wrap = false } func setViewTextAndCursor(v *gocui.View, s string) { From f131e1ad5ab78305d57e76601843efefd3a4339e Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Fri, 17 Feb 2017 00:45:16 +0100 Subject: [PATCH 03/15] Moved most view properties to the top, leaving Layout function much smaller 2nd commit due to master changes --- wuzz.go | 248 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 134 insertions(+), 114 deletions(-) diff --git a/wuzz.go b/wuzz.go index 19f0171..c64d947 100644 --- a/wuzz.go +++ b/wuzz.go @@ -32,6 +32,7 @@ const VERSION = "0.1.0" const TIMEOUT_DURATION = 5 // in seconds const WINDOWS_OS = "windows" +const SEARCH_PROMPT = "search> " const ( ALL_VIEWS = "" @@ -45,31 +46,23 @@ const ( RESPONSE_HEADERS_VIEW = "response-headers" RESPONSE_BODY_VIEW = "response-body" - PROMPT = "prompt" - POPUP_VIEW = "popup_view" - ERROR_VIEW = "error_view" - HISTORY_VIEW = "history" - SAVE_DIALOG_VIEW = "save-dialog" - SAVE_RESULT_VIEW = "save-result" - METHOD_LIST_VIEW = "method-list" - HELP_VIEW = "help" + SEARCH_PROMPT_VIEW = "prompt" + POPUP_VIEW = "popup_view" + ERROR_VIEW = "error_view" + HISTORY_VIEW = "history" + SAVE_DIALOG_VIEW = "save-dialog" + SAVE_RESULT_VIEW = "save-result" + METHOD_LIST_VIEW = "method-list" + HELP_VIEW = "help" ) var VIEW_TITLES = map[string]string{ - URL_VIEW: "URL - press F1 for help", - URL_PARAMS_VIEW: "URL params", - REQUEST_METHOD_VIEW: "Method", - REQUEST_DATA_VIEW: "Request data (POST/PUT/PATCH)", - REQUEST_HEADERS_VIEW: "Request headers", - SEARCH_VIEW: "search> ", - RESPONSE_HEADERS_VIEW: "Response headers", - RESPONSE_BODY_VIEW: "Response body", - POPUP_VIEW: "Info", ERROR_VIEW: "Error", HISTORY_VIEW: "History", SAVE_DIALOG_VIEW: "Save Response (enter to submit, ctrl+q to cancel)", METHOD_LIST_VIEW: "Methods", + HELP_VIEW: "Help", } type position struct { @@ -128,7 +121,7 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.0, 0}, position{1.0, -2}, position{1.0, -2}}, - PROMPT: { + SEARCH_PROMPT_VIEW: { position{0.0, -1}, position{1.0, -3}, position{0.0, 8}, @@ -140,6 +133,80 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.5, 1}}, } +type viewProperties struct { + title string + frame bool + editable bool + wrap bool + editor gocui.Editor +} + +var VIEW_PROPERTIES = map[string]viewProperties{ + URL_VIEW: { + title: "URL - press F1 for help", + frame: true, + editable: true, + wrap: false, + editor: &singleLineEditor{&defaultEditor}, + }, + URL_PARAMS_VIEW: { + title: "URL params", + frame: true, + editable: true, + wrap: false, + editor: &defaultEditor, + }, + REQUEST_METHOD_VIEW: { + title: "Method", + frame: true, + editable: true, + wrap: false, + editor: &singleLineEditor{&defaultEditor}, + }, + REQUEST_DATA_VIEW: { + title: "Request data (POST/PUT/PATCH)", + frame: true, + editable: true, + wrap: false, + editor: &defaultEditor, + }, + REQUEST_HEADERS_VIEW: { + title: "Request headers", + frame: true, + editable: true, + wrap: false, + editor: &defaultEditor, + }, + RESPONSE_HEADERS_VIEW: { + title: "Response headers", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, + RESPONSE_BODY_VIEW: { + title: "Response body", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, + SEARCH_VIEW: { + title: "", + frame: false, + editable: true, + wrap: false, + editor: &singleLineEditor{&SearchEditor{&defaultEditor}}, + }, + SEARCH_PROMPT_VIEW: { + title: "", + frame: false, + editable: false, + wrap: false, + editor: nil, + }, +} + var METHODS []string = []string{ http.MethodGet, http.MethodPost, @@ -279,26 +346,31 @@ func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui. // +func (a *App) getResponseViewEditor(g *gocui.Gui) gocui.Editor { + return &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + return + })} +} + func (p position) getCoordinate(max int) int { return int(p.pct*float32(max)) + p.abs } -func setView(g *gocui.Gui, maxX, maxY int, viewName string) (*gocui.View, error) { +func setView(g *gocui.Gui, viewName string) (*gocui.View, error) { + maxX, maxY := g.Size() position := VIEW_POSITIONS[viewName] return g.SetView(viewName, - position.x0.getCoordinate(maxX), - position.y0.getCoordinate(maxY), - position.x1.getCoordinate(maxX), - position.y1.getCoordinate(maxY)) + position.x0.getCoordinate(maxX+1), + position.y0.getCoordinate(maxY+1), + position.x1.getCoordinate(maxX+1), + position.y1.getCoordinate(maxY+1)) } func (a *App) Layout(g *gocui.Gui) error { maxX, maxY := g.Size() - maxX++ - maxY++ - if maxX-1 < MIN_WIDTH || maxY-1 < MIN_HEIGHT { - if v, err := setView(g, maxX, maxY, ERROR_VIEW); err != nil { + if maxX < MIN_WIDTH || maxY < MIN_HEIGHT { + if v, err := setView(g, ERROR_VIEW); err != nil { if err != gocui.ErrUnknownView { return err } @@ -315,94 +387,45 @@ func (a *App) Layout(g *gocui.Gui) error { a.setView(g) } - if v, err := setView(g, maxX, maxY, URL_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Title = VIEW_TITLES[URL_VIEW] - v.Editable = true - v.Overwrite = false - v.Editor = &singleLineEditor{&defaultEditor} - setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") + for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { + vp := VIEW_PROPERTIES[name] + vp.editor = a.getResponseViewEditor(g) + VIEW_PROPERTIES[name] = vp } - if v, err := setView(g, maxX, maxY, URL_PARAMS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[URL_PARAMS_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, REQUEST_METHOD_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_METHOD_VIEW] - v.Editor = &singleLineEditor{&defaultEditor} - setViewTextAndCursor(v, DEFAULT_METHOD) - } - if v, err := setView(g, maxX, maxY, REQUEST_DATA_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_DATA_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, REQUEST_HEADERS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_HEADERS_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, RESPONSE_HEADERS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err + for _, name := range []string{ + URL_VIEW, + URL_PARAMS_VIEW, + REQUEST_METHOD_VIEW, + REQUEST_DATA_VIEW, + REQUEST_HEADERS_VIEW, + RESPONSE_HEADERS_VIEW, + RESPONSE_BODY_VIEW, + SEARCH_PROMPT_VIEW, + SEARCH_VIEW, + } { + if v, err := setView(g, name); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = VIEW_PROPERTIES[name].title + v.Frame = VIEW_PROPERTIES[name].frame + v.Editable = VIEW_PROPERTIES[name].editable + v.Wrap = VIEW_PROPERTIES[name].wrap + v.Editor = VIEW_PROPERTIES[name].editor } - setViewDefaults(v) - v.Wrap = true - v.Title = VIEW_TITLES[RESPONSE_HEADERS_VIEW] - v.Editable = true - v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} } - if v, err := setView(g, maxX, maxY, RESPONSE_BODY_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Wrap = true - v.Title = VIEW_TITLES[RESPONSE_BODY_VIEW] - v.Editable = true - v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} + + if v, err := g.View(URL_VIEW); err == nil { + setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") } - if v, err := setView(g, maxX, maxY, PROMPT); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Frame = false - setViewTextAndCursor(v, VIEW_TITLES[SEARCH_VIEW]) + if v, err := g.View(REQUEST_METHOD_VIEW); err == nil { + setViewTextAndCursor(v, DEFAULT_METHOD) } - if v, err := setView(g, maxX, maxY, SEARCH_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Frame = false - v.Editable = true - v.Editor = &singleLineEditor{&SearchEditor{&defaultEditor}} + if v, err := g.View(SEARCH_PROMPT_VIEW); err == nil { + setViewTextAndCursor(v, SEARCH_PROMPT) } + return nil } @@ -435,15 +458,12 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { func popup(g *gocui.Gui, msg string) { var popup *gocui.View var err error - maxX, maxY := g.Size() - maxX++ - maxY++ p := VIEW_POSITIONS[POPUP_VIEW] p.x0.abs = -len(msg)/2 - 1 p.x1.abs = len(msg)/2 + 1 VIEW_POSITIONS[POPUP_VIEW] = p - if popup, err = setView(g, maxX, maxY, POPUP_VIEW); err != nil { + if popup, err = setView(g, POPUP_VIEW); err != nil { if err != gocui.ErrUnknownView { return } @@ -761,7 +781,7 @@ func (a *App) SetKeys(g *gocui.Gui) error { if err != nil { return err } - help.Title = "Help" + help.Title = VIEW_TITLES[HELP_VIEW] help.Highlight = false fmt.Fprint(help, "Keybindings:\n") a.printViewKeybindings(help, "global") @@ -1199,7 +1219,7 @@ func getViewValue(g *gocui.Gui, name string) string { func setViewDefaults(v *gocui.View) { v.Frame = true - v.Wrap = false + v.Wrap = true } func setViewTextAndCursor(v *gocui.View, s string) { From 221de5ac8963928537b24fa68e45900965575956 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Fri, 17 Feb 2017 01:00:01 +0100 Subject: [PATCH 04/15] run gofmt --- wuzz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wuzz.go b/wuzz.go index 86e41ad..9ae69c7 100644 --- a/wuzz.go +++ b/wuzz.go @@ -392,7 +392,7 @@ func (a *App) Layout(g *gocui.Gui) error { vp.editor = a.getResponseViewEditor(g) VIEW_PROPERTIES[name] = vp } - + for _, name := range []string{ URL_VIEW, URL_PARAMS_VIEW, From 352a9df4b606ee48baed97033af602d23779e65b Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Fri, 17 Feb 2017 00:22:58 +0100 Subject: [PATCH 05/15] Moved most view properties to the top, leaving Layout function much smaller --- wuzz.go | 247 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 133 insertions(+), 114 deletions(-) diff --git a/wuzz.go b/wuzz.go index 45a85fd..bb9abda 100644 --- a/wuzz.go +++ b/wuzz.go @@ -22,7 +22,6 @@ import ( "github.com/asciimoo/wuzz/config" "crypto/tls" - "github.com/jroimartin/gocui" "github.com/mattn/go-runewidth" "github.com/nwidger/jsoncolor" @@ -32,6 +31,7 @@ const VERSION = "0.2.0" const TIMEOUT_DURATION = 5 // in seconds const WINDOWS_OS = "windows" +const SEARCH_PROMPT = "search> " const ( ALL_VIEWS = "" @@ -45,31 +45,23 @@ const ( RESPONSE_HEADERS_VIEW = "response-headers" RESPONSE_BODY_VIEW = "response-body" - PROMPT = "prompt" - POPUP_VIEW = "popup_view" - ERROR_VIEW = "error_view" - HISTORY_VIEW = "history" - SAVE_DIALOG_VIEW = "save-dialog" - SAVE_RESULT_VIEW = "save-result" - METHOD_LIST_VIEW = "method-list" - HELP_VIEW = "help" + SEARCH_PROMPT_VIEW = "prompt" + POPUP_VIEW = "popup_view" + ERROR_VIEW = "error_view" + HISTORY_VIEW = "history" + SAVE_DIALOG_VIEW = "save-dialog" + SAVE_RESULT_VIEW = "save-result" + METHOD_LIST_VIEW = "method-list" + HELP_VIEW = "help" ) var VIEW_TITLES = map[string]string{ - URL_VIEW: "URL - press F1 for help", - URL_PARAMS_VIEW: "URL params", - REQUEST_METHOD_VIEW: "Method", - REQUEST_DATA_VIEW: "Request data (POST/PUT/PATCH)", - REQUEST_HEADERS_VIEW: "Request headers", - SEARCH_VIEW: "search> ", - RESPONSE_HEADERS_VIEW: "Response headers", - RESPONSE_BODY_VIEW: "Response body", - POPUP_VIEW: "Info", ERROR_VIEW: "Error", HISTORY_VIEW: "History", SAVE_DIALOG_VIEW: "Save Response (enter to submit, ctrl+q to cancel)", METHOD_LIST_VIEW: "Methods", + HELP_VIEW: "Help", } type position struct { @@ -128,7 +120,7 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.0, 0}, position{1.0, -2}, position{1.0, -2}}, - PROMPT: { + SEARCH_PROMPT_VIEW: { position{0.0, -1}, position{1.0, -3}, position{0.0, 8}, @@ -140,6 +132,80 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.5, 1}}, } +type viewProperties struct { + title string + frame bool + editable bool + wrap bool + editor gocui.Editor +} + +var VIEW_PROPERTIES = map[string]viewProperties{ + URL_VIEW: { + title: "URL - press F1 for help", + frame: true, + editable: true, + wrap: true, + editor: &singleLineEditor{&defaultEditor}, + }, + URL_PARAMS_VIEW: { + title: "URL params", + frame: true, + editable: true, + wrap: true, + editor: &defaultEditor, + }, + REQUEST_METHOD_VIEW: { + title: "Method", + frame: true, + editable: true, + wrap: true, + editor: &singleLineEditor{&defaultEditor}, + }, + REQUEST_DATA_VIEW: { + title: "Request data (POST/PUT)", + frame: true, + editable: true, + wrap: true, + editor: &defaultEditor, + }, + REQUEST_HEADERS_VIEW: { + title: "Request headers", + frame: true, + editable: true, + wrap: false, + editor: &defaultEditor, + }, + RESPONSE_HEADERS_VIEW: { + title: "Response headers", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, + RESPONSE_BODY_VIEW: { + title: "Response body", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, + SEARCH_VIEW: { + title: "", + frame: false, + editable: true, + wrap: true, + editor: &singleLineEditor{&SearchEditor{&defaultEditor}}, + }, + SEARCH_PROMPT_VIEW: { + title: "", + frame: false, + editable: false, + wrap: true, + editor: nil, + }, +} + var METHODS []string = []string{ http.MethodGet, http.MethodPost, @@ -279,26 +345,31 @@ func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui. // +func (a *App) getResponseViewEditor(g *gocui.Gui) gocui.Editor { + return &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + return + })} +} + func (p position) getCoordinate(max int) int { return int(p.pct*float32(max)) + p.abs } -func setView(g *gocui.Gui, maxX, maxY int, viewName string) (*gocui.View, error) { +func setView(g *gocui.Gui, viewName string) (*gocui.View, error) { + maxX, maxY := g.Size() position := VIEW_POSITIONS[viewName] return g.SetView(viewName, - position.x0.getCoordinate(maxX), - position.y0.getCoordinate(maxY), - position.x1.getCoordinate(maxX), - position.y1.getCoordinate(maxY)) + position.x0.getCoordinate(maxX+1), + position.y0.getCoordinate(maxY+1), + position.x1.getCoordinate(maxX+1), + position.y1.getCoordinate(maxY+1)) } func (a *App) Layout(g *gocui.Gui) error { maxX, maxY := g.Size() - maxX++ - maxY++ - if maxX-1 < MIN_WIDTH || maxY-1 < MIN_HEIGHT { - if v, err := setView(g, maxX, maxY, ERROR_VIEW); err != nil { + if maxX < MIN_WIDTH || maxY < MIN_HEIGHT { + if v, err := setView(g, ERROR_VIEW); err != nil { if err != gocui.ErrUnknownView { return err } @@ -315,94 +386,45 @@ func (a *App) Layout(g *gocui.Gui) error { a.setView(g) } - if v, err := setView(g, maxX, maxY, URL_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Title = VIEW_TITLES[URL_VIEW] - v.Editable = true - v.Overwrite = false - v.Editor = &singleLineEditor{&defaultEditor} - setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") + for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { + vp := VIEW_PROPERTIES[name] + vp.editor = a.getResponseViewEditor(g) + VIEW_PROPERTIES[name] = vp } - if v, err := setView(g, maxX, maxY, URL_PARAMS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[URL_PARAMS_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, REQUEST_METHOD_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_METHOD_VIEW] - v.Editor = &singleLineEditor{&defaultEditor} - setViewTextAndCursor(v, DEFAULT_METHOD) - } - if v, err := setView(g, maxX, maxY, REQUEST_DATA_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_DATA_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, REQUEST_HEADERS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Editable = true - v.Title = VIEW_TITLES[REQUEST_HEADERS_VIEW] - v.Editor = &defaultEditor - } - if v, err := setView(g, maxX, maxY, RESPONSE_HEADERS_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err + for _, name := range []string{ + URL_VIEW, + URL_PARAMS_VIEW, + REQUEST_METHOD_VIEW, + REQUEST_DATA_VIEW, + REQUEST_HEADERS_VIEW, + RESPONSE_HEADERS_VIEW, + RESPONSE_BODY_VIEW, + SEARCH_PROMPT_VIEW, + SEARCH_VIEW, + } { + if v, err := setView(g, name); err != nil { + if err != gocui.ErrUnknownView { + return err + } + v.Title = VIEW_PROPERTIES[name].title + v.Frame = VIEW_PROPERTIES[name].frame + v.Editable = VIEW_PROPERTIES[name].editable + v.Wrap = VIEW_PROPERTIES[name].wrap + v.Editor = VIEW_PROPERTIES[name].editor } - setViewDefaults(v) - v.Wrap = true - v.Title = VIEW_TITLES[RESPONSE_HEADERS_VIEW] - v.Editable = true - v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} } - if v, err := setView(g, maxX, maxY, RESPONSE_BODY_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - setViewDefaults(v) - v.Wrap = true - v.Title = VIEW_TITLES[RESPONSE_BODY_VIEW] - v.Editable = true - v.Editor = &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} + + if v, err := g.View(URL_VIEW); err == nil { + setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") } - if v, err := setView(g, maxX, maxY, PROMPT); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Frame = false - setViewTextAndCursor(v, VIEW_TITLES[SEARCH_VIEW]) + if v, err := g.View(REQUEST_METHOD_VIEW); err == nil { + setViewTextAndCursor(v, DEFAULT_METHOD) } - if v, err := setView(g, maxX, maxY, SEARCH_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Frame = false - v.Editable = true - v.Editor = &singleLineEditor{&SearchEditor{&defaultEditor}} + if v, err := g.View(SEARCH_PROMPT_VIEW); err == nil { + setViewTextAndCursor(v, SEARCH_PROMPT) } + return nil } @@ -435,15 +457,12 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { func popup(g *gocui.Gui, msg string) { var popup *gocui.View var err error - maxX, maxY := g.Size() - maxX++ - maxY++ p := VIEW_POSITIONS[POPUP_VIEW] p.x0.abs = -len(msg)/2 - 1 p.x1.abs = len(msg)/2 + 1 VIEW_POSITIONS[POPUP_VIEW] = p - if popup, err = setView(g, maxX, maxY, POPUP_VIEW); err != nil { + if popup, err = setView(g, POPUP_VIEW); err != nil { if err != gocui.ErrUnknownView { return } @@ -761,7 +780,7 @@ func (a *App) SetKeys(g *gocui.Gui) error { if err != nil { return err } - help.Title = "Help" + help.Title = VIEW_TITLES[HELP_VIEW] help.Highlight = false fmt.Fprint(help, "Keybindings:\n") a.printViewKeybindings(help, "global") From 112e85f4b4564d76d33c9512e77acae13cc5e898 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Fri, 17 Feb 2017 00:29:37 +0100 Subject: [PATCH 06/15] [fix] disable wrapping of editable views - related to #61 --- wuzz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wuzz.go b/wuzz.go index bb9abda..62b235d 100644 --- a/wuzz.go +++ b/wuzz.go @@ -163,7 +163,7 @@ var VIEW_PROPERTIES = map[string]viewProperties{ editor: &singleLineEditor{&defaultEditor}, }, REQUEST_DATA_VIEW: { - title: "Request data (POST/PUT)", + title: "Request data (POST/PUT/PATCH)", frame: true, editable: true, wrap: true, From fa40a8d345048f05af3139be1fc043230bfa66f8 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Fri, 17 Feb 2017 00:45:16 +0100 Subject: [PATCH 07/15] Moved most view properties to the top, leaving Layout function much smaller 2nd commit due to master changes --- wuzz.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wuzz.go b/wuzz.go index 62b235d..3dd9c69 100644 --- a/wuzz.go +++ b/wuzz.go @@ -145,28 +145,28 @@ var VIEW_PROPERTIES = map[string]viewProperties{ title: "URL - press F1 for help", frame: true, editable: true, - wrap: true, + wrap: false, editor: &singleLineEditor{&defaultEditor}, }, URL_PARAMS_VIEW: { title: "URL params", frame: true, editable: true, - wrap: true, + wrap: false, editor: &defaultEditor, }, REQUEST_METHOD_VIEW: { title: "Method", frame: true, editable: true, - wrap: true, + wrap: false, editor: &singleLineEditor{&defaultEditor}, }, REQUEST_DATA_VIEW: { title: "Request data (POST/PUT/PATCH)", frame: true, editable: true, - wrap: true, + wrap: false, editor: &defaultEditor, }, REQUEST_HEADERS_VIEW: { @@ -194,14 +194,14 @@ var VIEW_PROPERTIES = map[string]viewProperties{ title: "", frame: false, editable: true, - wrap: true, + wrap: false, editor: &singleLineEditor{&SearchEditor{&defaultEditor}}, }, SEARCH_PROMPT_VIEW: { title: "", frame: false, editable: false, - wrap: true, + wrap: false, editor: nil, }, } From 06dbce4750a17cd7297dc13f06492bed4677e60e Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Sun, 19 Feb 2017 19:52:19 +0100 Subject: [PATCH 08/15] Fixed error that unabled editing URL + minor refactor --- wuzz.go | 77 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/wuzz.go b/wuzz.go index 3dd9c69..ad6ff96 100644 --- a/wuzz.go +++ b/wuzz.go @@ -138,6 +138,7 @@ type viewProperties struct { editable bool wrap bool editor gocui.Editor + text string } var VIEW_PROPERTIES = map[string]viewProperties{ @@ -161,6 +162,7 @@ var VIEW_PROPERTIES = map[string]viewProperties{ editable: true, wrap: false, editor: &singleLineEditor{&defaultEditor}, + text: DEFAULT_METHOD, }, REQUEST_DATA_VIEW: { title: "Request data (POST/PUT/PATCH)", @@ -203,10 +205,18 @@ var VIEW_PROPERTIES = map[string]viewProperties{ editable: false, wrap: false, editor: nil, + text: SEARCH_PROMPT, + }, + POPUP_VIEW: { + title: "Info", + frame: true, + editable: false, + wrap: false, + editor: nil, }, } -var METHODS []string = []string{ +var METHODS = []string{ http.MethodGet, http.MethodPost, http.MethodPut, @@ -220,14 +230,14 @@ var METHODS []string = []string{ const DEFAULT_METHOD = http.MethodGet -var CLIENT *http.Client = &http.Client{ +var CLIENT = &http.Client{ Timeout: time.Duration(TIMEOUT_DURATION * time.Second), } -var TRANSPORT *http.Transport = &http.Transport{ +var TRANSPORT = &http.Transport{ Proxy: http.ProxyFromEnvironment, } -var VIEWS []string = []string{ +var VIEWS = []string{ URL_VIEW, URL_PARAMS_VIEW, REQUEST_METHOD_VIEW, @@ -275,7 +285,7 @@ type SearchEditor struct { wuzzEditor *ViewEditor } -// The singleLineEditor removes multilines capabilities +// The singleLineEditor removes multi lines capabilities type singleLineEditor struct { wuzzEditor gocui.Editor } @@ -314,7 +324,7 @@ func (e *SearchEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Mod }) } -// The singleLineEditor removes multilines capabilities +// The singleLineEditor removes multi lines capabilities func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { switch { case (ch != 0 || key == gocui.KeySpace) && mod == 0: @@ -365,6 +375,15 @@ func setView(g *gocui.Gui, viewName string) (*gocui.View, error) { position.y1.getCoordinate(maxY+1)) } +func setViewProperties(v *gocui.View, name string) { + v.Title = VIEW_PROPERTIES[name].title + v.Frame = VIEW_PROPERTIES[name].frame + v.Editable = VIEW_PROPERTIES[name].editable + v.Wrap = VIEW_PROPERTIES[name].wrap + v.Editor = VIEW_PROPERTIES[name].editor + setViewTextAndCursor(v, VIEW_PROPERTIES[name].text) +} + func (a *App) Layout(g *gocui.Gui) error { maxX, maxY := g.Size() @@ -392,6 +411,10 @@ func (a *App) Layout(g *gocui.Gui) error { VIEW_PROPERTIES[name] = vp } + p := VIEW_PROPERTIES[URL_VIEW] + p.text = a.config.General.DefaultURLScheme + "://" + VIEW_PROPERTIES[URL_VIEW] = p + for _, name := range []string{ URL_VIEW, URL_PARAMS_VIEW, @@ -407,24 +430,10 @@ func (a *App) Layout(g *gocui.Gui) error { if err != gocui.ErrUnknownView { return err } - v.Title = VIEW_PROPERTIES[name].title - v.Frame = VIEW_PROPERTIES[name].frame - v.Editable = VIEW_PROPERTIES[name].editable - v.Wrap = VIEW_PROPERTIES[name].wrap - v.Editor = VIEW_PROPERTIES[name].editor + setViewProperties(v, name) } } - if v, err := g.View(URL_VIEW); err == nil { - setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://") - } - if v, err := g.View(REQUEST_METHOD_VIEW); err == nil { - setViewTextAndCursor(v, DEFAULT_METHOD) - } - if v, err := g.View(SEARCH_PROMPT_VIEW); err == nil { - setViewTextAndCursor(v, SEARCH_PROMPT) - } - return nil } @@ -455,20 +464,20 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { } func popup(g *gocui.Gui, msg string) { - var popup *gocui.View - var err error - - p := VIEW_POSITIONS[POPUP_VIEW] - p.x0.abs = -len(msg)/2 - 1 - p.x1.abs = len(msg)/2 + 1 - VIEW_POSITIONS[POPUP_VIEW] = p - if popup, err = setView(g, POPUP_VIEW); err != nil { + pos := VIEW_POSITIONS[POPUP_VIEW] + pos.x0.abs = -len(msg)/2 - 1 + pos.x1.abs = len(msg)/2 + 1 + VIEW_POSITIONS[POPUP_VIEW] = pos + + p := VIEW_PROPERTIES[POPUP_VIEW] + p.text = msg + VIEW_PROPERTIES[POPUP_VIEW] = p + + if v, err := setView(g, POPUP_VIEW); err != nil { if err != gocui.ErrUnknownView { return } - setViewDefaults(popup) - popup.Title = VIEW_TITLES[POPUP_VIEW] - setViewTextAndCursor(popup, msg) + setViewProperties(v, POPUP_VIEW) g.SetViewOnTop(POPUP_VIEW) } } @@ -603,7 +612,7 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { // print status code and sorted headers hkeys := make([]string, 0, len(response.Header)) - for hname, _ := range response.Header { + for hname := range response.Header { hkeys = append(hkeys, hname) } sort.Strings(hkeys) @@ -746,7 +755,7 @@ func (a *App) printViewKeybindings(v io.Writer, viewName string) { } mk := make([]string, len(keys)) i := 0 - for k, _ := range keys { + for k := range keys { mk[i] = k i++ } From 738df991cd6c764f07cbdfc9ceb4df2e157cfe39 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Mon, 20 Feb 2017 02:55:14 +0100 Subject: [PATCH 09/15] Refactored SubmitRequest() function + fixed a bug, where response headers would display twice + other minor refactorings --- wuzz.go | 466 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 254 insertions(+), 212 deletions(-) diff --git a/wuzz.go b/wuzz.go index ad6ff96..22d4a7a 100644 --- a/wuzz.go +++ b/wuzz.go @@ -55,15 +55,6 @@ const ( HELP_VIEW = "help" ) -var VIEW_TITLES = map[string]string{ - POPUP_VIEW: "Info", - ERROR_VIEW: "Error", - HISTORY_VIEW: "History", - SAVE_DIALOG_VIEW: "Save Response (enter to submit, ctrl+q to cancel)", - METHOD_LIST_VIEW: "Methods", - HELP_VIEW: "Help", -} - type position struct { // value = prc * MAX + abs pct float32 @@ -207,6 +198,13 @@ var VIEW_PROPERTIES = map[string]viewProperties{ editor: nil, text: SEARCH_PROMPT, }, + ERROR_VIEW: { + title: "Error", + frame: true, + editable: false, + wrap: false, + editor: nil, + }, POPUP_VIEW: { title: "Info", frame: true, @@ -214,6 +212,18 @@ var VIEW_PROPERTIES = map[string]viewProperties{ wrap: false, editor: nil, }, + HISTORY_VIEW: { + title: "History", + }, + SAVE_DIALOG_VIEW: { + title: "Save Response (enter to submit, ctrl+q to cancel)", + }, + METHOD_LIST_VIEW: { + title: "Methods", + }, + HELP_VIEW: { + title: "Help", + }, } var METHODS = []string{ @@ -385,52 +395,50 @@ func setViewProperties(v *gocui.View, name string) { } func (a *App) Layout(g *gocui.Gui) error { - maxX, maxY := g.Size() - - if maxX < MIN_WIDTH || maxY < MIN_HEIGHT { + if maxX, maxY := g.Size(); maxX < MIN_WIDTH || maxY < MIN_HEIGHT { if v, err := setView(g, ERROR_VIEW); err != nil { if err != gocui.ErrUnknownView { return err } - setViewDefaults(v) - v.Title = VIEW_TITLES[ERROR_VIEW] + setViewProperties(v, ERROR_VIEW) + g.Cursor = false fmt.Fprintln(v, "Terminal is too small") } - return nil - } - if _, err := g.View(ERROR_VIEW); err == nil { - g.DeleteView(ERROR_VIEW) - g.Cursor = true - a.setView(g) - } + } else { + if _, err := g.View(ERROR_VIEW); err == nil { + g.DeleteView(ERROR_VIEW) + g.Cursor = true + a.setView(g) + } - for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { - vp := VIEW_PROPERTIES[name] - vp.editor = a.getResponseViewEditor(g) - VIEW_PROPERTIES[name] = vp - } + for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { + vp := VIEW_PROPERTIES[name] + vp.editor = a.getResponseViewEditor(g) + VIEW_PROPERTIES[name] = vp + } - p := VIEW_PROPERTIES[URL_VIEW] - p.text = a.config.General.DefaultURLScheme + "://" - VIEW_PROPERTIES[URL_VIEW] = p - - for _, name := range []string{ - URL_VIEW, - URL_PARAMS_VIEW, - REQUEST_METHOD_VIEW, - REQUEST_DATA_VIEW, - REQUEST_HEADERS_VIEW, - RESPONSE_HEADERS_VIEW, - RESPONSE_BODY_VIEW, - SEARCH_PROMPT_VIEW, - SEARCH_VIEW, - } { - if v, err := setView(g, name); err != nil { - if err != gocui.ErrUnknownView { - return err + p := VIEW_PROPERTIES[URL_VIEW] + p.text = a.config.General.DefaultURLScheme + "://" + VIEW_PROPERTIES[URL_VIEW] = p + + for _, name := range []string{ + URL_VIEW, + URL_PARAMS_VIEW, + REQUEST_METHOD_VIEW, + REQUEST_DATA_VIEW, + REQUEST_HEADERS_VIEW, + RESPONSE_HEADERS_VIEW, + RESPONSE_BODY_VIEW, + SEARCH_PROMPT_VIEW, + SEARCH_VIEW, + } { + if v, err := setView(g, name); err != nil { + if err != gocui.ErrUnknownView { + return err + } + setViewProperties(v, name) } - setViewProperties(v, name) } } @@ -463,7 +471,96 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { return fmt.Errorf("View not found") } -func popup(g *gocui.Gui, msg string) { +func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { + rhv, _ := g.View(RESPONSE_HEADERS_VIEW) + rhv.Clear() + rbv, _ := g.View(RESPONSE_BODY_VIEW) + rbv.Clear() + + go a.CreateAndProcessRequest(g) + + return nil +} + +func (a *App) CreateAndProcessRequest(g *gocui.Gui) { + defer func() { + if r := recover(); r != nil { + displayErrorInResponseBody(g, r) + } + }() + + displayInfo(g, "Sending request..") + defer g.DeleteView(POPUP_VIEW) + + var r *Request = &Request{} + + // get url + r.Url = getViewValue(g, URL_VIEW) + u := getUrlFromString(r.Url) + + // get request method + r.Method = getViewValue(g, REQUEST_METHOD_VIEW) + + // get params + p := getViewValue(g, URL_PARAMS_VIEW) + u.RawQuery = getQuery(p, u) + r.GetParams = u.RawQuery + + // set headers + r.Headers = getViewValue(g, REQUEST_HEADERS_VIEW) + headers := getHttpHeaders(r.Headers) + + // set body + body := getRequestBody(r.Method, g, headers) + + // create request + request := getHttpRequest(r.Method, u, headers, body) + + // do request + response := getHttpResponse(request) + defer response.Body.Close() + + // set content type + r.ContentType = response.Header.Get("Content-Type") + + // extract body + if response.Header.Get("Content-Encoding") == "gzip" { + reader, err := gzip.NewReader(response.Body) + if err != nil { + panic(fmt.Sprintf("Cannot uncompress response: %v", err)) + } + defer reader.Close() + response.Body = reader + } + + if bodyBytes, err := ioutil.ReadAll(response.Body); err == nil { + r.RawResponseBody = bodyBytes + } + + responseHeaders := getSortedResponseHeaders(response) + r.ResponseHeaders = responseHeaders + + // add to history + a.history = append(a.history, r) + a.historyIndex = len(a.history) - 1 + + // render response + g.Execute(func(g *gocui.Gui) error { + printResponseHeaders(g, responseHeaders) + a.PrintBody(g) + return nil + }) +} + +func displayErrorInResponseBody(g *gocui.Gui, a interface{}) { + g.Execute(func(g *gocui.Gui) error { + v, _ := g.View(RESPONSE_BODY_VIEW) + fmt.Fprint(v, a) + return nil + }) +} + +func displayInfo(g *gocui.Gui, msg string) { pos := VIEW_POSITIONS[POPUP_VIEW] pos.x0.abs = -len(msg)/2 - 1 pos.x1.abs = len(msg)/2 + 1 @@ -482,164 +579,108 @@ func popup(g *gocui.Gui, msg string) { } } -func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - vrb.Clear() - vrh, _ := g.View(RESPONSE_HEADERS_VIEW) - vrh.Clear() - popup(g, "Sending request..") - - var r *Request = &Request{} - - go func(g *gocui.Gui, a *App, r *Request) error { - defer g.DeleteView(POPUP_VIEW) - // parse url - r.Url = getViewValue(g, URL_VIEW) - u, err := url.Parse(r.Url) - if err != nil { - g.Execute(func(g *gocui.Gui) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprintf(vrb, "URL parse error: %v", err) - return nil - }) - return nil - } - - q, err := url.ParseQuery(strings.Replace(getViewValue(g, URL_PARAMS_VIEW), "\n", "&", -1)) - if err != nil { - g.Execute(func(g *gocui.Gui) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprintf(vrb, "Invalid GET parameters: %v", err) - return nil - }) - return nil - } - originalQuery := u.Query() - for k, v := range q { - originalQuery.Add(k, strings.Join(v, "")) - } - u.RawQuery = originalQuery.Encode() - r.GetParams = u.RawQuery - - // parse method - r.Method = getViewValue(g, REQUEST_METHOD_VIEW) - - // set headers - headers := http.Header{} - headers.Set("User-Agent", "") - r.Headers = getViewValue(g, REQUEST_HEADERS_VIEW) - for _, header := range strings.Split(r.Headers, "\n") { - if header != "" { - header_parts := strings.SplitN(header, ": ", 2) - if len(header_parts) != 2 { - g.Execute(func(g *gocui.Gui) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprintf(vrb, "Invalid header: %v", header) - return nil - }) - return nil - } - headers.Set(header_parts[0], header_parts[1]) - } - } - - var body io.Reader - - // parse POST/PUT/PATCH data - if r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch { - bodyStr := getViewValue(g, REQUEST_DATA_VIEW) - if headers.Get("Content-Type") == "application/x-www-form-urlencoded" { - bodyStr = strings.Replace(bodyStr, "\n", "&", -1) - } - body = bytes.NewBufferString(bodyStr) - } - - // create request - req, err := http.NewRequest(r.Method, u.String(), body) - if err != nil { - g.Execute(func(g *gocui.Gui) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprintf(vrb, "Request error: %v", err) - return nil - }) - return nil - } - req.Header = headers +func getUrlFromString(urlString string) *url.URL { + u, err := url.Parse(urlString) + if err != nil { + panic(fmt.Sprintf("URL parse error: %v", err)) + } + return u +} - // do request - response, err := CLIENT.Do(req) - if err != nil { - g.Execute(func(g *gocui.Gui) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprintf(vrb, "Response error: %v", err) - return nil - }) - return nil - } - defer response.Body.Close() +func getQuery(paramsString string, u *url.URL) string { + query, err := url.ParseQuery(strings.Replace(paramsString, "\n", "&", -1)) + if err != nil { + panic(fmt.Sprintf("Invalid GET parameters: %v", err)) + } + originalQuery := u.Query() + for k, v := range query { + originalQuery.Add(k, strings.Join(v, "")) + } + return originalQuery.Encode() +} - // extract body - r.ContentType = response.Header.Get("Content-Type") - if response.Header.Get("Content-Encoding") == "gzip" { - reader, err := gzip.NewReader(response.Body) - if err == nil { - defer reader.Close() - response.Body = reader - } else { - g.Execute(func(g *gocui.Gui) error { - vrb, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprintf(vrb, "Cannot uncompress response: %v", err) - return nil - }) - return nil +func getHttpHeaders(h string) http.Header { + headers := http.Header{} + headers.Set("User-Agent", "") + for _, header := range strings.Split(h, "\n") { + if header != "" { + header_parts := strings.SplitN(header, ": ", 2) + if len(header_parts) != 2 { + panic(fmt.Sprintf("Invalid header: %v", header)) } + headers.Set(header_parts[0], header_parts[1]) } + } + return headers +} - bodyBytes, err := ioutil.ReadAll(response.Body) - if err == nil { - r.RawResponseBody = bodyBytes +func getRequestBody(method string, g *gocui.Gui, headers http.Header) io.Reader { + var body io.Reader + if method == http.MethodPost || method == http.MethodPut || method == http.MethodPatch { + bodyStr := getViewValue(g, REQUEST_DATA_VIEW) + if headers.Get("Content-Type") == "application/x-www-form-urlencoded" { + bodyStr = strings.Replace(bodyStr, "\n", "&", -1) } + body = bytes.NewBufferString(bodyStr) + } + return body +} - // add to history - a.history = append(a.history, r) - a.historyIndex = len(a.history) - 1 +func getHttpRequest(method string, url *url.URL, headers http.Header, body io.Reader) *http.Request { + request, err := http.NewRequest(method, url.String(), body) + if err != nil { + panic(fmt.Sprintf("Request error: %v", err)) + } + request.Header = headers + return request +} - // render response - g.Execute(func(g *gocui.Gui) error { - vrh, _ := g.View(RESPONSE_HEADERS_VIEW) +func getHttpResponse(request *http.Request) *http.Response { + response, err := CLIENT.Do(request) + if err != nil { + panic(fmt.Sprintf("Response error: %v", err)) + } + return response +} - a.PrintBody(g) +func getSortedResponseHeaders(response *http.Response) string { + keys := make([]string, 0, len(response.Header)) + for name := range response.Header { + keys = append(keys, name) + } + sort.Strings(keys) + status_color := statusCodeToColor(response.StatusCode) + headerString := fmt.Sprintf( + "\x1b[0;%dmHTTP/1.1 %v %v\x1b[0;0m\n", + status_color, + response.StatusCode, + http.StatusText(response.StatusCode), + ) + for _, name := range keys { + headerString += fmt.Sprintf( + "\x1b[0;33m%v:\x1b[0;0m %v\n", + name, + strings.Join(response.Header[name], ","), + ) + } + return headerString +} - // print status code and sorted headers - hkeys := make([]string, 0, len(response.Header)) - for hname := range response.Header { - hkeys = append(hkeys, hname) - } - sort.Strings(hkeys) - status_color := 32 - if response.StatusCode != 200 { - status_color = 31 - } - header_str := fmt.Sprintf( - "\x1b[0;%dmHTTP/1.1 %v %v\x1b[0;0m\n", - status_color, - response.StatusCode, - http.StatusText(response.StatusCode), - ) - for _, hname := range hkeys { - header_str += fmt.Sprintf("\x1b[0;33m%v:\x1b[0;0m %v\n", hname, strings.Join(response.Header[hname], ",")) - } - fmt.Fprint(vrh, header_str) - if _, err := vrh.Line(0); err != nil { - vrh.SetOrigin(0, 0) - } - r.ResponseHeaders = header_str - return nil - }) - return nil - }(g, a, r) +func statusCodeToColor(code int) int { + if code == 200 { + return 32 + } else { + return 31 + } +} - return nil +func printResponseHeaders(g *gocui.Gui, headersString string) { + v, _ := g.View(RESPONSE_HEADERS_VIEW) + v.Clear() + fmt.Fprint(v, headersString) + if _, err := v.Line(0); err != nil { + v.SetOrigin(0, 0) + } } func (a *App) PrintBody(g *gocui.Gui) { @@ -668,7 +709,7 @@ func (a *App) PrintBody(g *gocui.Gui) { is_binary := strings.Index(req.ContentType, "text") == -1 && strings.Index(req.ContentType, "application") == -1 search_text := getViewValue(g, SEARCH_VIEW) if search_text == "" || is_binary { - vrb.Title = RESPONSE_BODY_VIEW + vrb.Title = VIEW_PROPERTIES[RESPONSE_BODY_VIEW].title if is_binary { vrb.Title += " [binary content]" fmt.Fprint(vrb, hex.Dump(req.RawResponseBody)) @@ -789,7 +830,7 @@ func (a *App) SetKeys(g *gocui.Gui) error { if err != nil { return err } - help.Title = VIEW_TITLES[HELP_VIEW] + help.Title = VIEW_PROPERTIES[HELP_VIEW].title help.Highlight = false fmt.Fprint(help, "Keybindings:\n") a.printViewKeybindings(help, "global") @@ -941,16 +982,22 @@ func (a *App) CreatePopupView(name string, width, height int, g *gocui.Gui) (v * if width > maxX-4 { width = maxX - 4 } - v, err = g.SetView(name, maxX/2-width/2-1, maxY/2-height/2-1, maxX/2+width/2, maxY/2+height/2+1) - if err != nil && err != gocui.ErrUnknownView { - return + + VIEW_POSITIONS[name] = viewPosition{ + position{0.5, -width/2 - 1}, + position{0.5, -height/2 - 1}, + position{0.5, width / 2}, + position{0.5, height/2 + 1}, + } + v, err = setView(g, name) + if err == nil || err == gocui.ErrUnknownView { + err = nil + v.Wrap = false + v.Frame = true + v.Highlight = true + v.SelFgColor = gocui.ColorYellow + a.currentPopup = name } - err = nil - v.Wrap = false - v.Frame = true - v.Highlight = true - v.SelFgColor = gocui.ColorYellow - a.currentPopup = name return } @@ -966,7 +1013,7 @@ func (a *App) ToggleHistory(g *gocui.Gui, _ *gocui.View) (err error) { return } - history.Title = VIEW_TITLES[HISTORY_VIEW] + history.Title = VIEW_PROPERTIES[HISTORY_VIEW].title if len(a.history) == 0 { setViewTextAndCursor(history, "[!] No items in history") @@ -1002,7 +1049,7 @@ func (a *App) ToggleMethodList(g *gocui.Gui, _ *gocui.View) (err error) { if err != nil { return } - method.Title = VIEW_TITLES[METHOD_LIST_VIEW] + method.Title = VIEW_PROPERTIES[METHOD_LIST_VIEW].title cur := getViewValue(g, REQUEST_METHOD_VIEW) @@ -1025,7 +1072,7 @@ func (a *App) OpenSaveDialog(g *gocui.Gui, _ *gocui.View) (err error) { g.Cursor = true - dialog.Title = VIEW_TITLES[SAVE_DIALOG_VIEW] + dialog.Title = VIEW_PROPERTIES[SAVE_DIALOG_VIEW].title dialog.Editable = true dialog.Wrap = false @@ -1225,11 +1272,6 @@ func getViewValue(g *gocui.Gui, name string) string { return strings.TrimSpace(v.Buffer()) } -func setViewDefaults(v *gocui.View) { - v.Frame = true - v.Wrap = false -} - func setViewTextAndCursor(v *gocui.View, s string) { v.Clear() fmt.Fprint(v, s) From 12a7b517bfbf20060e84204920d86ddd622a2947 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Mon, 20 Feb 2017 06:26:14 +0100 Subject: [PATCH 10/15] Refactored PrintBody() function --- wuzz.go | 133 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 83 insertions(+), 50 deletions(-) diff --git a/wuzz.go b/wuzz.go index 22d4a7a..460c269 100644 --- a/wuzz.go +++ b/wuzz.go @@ -547,7 +547,7 @@ func (a *App) CreateAndProcessRequest(g *gocui.Gui) { // render response g.Execute(func(g *gocui.Gui) error { printResponseHeaders(g, responseHeaders) - a.PrintBody(g) + a.getAndPrintResponseBody(g) return nil }) } @@ -685,60 +685,93 @@ func printResponseHeaders(g *gocui.Gui, headersString string) { func (a *App) PrintBody(g *gocui.Gui) { g.Execute(func(g *gocui.Gui) error { - if len(a.history) == 0 { - return nil - } - req := a.history[a.historyIndex] - if req.RawResponseBody == nil { - return nil - } - vrb, _ := g.View(RESPONSE_BODY_VIEW) - vrb.Clear() - - responseBody := req.RawResponseBody - // pretty-print json - if strings.Contains(req.ContentType, "application/json") && a.config.General.FormatJSON { - formatter := jsoncolor.NewFormatter() - buf := bytes.NewBuffer(make([]byte, 0, len(req.RawResponseBody))) - err := formatter.Format(buf, req.RawResponseBody) - if err == nil { - responseBody = buf.Bytes() - } - } + a.getAndPrintResponseBody(g) + return nil + }) +} - is_binary := strings.Index(req.ContentType, "text") == -1 && strings.Index(req.ContentType, "application") == -1 - search_text := getViewValue(g, SEARCH_VIEW) - if search_text == "" || is_binary { - vrb.Title = VIEW_PROPERTIES[RESPONSE_BODY_VIEW].title - if is_binary { - vrb.Title += " [binary content]" - fmt.Fprint(vrb, hex.Dump(req.RawResponseBody)) - } else { - vrb.Write(responseBody) - } - if _, err := vrb.Line(0); !a.config.General.PreserveScrollPosition || err != nil { - vrb.SetOrigin(0, 0) +func (a *App) getAndPrintResponseBody(g *gocui.Gui) { + if len(a.history) == 0 { + return + } + req := a.history[a.historyIndex] + if req.RawResponseBody == nil { + return + } + + title, body, shouldSetOrigin := getResponseBody( + getViewValue(g, SEARCH_VIEW), + req.RawResponseBody, + req.ContentType, + a.config.General, + ) + printResponseBody(g, title, body, shouldSetOrigin) +} + +func getResponseBody( + searchString string, + responseBody []byte, + contentType string, + generalAppOptions config.GeneralOptions) (title, body string, shouldSetOrigin bool) { + + isText := strings.Contains(contentType, "text") + isApplication := strings.Contains(contentType, "application") + isJson := strings.Contains(contentType, "application/json") + isBinary := !(isText || isApplication) + + title = VIEW_PROPERTIES[RESPONSE_BODY_VIEW].title + if searchString == "" || isBinary { + if isBinary { + title += " [binary content]" + body = hex.Dump(responseBody) + } else { + if isJson && generalAppOptions.FormatJSON { + responseBody = formatJson(responseBody) } - return nil - } - vrb.SetOrigin(0, 0) - search_re, err := regexp.Compile(search_text) - if err != nil { - fmt.Fprint(vrb, "Error: invalid search regexp") - return nil + body = string(responseBody) } - results := search_re.FindAll(req.RawResponseBody, 1000) - if len(results) == 0 { - vrb.Title = "No results" - fmt.Fprint(vrb, "Error: no results") - return nil + if !generalAppOptions.PreserveScrollPosition { + shouldSetOrigin = true } - vrb.Title = fmt.Sprintf("%d results", len(results)) - for _, result := range results { - fmt.Fprintf(vrb, "-----\n%s\n", result) + } else { + shouldSetOrigin = true + if search_re, err := regexp.Compile(searchString); err != nil { + body = "Error: invalid search regexp" + } else { + results := search_re.FindAll(responseBody, 1000) + if len(results) == 0 { + title = "No results" + body = "Error: no results" + } else { + title = fmt.Sprintf("%d results", len(results)) + for _, result := range results { + body += fmt.Sprintf("-----\n%s\n", result) + } + } } - return nil - }) + } + + return title, body, shouldSetOrigin +} + +func printResponseBody(g *gocui.Gui, title string, body string, shouldSetOrigin bool) { + v, _ := g.View(RESPONSE_BODY_VIEW) + v.Clear() + v.Title = title + fmt.Fprint(v, body) + + if _, err := v.Line(0); shouldSetOrigin || err != nil { + v.SetOrigin(0, 0) + } +} + +func formatJson(text []byte) []byte { + formatter := jsoncolor.NewFormatter() + buf := bytes.NewBuffer(make([]byte, 0, len(text))) + if err := formatter.Format(buf, text); err == nil { + return buf.Bytes() + } + return text } func parseKey(k string) (interface{}, gocui.Modifier, error) { From f1d7f04165c5c25119af7fca51a521da547e2ff9 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Mon, 20 Feb 2017 21:56:04 +0100 Subject: [PATCH 11/15] Added response info view that displays errors or response time + further minor refactorings --- wuzz.go | 162 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 90 insertions(+), 72 deletions(-) diff --git a/wuzz.go b/wuzz.go index 460c269..090b25a 100644 --- a/wuzz.go +++ b/wuzz.go @@ -42,6 +42,7 @@ const ( REQUEST_DATA_VIEW = "data" REQUEST_HEADERS_VIEW = "headers" SEARCH_VIEW = "search" + RESPONSE_INFO_VIEW = "response-info" RESPONSE_HEADERS_VIEW = "response-headers" RESPONSE_BODY_VIEW = "response-body" @@ -91,10 +92,15 @@ var VIEW_POSITIONS = map[string]viewPosition{ position{0.5, 1}, position{0.3, 0}, position{1.0, -3}}, - RESPONSE_HEADERS_VIEW: { + RESPONSE_INFO_VIEW: { position{0.3, 0}, position{0.0, 3}, position{1.0, -2}, + position{0.0, 6}}, + RESPONSE_HEADERS_VIEW: { + position{0.3, 0}, + position{0.0, 6}, + position{1.0, -2}, position{0.25, 2}}, RESPONSE_BODY_VIEW: { position{0.3, 0}, @@ -169,6 +175,13 @@ var VIEW_PROPERTIES = map[string]viewProperties{ wrap: false, editor: &defaultEditor, }, + RESPONSE_INFO_VIEW: { + title: "Response info", + frame: true, + editable: true, + wrap: true, + editor: nil, // should be set using a.getViewEditor(g) + }, RESPONSE_HEADERS_VIEW: { title: "Response headers", frame: true, @@ -254,6 +267,7 @@ var VIEWS = []string{ REQUEST_DATA_VIEW, REQUEST_HEADERS_VIEW, SEARCH_VIEW, + RESPONSE_INFO_VIEW, RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW, } @@ -328,10 +342,7 @@ func (e *ViewEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modif 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 { - e.wuzzEditor.app.PrintBody(g) - return nil - }) + e.wuzzEditor.app.getAndPrintResponseBody(e.wuzzEditor.g) } // The singleLineEditor removes multi lines capabilities @@ -412,7 +423,7 @@ func (a *App) Layout(g *gocui.Gui) error { a.setView(g) } - for _, name := range []string{RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { + for _, name := range []string{RESPONSE_INFO_VIEW, RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { vp := VIEW_PROPERTIES[name] vp.editor = a.getResponseViewEditor(g) VIEW_PROPERTIES[name] = vp @@ -428,6 +439,7 @@ func (a *App) Layout(g *gocui.Gui) error { REQUEST_METHOD_VIEW, REQUEST_DATA_VIEW, REQUEST_HEADERS_VIEW, + RESPONSE_INFO_VIEW, RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW, SEARCH_PROMPT_VIEW, @@ -485,7 +497,7 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { func (a *App) CreateAndProcessRequest(g *gocui.Gui) { defer func() { if r := recover(); r != nil { - displayErrorInResponseBody(g, r) + printResponseInfo(g, r) } }() @@ -517,7 +529,7 @@ func (a *App) CreateAndProcessRequest(g *gocui.Gui) { request := getHttpRequest(r.Method, u, headers, body) // do request - response := getHttpResponse(request) + response, duration := getHttpResponse(request) defer response.Body.Close() // set content type @@ -545,19 +557,9 @@ func (a *App) CreateAndProcessRequest(g *gocui.Gui) { a.historyIndex = len(a.history) - 1 // render response - g.Execute(func(g *gocui.Gui) error { - printResponseHeaders(g, responseHeaders) - a.getAndPrintResponseBody(g) - return nil - }) -} - -func displayErrorInResponseBody(g *gocui.Gui, a interface{}) { - g.Execute(func(g *gocui.Gui) error { - v, _ := g.View(RESPONSE_BODY_VIEW) - fmt.Fprint(v, a) - return nil - }) + printResponseInfo(g, fmt.Sprintf("Response time: %v", duration)) + printResponseHeaders(g, responseHeaders) + a.getAndPrintResponseBody(g) } func displayInfo(g *gocui.Gui, msg string) { @@ -635,12 +637,14 @@ func getHttpRequest(method string, url *url.URL, headers http.Header, body io.Re return request } -func getHttpResponse(request *http.Request) *http.Response { +func getHttpResponse(request *http.Request) (*http.Response, time.Duration) { + start := time.Now() response, err := CLIENT.Do(request) + duration := time.Since(start) if err != nil { panic(fmt.Sprintf("Response error: %v", err)) } - return response + return response, duration } func getSortedResponseHeaders(response *http.Response) string { @@ -674,38 +678,45 @@ func statusCodeToColor(code int) int { } } -func printResponseHeaders(g *gocui.Gui, headersString string) { - v, _ := g.View(RESPONSE_HEADERS_VIEW) - v.Clear() - fmt.Fprint(v, headersString) - if _, err := v.Line(0); err != nil { +func printResponseInfo(g *gocui.Gui, info interface{}) { + g.Execute(func(g *gocui.Gui) error { + v, _ := g.View(RESPONSE_INFO_VIEW) + v.Clear() + fmt.Fprint(v, info) v.SetOrigin(0, 0) - } + return nil + }) } -func (a *App) PrintBody(g *gocui.Gui) { +func printResponseHeaders(g *gocui.Gui, headersString string) { g.Execute(func(g *gocui.Gui) error { - a.getAndPrintResponseBody(g) + v, _ := g.View(RESPONSE_HEADERS_VIEW) + v.Clear() + fmt.Fprint(v, headersString) + if _, err := v.Line(0); err != nil { + v.SetOrigin(0, 0) + } return nil }) } func (a *App) getAndPrintResponseBody(g *gocui.Gui) { + title, body, shouldSetOrigin := a.getResponseBody(g) + printResponseBody(g, title, body, shouldSetOrigin) +} + +func (a *App) getResponseBody(g *gocui.Gui) (title, body string, shouldSetOrigin bool) { if len(a.history) == 0 { return } req := a.history[a.historyIndex] - if req.RawResponseBody == nil { - return - } - title, body, shouldSetOrigin := getResponseBody( + return getResponseBody( getViewValue(g, SEARCH_VIEW), req.RawResponseBody, req.ContentType, a.config.General, ) - printResponseBody(g, title, body, shouldSetOrigin) } func getResponseBody( @@ -714,38 +725,41 @@ func getResponseBody( contentType string, generalAppOptions config.GeneralOptions) (title, body string, shouldSetOrigin bool) { - isText := strings.Contains(contentType, "text") - isApplication := strings.Contains(contentType, "application") - isJson := strings.Contains(contentType, "application/json") - isBinary := !(isText || isApplication) - title = VIEW_PROPERTIES[RESPONSE_BODY_VIEW].title - if searchString == "" || isBinary { - if isBinary { - title += " [binary content]" - body = hex.Dump(responseBody) - } else { - if isJson && generalAppOptions.FormatJSON { - responseBody = formatJson(responseBody) + + if responseBody != nil { + isText := strings.Contains(contentType, "text") + isApplication := strings.Contains(contentType, "application") + isJson := strings.Contains(contentType, "application/json") + isBinary := !(isText || isApplication) + + if searchString == "" || isBinary { + if isBinary { + title += " [binary content]" + body = hex.Dump(responseBody) + } else { + if isJson && generalAppOptions.FormatJSON { + responseBody = formatJson(responseBody) + } + body = string(responseBody) + } + if !generalAppOptions.PreserveScrollPosition { + shouldSetOrigin = true } - body = string(responseBody) - } - if !generalAppOptions.PreserveScrollPosition { - shouldSetOrigin = true - } - } else { - shouldSetOrigin = true - if search_re, err := regexp.Compile(searchString); err != nil { - body = "Error: invalid search regexp" } else { - results := search_re.FindAll(responseBody, 1000) - if len(results) == 0 { - title = "No results" - body = "Error: no results" + shouldSetOrigin = true + if search_re, err := regexp.Compile(searchString); err != nil { + body = "Error: invalid search regexp" } else { - title = fmt.Sprintf("%d results", len(results)) - for _, result := range results { - body += fmt.Sprintf("-----\n%s\n", result) + results := search_re.FindAll(responseBody, 1000) + if len(results) == 0 { + title = "No results" + body = "Error: no results" + } else { + title = fmt.Sprintf("%d results", len(results)) + for _, result := range results { + body += fmt.Sprintf("-----\n%s\n", result) + } } } } @@ -755,14 +769,18 @@ func getResponseBody( } func printResponseBody(g *gocui.Gui, title string, body string, shouldSetOrigin bool) { - v, _ := g.View(RESPONSE_BODY_VIEW) - v.Clear() - v.Title = title - fmt.Fprint(v, body) + g.Execute(func(g *gocui.Gui) error { + v, _ := g.View(RESPONSE_BODY_VIEW) + v.Clear() + v.Title = title + fmt.Fprint(v, body) - if _, err := v.Line(0); shouldSetOrigin || err != nil { - v.SetOrigin(0, 0) - } + if _, err := v.Line(0); shouldSetOrigin || err != nil { + v.SetOrigin(0, 0) + } + + return nil + }) } func formatJson(text []byte) []byte { @@ -1149,7 +1167,7 @@ func (a *App) restoreRequest(g *gocui.Gui, idx int) { v, _ = g.View(RESPONSE_HEADERS_VIEW) setViewTextAndCursor(v, r.ResponseHeaders) - a.PrintBody(g) + a.getAndPrintResponseBody(g) } From b73f898f09ee9c1788af77852affbea2f5647b9b Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Mon, 20 Feb 2017 23:54:19 +0100 Subject: [PATCH 12/15] Very minor typos fix (so that IDE doesn't bug me about those) --- wuzz.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wuzz.go b/wuzz.go index 090b25a..f1663cb 100644 --- a/wuzz.go +++ b/wuzz.go @@ -319,7 +319,7 @@ func init() { CLIENT.Transport = TRANSPORT } -// Editor funcs +// Editor functions func (e *ViewEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { // handle back-tab (\033[Z) sequence @@ -350,7 +350,7 @@ func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui. switch { case (ch != 0 || key == gocui.KeySpace) && mod == 0: e.wuzzEditor.Edit(v, key, ch, mod) - // At the end of the line the default gcui editor adds a whitespace + // At the end of the line the default gocui editor adds a whitespace // Force him to remove ox, _ := v.Cursor() if ox > 1 && ox >= len(v.Buffer())-2 { @@ -480,7 +480,7 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { return a.setView(g) } } - return fmt.Errorf("View not found") + return errors.New("View not found") } func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { @@ -840,7 +840,7 @@ func (a *App) setKey(g *gocui.Gui, keyStr, commandStr, viewName string) error { return nil } -func (a *App) printViewKeybindings(v io.Writer, viewName string) { +func (a *App) printViewKeyBindings(v io.Writer, viewName string) { keys, found := a.config.Keys[viewName] if !found { return @@ -859,7 +859,7 @@ func (a *App) printViewKeybindings(v io.Writer, viewName string) { } func (a *App) SetKeys(g *gocui.Gui) error { - // load config keybindings + // load config key bindings for viewName, keys := range a.config.Keys { if viewName == "global" { viewName = ALL_VIEWS @@ -884,12 +884,12 @@ func (a *App) SetKeys(g *gocui.Gui) error { help.Title = VIEW_PROPERTIES[HELP_VIEW].title help.Highlight = false fmt.Fprint(help, "Keybindings:\n") - a.printViewKeybindings(help, "global") + a.printViewKeyBindings(help, "global") for _, viewName := range VIEWS { if _, found := a.config.Keys[viewName]; !found { continue } - a.printViewKeybindings(help, viewName) + a.printViewKeyBindings(help, viewName) } g.SetViewOnTop(HELP_VIEW) g.SetCurrentView(HELP_VIEW) @@ -1010,11 +1010,11 @@ func (a *App) SetKeys(g *gocui.Gui) error { return nil } -func (a *App) closePopup(g *gocui.Gui, viewname string) { - _, err := g.View(viewname) +func (a *App) closePopup(g *gocui.Gui, viewName string) { + _, err := g.View(viewName) if err == nil { a.currentPopup = "" - g.DeleteView(viewname) + g.DeleteView(viewName) g.SetCurrentView(VIEWS[a.viewIndex%len(VIEWS)]) g.Cursor = true } @@ -1258,7 +1258,7 @@ func (a *App) ParseArgs(g *gocui.Gui, args []string) error { if err != nil || timeout <= 0 { return errors.New("Invalid timeout value") } - a.config.General.Timeout = config.Duration{time.Duration(timeout) * time.Millisecond} + a.config.General.Timeout = config.Duration{Duration: time.Duration(timeout) * time.Millisecond} case "--compressed": vh, _ := g.View(REQUEST_HEADERS_VIEW) if strings.Index(getViewValue(g, REQUEST_HEADERS_VIEW), "Accept-Encoding") == -1 { From fa7124ce2f714f931156378aa17960b422daeb05 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Tue, 21 Feb 2017 04:14:14 +0100 Subject: [PATCH 13/15] Refactored editors + taken care of issue "Home/End doesn't work in other areas except URL #69" --- wuzz.go | 174 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 75 deletions(-) diff --git a/wuzz.go b/wuzz.go index f1663cb..f62184f 100644 --- a/wuzz.go +++ b/wuzz.go @@ -134,7 +134,7 @@ type viewProperties struct { frame bool editable bool wrap bool - editor gocui.Editor + editor *gocui.Editor text string } @@ -144,7 +144,7 @@ var VIEW_PROPERTIES = map[string]viewProperties{ frame: true, editable: true, wrap: false, - editor: &singleLineEditor{&defaultEditor}, + editor: &singleLineEditor, }, URL_PARAMS_VIEW: { title: "URL params", @@ -158,7 +158,7 @@ var VIEW_PROPERTIES = map[string]viewProperties{ frame: true, editable: true, wrap: false, - editor: &singleLineEditor{&defaultEditor}, + editor: &singleLineEditor, text: DEFAULT_METHOD, }, REQUEST_DATA_VIEW: { @@ -180,35 +180,34 @@ var VIEW_PROPERTIES = map[string]viewProperties{ frame: true, editable: true, wrap: true, - editor: nil, // should be set using a.getViewEditor(g) + editor: &emptyEditor, }, RESPONSE_HEADERS_VIEW: { title: "Response headers", frame: true, editable: true, wrap: true, - editor: nil, // should be set using a.getViewEditor(g) + editor: &emptyEditor, }, RESPONSE_BODY_VIEW: { title: "Response body", frame: true, editable: true, wrap: true, - editor: nil, // should be set using a.getViewEditor(g) + editor: &emptyEditor, }, SEARCH_VIEW: { title: "", frame: false, editable: true, wrap: false, - editor: &singleLineEditor{&SearchEditor{&defaultEditor}}, + editor: &searchEditor, }, SEARCH_PROMPT_VIEW: { title: "", frame: false, editable: false, wrap: false, - editor: nil, text: SEARCH_PROMPT, }, ERROR_VIEW: { @@ -216,14 +215,12 @@ var VIEW_PROPERTIES = map[string]viewProperties{ frame: true, editable: false, wrap: false, - editor: nil, }, POPUP_VIEW: { title: "Info", frame: true, editable: false, wrap: false, - editor: nil, }, HISTORY_VIEW: { title: "History", @@ -272,8 +269,6 @@ var VIEWS = []string{ RESPONSE_BODY_VIEW, } -var defaultEditor ViewEditor - const ( MIN_WIDTH = 60 MIN_HEIGHT = 20 @@ -298,88 +293,126 @@ type App struct { config *config.Config } -type ViewEditor struct { - app *App - g *gocui.Gui - backTabEscape bool - origEditor gocui.Editor +// Acts as an editor but does nothing +type EmptyEditor struct { + // (!) Deliberately left empty } -type SearchEditor struct { - wuzzEditor *ViewEditor +func (e EmptyEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + // (!) Deliberately left empty } -// The singleLineEditor removes multi lines capabilities -type singleLineEditor struct { - wuzzEditor gocui.Editor +// Handles BackTab (\033[Z) sequence +type BackTabEditor struct { + editor gocui.Editor + goBack func() + waitingForSecondBackTabRune bool } -func init() { - TRANSPORT.DisableCompression = true - CLIENT.Transport = TRANSPORT -} - -// Editor functions - -func (e *ViewEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - // handle back-tab (\033[Z) sequence - if e.backTabEscape { +func (e *BackTabEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + if e.waitingForSecondBackTabRune { if ch == 'Z' { - e.app.PrevView(e.g, nil) - e.backTabEscape = false + e.goBack() + e.waitingForSecondBackTabRune = false return } else { - e.origEditor.Edit(v, 0, '[', gocui.ModAlt) + e.editor.Edit(v, 0, '[', gocui.ModAlt) } } + if ch == '[' && mod == gocui.ModAlt { - e.backTabEscape = true - return + e.waitingForSecondBackTabRune = true + } else { + e.editor.Edit(v, key, ch, mod) } +} - e.origEditor.Edit(v, key, ch, mod) +// Calls search function +type SearchEditor struct { + editor gocui.Editor + search func() +} + +func (e SearchEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + e.editor.Edit(v, key, ch, mod) + e.search() +} + +// Adds home and end buttons functionality +type HomeEndEditor struct { + editor gocui.Editor +} + +func (e HomeEndEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + switch key { + case gocui.KeyHome: + v.SetCursor(0, 0) + case gocui.KeyEnd: + v.SetCursor(len(v.Buffer())-1, 0) + default: + e.editor.Edit(v, key, ch, mod) + } } -func (e *SearchEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - e.wuzzEditor.Edit(v, key, ch, mod) - e.wuzzEditor.app.getAndPrintResponseBody(e.wuzzEditor.g) +// Removes multi lines capabilities +type SingleLineEditor struct { + editor gocui.Editor } -// The singleLineEditor removes multi lines capabilities -func (e singleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { +func (e SingleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { switch { case (ch != 0 || key == gocui.KeySpace) && mod == 0: - e.wuzzEditor.Edit(v, key, ch, mod) + e.editor.Edit(v, key, ch, mod) // At the end of the line the default gocui editor adds a whitespace // Force him to remove - ox, _ := v.Cursor() - if ox > 1 && ox >= len(v.Buffer())-2 { + o, _ := v.Cursor() + if o > 1 && o >= len(v.Buffer())-2 { v.EditDelete(false) } return + case key == gocui.KeyArrowUp: + key = gocui.KeyHome + case key == gocui.KeyArrowDown: + key = gocui.KeyEnd case key == gocui.KeyEnter: return case key == gocui.KeyArrowRight: - ox, _ := v.Cursor() - if ox >= len(v.Buffer())-1 { + if x, _ := v.Cursor(); x > len(v.Buffer()) { return } - case key == gocui.KeyHome || key == gocui.KeyArrowUp: - v.SetCursor(0, 0) - return - case key == gocui.KeyEnd || key == gocui.KeyArrowDown: - v.SetCursor(len(v.Buffer())-1, 0) - return } - e.wuzzEditor.Edit(v, key, ch, mod) + + e.editor.Edit(v, key, ch, mod) } -// +var defaultEditor, singleLineEditor, searchEditor, emptyEditor gocui.Editor -func (a *App) getResponseViewEditor(g *gocui.Gui) gocui.Editor { - return &ViewEditor{a, g, false, gocui.EditorFunc(func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - return - })} +func (a *App) getPrevViewFunc(g *gocui.Gui) func() { + return func() { + a.PrevView(g, nil) + } +} + +func (a *App) getSearchFun(g *gocui.Gui) func() { + return func() { + a.getAndPrintResponseBody(g) + } +} + +func (a *App) initiateEditors(g *gocui.Gui) { + defaultEditor = &BackTabEditor{ + HomeEndEditor{gocui.DefaultEditor}, + a.getPrevViewFunc(g), + false} + singleLineEditor = SingleLineEditor{ + defaultEditor} + searchEditor = SearchEditor{ + singleLineEditor, + a.getSearchFun(g)} + emptyEditor = &BackTabEditor{ + EmptyEditor{}, + a.getPrevViewFunc(g), + false} } func (p position) getCoordinate(max int) int { @@ -401,7 +434,9 @@ func setViewProperties(v *gocui.View, name string) { v.Frame = VIEW_PROPERTIES[name].frame v.Editable = VIEW_PROPERTIES[name].editable v.Wrap = VIEW_PROPERTIES[name].wrap - v.Editor = VIEW_PROPERTIES[name].editor + if VIEW_PROPERTIES[name].editor != nil { + v.Editor = *VIEW_PROPERTIES[name].editor + } setViewTextAndCursor(v, VIEW_PROPERTIES[name].text) } @@ -423,12 +458,6 @@ func (a *App) Layout(g *gocui.Gui) error { a.setView(g) } - for _, name := range []string{RESPONSE_INFO_VIEW, RESPONSE_HEADERS_VIEW, RESPONSE_BODY_VIEW} { - vp := VIEW_PROPERTIES[name] - vp.editor = a.getResponseViewEditor(g) - VIEW_PROPERTIES[name] = vp - } - p := VIEW_PROPERTIES[URL_VIEW] p.text = a.config.General.DefaultURLScheme + "://" VIEW_PROPERTIES[URL_VIEW] = p @@ -701,22 +730,18 @@ func printResponseHeaders(g *gocui.Gui, headersString string) { } func (a *App) getAndPrintResponseBody(g *gocui.Gui) { - title, body, shouldSetOrigin := a.getResponseBody(g) - printResponseBody(g, title, body, shouldSetOrigin) -} - -func (a *App) getResponseBody(g *gocui.Gui) (title, body string, shouldSetOrigin bool) { if len(a.history) == 0 { return } req := a.history[a.historyIndex] - return getResponseBody( + title, body, shouldSetOrigin := getResponseBody( getViewValue(g, SEARCH_VIEW), req.RawResponseBody, req.ContentType, a.config.General, ) + printResponseBody(g, title, body, shouldSetOrigin) } func getResponseBody( @@ -1379,8 +1404,7 @@ func main() { app := &App{history: make([]*Request, 0, 31)} - // overwrite default editor - defaultEditor = ViewEditor{app, g, false, gocui.DefaultEditor} + app.initiateEditors(g) initApp(app, g) From d075c00e0e6d2a4eaa6c9419c31a9395a69edd8e Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Tue, 21 Feb 2017 04:21:20 +0100 Subject: [PATCH 14/15] Moved editors to separate file --- editors.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ wuzz.go | 92 --------------------------------------------------- 2 files changed, 97 insertions(+), 92 deletions(-) create mode 100644 editors.go diff --git a/editors.go b/editors.go new file mode 100644 index 0000000..5610454 --- /dev/null +++ b/editors.go @@ -0,0 +1,97 @@ +package main + +import ( + "github.com/jroimartin/gocui" +) + +// Acts as an editor but does nothing +type EmptyEditor struct { + // (!) Deliberately left empty +} + +func (e EmptyEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + // (!) Deliberately left empty +} + +// Handles BackTab (\033[Z) sequence +type BackTabEditor struct { + editor gocui.Editor + goBack func() + waitingForSecondBackTabRune bool +} + +func (e *BackTabEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + if e.waitingForSecondBackTabRune { + if ch == 'Z' { + e.goBack() + e.waitingForSecondBackTabRune = false + return + } else { + e.editor.Edit(v, 0, '[', gocui.ModAlt) + } + } + + if ch == '[' && mod == gocui.ModAlt { + e.waitingForSecondBackTabRune = true + } else { + e.editor.Edit(v, key, ch, mod) + } +} + +// Calls search function +type SearchEditor struct { + editor gocui.Editor + search func() +} + +func (e SearchEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + e.editor.Edit(v, key, ch, mod) + e.search() +} + +// Adds home and end buttons functionality +type HomeEndEditor struct { + editor gocui.Editor +} + +func (e HomeEndEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + switch key { + case gocui.KeyHome: + v.SetCursor(0, 0) + case gocui.KeyEnd: + v.SetCursor(len(v.Buffer())-1, 0) + default: + e.editor.Edit(v, key, ch, mod) + } +} + +// Removes multi lines capabilities +type SingleLineEditor struct { + editor gocui.Editor +} + +func (e SingleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + switch { + case (ch != 0 || key == gocui.KeySpace) && mod == 0: + e.editor.Edit(v, key, ch, mod) + // At the end of the line the default gocui editor adds a whitespace + // Force him to remove + o, _ := v.Cursor() + if o > 1 && o >= len(v.Buffer())-2 { + v.EditDelete(false) + } + return + case key == gocui.KeyArrowUp: + key = gocui.KeyHome + case key == gocui.KeyArrowDown: + key = gocui.KeyEnd + case key == gocui.KeyEnter: + return + case key == gocui.KeyArrowRight: + if x, _ := v.Cursor(); x > len(v.Buffer()) { + return + } + } + + e.editor.Edit(v, key, ch, mod) +} diff --git a/wuzz.go b/wuzz.go index f62184f..30b9cf2 100644 --- a/wuzz.go +++ b/wuzz.go @@ -293,98 +293,6 @@ type App struct { config *config.Config } -// Acts as an editor but does nothing -type EmptyEditor struct { - // (!) Deliberately left empty -} - -func (e EmptyEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - // (!) Deliberately left empty -} - -// Handles BackTab (\033[Z) sequence -type BackTabEditor struct { - editor gocui.Editor - goBack func() - waitingForSecondBackTabRune bool -} - -func (e *BackTabEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - if e.waitingForSecondBackTabRune { - if ch == 'Z' { - e.goBack() - e.waitingForSecondBackTabRune = false - return - } else { - e.editor.Edit(v, 0, '[', gocui.ModAlt) - } - } - - if ch == '[' && mod == gocui.ModAlt { - e.waitingForSecondBackTabRune = true - } else { - e.editor.Edit(v, key, ch, mod) - } -} - -// Calls search function -type SearchEditor struct { - editor gocui.Editor - search func() -} - -func (e SearchEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - e.editor.Edit(v, key, ch, mod) - e.search() -} - -// Adds home and end buttons functionality -type HomeEndEditor struct { - editor gocui.Editor -} - -func (e HomeEndEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - switch key { - case gocui.KeyHome: - v.SetCursor(0, 0) - case gocui.KeyEnd: - v.SetCursor(len(v.Buffer())-1, 0) - default: - e.editor.Edit(v, key, ch, mod) - } -} - -// Removes multi lines capabilities -type SingleLineEditor struct { - editor gocui.Editor -} - -func (e SingleLineEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - switch { - case (ch != 0 || key == gocui.KeySpace) && mod == 0: - e.editor.Edit(v, key, ch, mod) - // At the end of the line the default gocui editor adds a whitespace - // Force him to remove - o, _ := v.Cursor() - if o > 1 && o >= len(v.Buffer())-2 { - v.EditDelete(false) - } - return - case key == gocui.KeyArrowUp: - key = gocui.KeyHome - case key == gocui.KeyArrowDown: - key = gocui.KeyEnd - case key == gocui.KeyEnter: - return - case key == gocui.KeyArrowRight: - if x, _ := v.Cursor(); x > len(v.Buffer()) { - return - } - } - - e.editor.Edit(v, key, ch, mod) -} - var defaultEditor, singleLineEditor, searchEditor, emptyEditor gocui.Editor func (a *App) getPrevViewFunc(g *gocui.Gui) func() { From 0cf3fd9d3ee6fc2a4f3c719e92a6a454aa450253 Mon Sep 17 00:00:00 2001 From: Pawel Wolowiec Date: Tue, 21 Feb 2017 05:23:16 +0100 Subject: [PATCH 15/15] Various minor refactorings + moved help text to separate file --- config/helpText.go | 19 ++++ editors.go | 4 +- wuzz.go | 219 +++++++++++++++++++++------------------------ 3 files changed, 125 insertions(+), 117 deletions(-) create mode 100644 config/helpText.go diff --git a/config/helpText.go b/config/helpText.go new file mode 100644 index 0000000..b78b0e1 --- /dev/null +++ b/config/helpText.go @@ -0,0 +1,19 @@ +package config + +var HelpText = `wuzz - Interactive cli tool for HTTP inspection + +Usage: wuzz [-H|--header HEADER]... [-d|--data|--data-binary DATA] [-X|--request METHOD] [-t|--timeout MSECS] [URL] + +Other command line options: + -c, --config PATH Specify custom configuration file + -h, --help Show this + -v, --version Display version number + +Key bindings: + ctrl+r Send request + ctrl+s Save response + tab, ctrl+j Next window + shift+tab, ctrl+k Previous window + ctrl+h, alt+h Show history + pageUp Scroll up the current window + pageDown Scroll down the current window` diff --git a/editors.go b/editors.go index 5610454..ffd0332 100644 --- a/editors.go +++ b/editors.go @@ -6,11 +6,11 @@ import ( // Acts as an editor but does nothing type EmptyEditor struct { - // (!) Deliberately left empty + // Deliberately left empty } func (e EmptyEditor) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - // (!) Deliberately left empty + // Deliberately left empty } // Handles BackTab (\033[Z) sequence diff --git a/wuzz.go b/wuzz.go index 30b9cf2..6f2d69f 100644 --- a/wuzz.go +++ b/wuzz.go @@ -293,6 +293,8 @@ type App struct { config *config.Config } +// EDITORS + var defaultEditor, singleLineEditor, searchEditor, emptyEditor gocui.Editor func (a *App) getPrevViewFunc(g *gocui.Gui) func() { @@ -323,6 +325,8 @@ func (a *App) initiateEditors(g *gocui.Gui) { false} } +// VIEW POSITIONS AND PROPERTIES + func (p position) getCoordinate(max int) int { return int(p.pct*float32(max)) + p.abs } @@ -348,6 +352,8 @@ func setViewProperties(v *gocui.View, name string) { setViewTextAndCursor(v, VIEW_PROPERTIES[name].text) } +// LAYOUT + func (a *App) Layout(g *gocui.Gui) error { if maxX, maxY := g.Size(); maxX < MIN_WIDTH || maxY < MIN_HEIGHT { if v, err := setView(g, ERROR_VIEW); err != nil { @@ -382,10 +388,11 @@ func (a *App) Layout(g *gocui.Gui) error { SEARCH_PROMPT_VIEW, SEARCH_VIEW, } { - if v, err := setView(g, name); err != nil { - if err != gocui.ErrUnknownView { - return err - } + if v, err := setView(g, name); err == nil { + // Deliberately left empty + } else if err != gocui.ErrUnknownView { + return err + } else { setViewProperties(v, name) } } @@ -394,6 +401,8 @@ func (a *App) Layout(g *gocui.Gui) error { return nil } +// VIEW FUNCTIONS + func (a *App) NextView(g *gocui.Gui, v *gocui.View) error { a.viewIndex = (a.viewIndex + 1) % len(VIEWS) return a.setView(g) @@ -420,25 +429,31 @@ func (a *App) setViewByName(g *gocui.Gui, name string) error { return errors.New("View not found") } +// REQUEST + func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error { - rhv, _ := g.View(RESPONSE_HEADERS_VIEW) - rhv.Clear() - rbv, _ := g.View(RESPONSE_BODY_VIEW) - rbv.Clear() + for _, name := range []string{ + RESPONSE_INFO_VIEW, + RESPONSE_HEADERS_VIEW, + RESPONSE_BODY_VIEW, + } { + v, _ := g.View(name) + v.Clear() + } - go a.CreateAndProcessRequest(g) + go a.createAndProcessRequest(g) return nil } -func (a *App) CreateAndProcessRequest(g *gocui.Gui) { +func (a *App) createAndProcessRequest(g *gocui.Gui) { defer func() { if r := recover(); r != nil { printResponseInfo(g, r) } }() - displayInfo(g, "Sending request..") + a.displayRequestInfo(g) defer g.DeleteView(POPUP_VIEW) var r *Request = &Request{} @@ -499,23 +514,11 @@ func (a *App) CreateAndProcessRequest(g *gocui.Gui) { a.getAndPrintResponseBody(g) } -func displayInfo(g *gocui.Gui, msg string) { - pos := VIEW_POSITIONS[POPUP_VIEW] - pos.x0.abs = -len(msg)/2 - 1 - pos.x1.abs = len(msg)/2 + 1 - VIEW_POSITIONS[POPUP_VIEW] = pos - - p := VIEW_PROPERTIES[POPUP_VIEW] - p.text = msg - VIEW_PROPERTIES[POPUP_VIEW] = p - - if v, err := setView(g, POPUP_VIEW); err != nil { - if err != gocui.ErrUnknownView { - return - } - setViewProperties(v, POPUP_VIEW) - g.SetViewOnTop(POPUP_VIEW) - } +func (a *App) displayRequestInfo(g *gocui.Gui) { + msg := "Sending request..." + v, _ := a.CreatePopupView(POPUP_VIEW, len(msg)+2, 1, g) + v.Title = VIEW_PROPERTIES[POPUP_VIEW].title + fmt.Fprintln(v, msg) } func getUrlFromString(urlString string) *url.URL { @@ -725,71 +728,7 @@ func formatJson(text []byte) []byte { return text } -func parseKey(k string) (interface{}, gocui.Modifier, error) { - mod := gocui.ModNone - if strings.Index(k, "Alt") == 0 { - mod = gocui.ModAlt - k = k[3:] - } - switch len(k) { - case 0: - return 0, 0, errors.New("Empty key string") - case 1: - if mod != gocui.ModNone { - k = strings.ToLower(k) - } - return rune(k[0]), mod, nil - } - - key, found := KEYS[k] - if !found { - return 0, 0, fmt.Errorf("Unknown key: %v", k) - } - return key, mod, nil -} - -func (a *App) setKey(g *gocui.Gui, keyStr, commandStr, viewName string) error { - if commandStr == "" { - return nil - } - key, mod, err := parseKey(keyStr) - if err != nil { - return err - } - commandParts := strings.SplitN(commandStr, " ", 2) - command := commandParts[0] - var commandArgs string - if len(commandParts) == 2 { - commandArgs = commandParts[1] - } - keyFnGen, found := COMMANDS[command] - if !found { - return fmt.Errorf("Unknown command: %v", command) - } - keyFn := keyFnGen(commandArgs, a) - if err := g.SetKeybinding(viewName, key, mod, keyFn); err != nil { - return fmt.Errorf("Failed to set key '%v': %v", keyStr, err) - } - return nil -} - -func (a *App) printViewKeyBindings(v io.Writer, viewName string) { - keys, found := a.config.Keys[viewName] - if !found { - return - } - mk := make([]string, len(keys)) - i := 0 - for k := range keys { - mk[i] = k - i++ - } - sort.Strings(mk) - fmt.Fprintf(v, "\n %v\n", viewName) - for _, key := range mk { - fmt.Fprintf(v, " %-15v %v\n", key, keys[key]) - } -} +// KEY BINDINGS func (a *App) SetKeys(g *gocui.Gui) error { // load config key bindings @@ -943,6 +882,74 @@ func (a *App) SetKeys(g *gocui.Gui) error { return nil } +func (a *App) setKey(g *gocui.Gui, keyStr, commandStr, viewName string) error { + if commandStr == "" { + return nil + } + key, mod, err := parseKey(keyStr) + if err != nil { + return err + } + commandParts := strings.SplitN(commandStr, " ", 2) + command := commandParts[0] + var commandArgs string + if len(commandParts) == 2 { + commandArgs = commandParts[1] + } + keyFnGen, found := COMMANDS[command] + if !found { + return fmt.Errorf("Unknown command: %v", command) + } + keyFn := keyFnGen(commandArgs, a) + if err := g.SetKeybinding(viewName, key, mod, keyFn); err != nil { + return fmt.Errorf("Failed to set key '%v': %v", keyStr, err) + } + return nil +} + +func parseKey(k string) (interface{}, gocui.Modifier, error) { + mod := gocui.ModNone + if strings.Index(k, "Alt") == 0 { + mod = gocui.ModAlt + k = k[3:] + } + switch len(k) { + case 0: + return 0, 0, errors.New("Empty key string") + case 1: + if mod != gocui.ModNone { + k = strings.ToLower(k) + } + return rune(k[0]), mod, nil + } + + key, found := KEYS[k] + if !found { + return 0, 0, fmt.Errorf("Unknown key: %v", k) + } + return key, mod, nil +} + +func (a *App) printViewKeyBindings(v io.Writer, viewName string) { + keys, found := a.config.Keys[viewName] + if !found { + return + } + mk := make([]string, len(keys)) + i := 0 + for k := range keys { + mk[i] = k + i++ + } + sort.Strings(mk) + fmt.Fprintf(v, "\n %v\n", viewName) + for _, key := range mk { + fmt.Fprintf(v, " %-15v %v\n", key, keys[key]) + } +} + +// POPUP VIEWS + func (a *App) closePopup(g *gocui.Gui, viewName string) { _, err := g.View(viewName) if err == nil { @@ -953,7 +960,6 @@ func (a *App) closePopup(g *gocui.Gui, viewName string) { } } -// CreatePopupView create a popup like view func (a *App) CreatePopupView(name string, width, height int, g *gocui.Gui) (v *gocui.View, err error) { // Remove any concurrent popup a.closePopup(g, a.currentPopup) @@ -1104,6 +1110,8 @@ func (a *App) restoreRequest(g *gocui.Gui, idx int) { } +// INITIALIZATION + func (a *App) LoadConfig(configPath string) error { if configPath == "" { // Load config from default path @@ -1262,26 +1270,7 @@ func setViewTextAndCursor(v *gocui.View, s string) { v.SetCursor(len(s), 0) } -func help() { - fmt.Println(`wuzz - Interactive cli tool for HTTP inspection - -Usage: wuzz [-H|--header HEADER]... [-d|--data|--data-binary DATA] [-X|--request METHOD] [-t|--timeout MSECS] [URL] - -Other command line options: - -c, --config PATH Specify custom configuration file - -h, --help Show this - -v, --version Display version number - -Key bindings: - ctrl+r Send request - ctrl+s Save response - tab, ctrl+j Next window - shift+tab, ctrl+k Previous window - ctrl+h, alt+h Show history - pageUp Scroll up the current window - pageDown Scroll down the current window`, - ) -} +// MAIN func main() { configPath := "" @@ -1289,7 +1278,7 @@ func main() { for i, arg := range os.Args { switch arg { case "-h", "--help": - help() + fmt.Println(config.HelpText) return case "-v", "--version": fmt.Printf("wuzz %v\n", VERSION)