From 15b53c952ef0d1ad5871f207614f449450000ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sat, 28 Oct 2023 14:02:10 +0800 Subject: [PATCH 1/3] draft for command mode --- pkg/cli/cli.go | 25 +++++++++++++++++++++++-- pkg/cli/editor.go | 31 ++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 71d24eb..47ffff1 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -51,7 +51,7 @@ func NewCLI(wsConn *ws.Connection, input Inputer, output io.Writer) *CLI { return &CLI{ formater: formater.NewFormatter(), - editor: NewEditor(output, history), + editor: NewEditor(output, history, false), wsConn: wsConn, input: input, output: output, @@ -93,7 +93,28 @@ func (c *CLI) Run(opts RunOptions) error { } default: - continue + if event.Key > 0 { + continue + } + + switch event.Rune { + case ':': + cmdEditor := NewEditor(c.output, c.editor.History, true) + + fmt.Fprint(c.output, ":") + cmd, err := cmdEditor.EditRequest(keysEvents, "") + if err != nil { + if err.Error() == "interrupted" { + return nil + } + + fmt.Fprintln(c.output, err) + } + + fmt.Fprintln(c.output, cmd) + default: + continue + } } case msg, ok := <-c.wsConn.Messages: diff --git a/pkg/cli/editor.go b/pkg/cli/editor.go index 7fdec18..dc1cac6 100644 --- a/pkg/cli/editor.go +++ b/pkg/cli/editor.go @@ -8,20 +8,22 @@ import ( ) type Editor struct { - History *History - content *Content - output io.Writer - buffer []rune - pos int + History *History + content *Content + output io.Writer + buffer []rune + pos int + isSingleLine bool } -func NewEditor(output io.Writer, history *History) *Editor { +func NewEditor(output io.Writer, history *History, isSingleLine bool) *Editor { return &Editor{ - History: history, - content: NewContent(), - buffer: make([]rune, 0), - pos: 0, - output: output, + History: history, + content: NewContent(), + buffer: make([]rune, 0), + pos: 0, + output: output, + isSingleLine: isSingleLine, } } @@ -56,6 +58,9 @@ func (ed *Editor) EditRequest(keyStream <-chan keyboard.KeyEvent, initBuffer str case keyboard.KeySpace: fmt.Fprint(ed.output, ed.content.InsertSymbol(' ')) case keyboard.KeyEnter: + if ed.isSingleLine { + continue + } fmt.Fprint(ed.output, ed.content.InsertSymbol('\n')) case keyboard.KeyBackspace, keyboard.KeyDelete, MacOSDeleteKey: fmt.Fprint(ed.output, ed.content.RemoveSymbol()) @@ -86,6 +91,10 @@ func (ed *Editor) EditRequest(keyStream <-chan keyboard.KeyEvent, initBuffer str continue } + if ed.isSingleLine && e.Rune == '\n' { + continue + } + fmt.Fprint(ed.output, ed.content.InsertSymbol(e.Rune)) } } From 47c07fe7b0d270b99bbe7b9ae451abe6532e8d6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Sun, 5 Nov 2023 12:22:05 +0800 Subject: [PATCH 2/3] Makes command mode work --- pkg/cli/cli.go | 7 ++++--- pkg/cli/editor.go | 7 ++++++- pkg/cli/editor_test.go | 12 ++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 0e3140e..aa9ff55 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -84,7 +84,7 @@ func (c *CLI) Run(opts RunOptions) error { if opts.StartEditor { if err := c.RequestMod(keysEvents); err != nil { switch err.Error() { - case "interrupted": + case ErrInterrupted: return nil case "empty request": default: @@ -103,7 +103,7 @@ func (c *CLI) Run(opts RunOptions) error { case keyboard.KeyEnter: if err := c.RequestMod(keysEvents); err != nil { switch err.Error() { - case "interrupted": + case ErrInterrupted: return nil case "empty request": continue @@ -122,9 +122,10 @@ func (c *CLI) Run(opts RunOptions) error { cmdEditor := NewEditor(c.output, c.editor.History, true) fmt.Fprint(c.output, ":") + cmd, err := cmdEditor.EditRequest(keysEvents, "") if err != nil { - if err.Error() == "interrupted" { + if err.Error() == ErrInterrupted { return nil } diff --git a/pkg/cli/editor.go b/pkg/cli/editor.go index e8c0543..064b8ea 100644 --- a/pkg/cli/editor.go +++ b/pkg/cli/editor.go @@ -10,6 +10,7 @@ import ( const ( PastingTimingThresholdInMicrosec = 250 + ErrInterrupted = "interrupted" ) type Editor struct { @@ -47,7 +48,7 @@ func (ed *Editor) EditRequest(keyStream <-chan keyboard.KeyEvent, initBuffer str switch e.Key { case keyboard.KeyCtrlC, keyboard.KeyCtrlD: - return "", fmt.Errorf("interrupted") + return "", fmt.Errorf(ErrInterrupted) case keyboard.KeyCtrlS: return ed.done() case keyboard.KeyEsc: @@ -126,6 +127,10 @@ func (ed *Editor) nextFromHistory() { func (ed *Editor) newLineOrDone(isPasting bool) (isDone bool) { prev := ed.content.PrevSymbol() + if ed.isSingleLine { + return true + } + isDone = prev != '\\' if !isDone { fmt.Fprint(ed.output, ed.content.RemoveSymbol()) diff --git a/pkg/cli/editor_test.go b/pkg/cli/editor_test.go index 6372a54..0e48283 100644 --- a/pkg/cli/editor_test.go +++ b/pkg/cli/editor_test.go @@ -11,7 +11,7 @@ import ( func TestNewEditor(t *testing.T) { output := new(bytes.Buffer) history := NewHistory("", 0) - editor := NewEditor(output, history) + editor := NewEditor(output, history, false) if editor.content == nil { t.Error("Expected non-nil content") @@ -44,7 +44,7 @@ func TestEditRequest(t *testing.T) { output := new(bytes.Buffer) history := NewHistory(tmpfile.Name(), 5) - editor := NewEditor(output, history) + editor := NewEditor(output, history, false) keyStream := make(chan keyboard.KeyEvent) defer close(keyStream) @@ -78,7 +78,7 @@ func TestEditRequestInterrupted(t *testing.T) { output := new(bytes.Buffer) history := NewHistory(tmpfile.Name(), 5) - editor := NewEditor(output, history) + editor := NewEditor(output, history, false) keyStream := make(chan keyboard.KeyEvent) defer close(keyStream) @@ -122,7 +122,7 @@ func TestEditRequestExitEditor(t *testing.T) { output := new(bytes.Buffer) history := NewHistory(tmpfile.Name(), 5) - editor := NewEditor(output, history) + editor := NewEditor(output, history, false) keyStream := make(chan keyboard.KeyEvent) defer close(keyStream) @@ -152,7 +152,7 @@ func TestEditRequestClosingKeyboard(t *testing.T) { output := new(bytes.Buffer) history := NewHistory(tmpfile.Name(), 5) - editor := NewEditor(output, history) + editor := NewEditor(output, history, false) keyStream := make(chan keyboard.KeyEvent) close(keyStream) @@ -178,7 +178,7 @@ func TestEditRequestSpecialKeys(t *testing.T) { output := new(bytes.Buffer) history := NewHistory(tmpfile.Name(), 5) - editor := NewEditor(output, history) + editor := NewEditor(output, history, false) keyStream := make(chan keyboard.KeyEvent) From 7f9aa4dc2f540830567b5af2a4891d67227576df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=A1=D1=8B=D1=81?= =?UTF-8?q?=D0=BE=D0=B5=D0=B2?= Date: Thu, 9 Nov 2023 21:15:27 +0800 Subject: [PATCH 3/3] Introduce command mode --- pkg/cli/cli.go | 54 ++++++++++++++--------------------- pkg/cli/commands.go | 68 ++++++++++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 5bb90e3..bdb6218 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -13,9 +13,10 @@ import ( ) const ( - HistoryFilename = ".wsget_history" - CommandsLimit = 100 - HistoryLimit = 100 + HistoryFilename = ".wsget_history" + HistoryCmdFilename = ".wsget_cmd_history" + CommandsLimit = 100 + HistoryLimit = 100 MacOSDeleteKey = 127 @@ -26,12 +27,13 @@ const ( ) type CLI struct { - formater *formater.Formater - wsConn *ws.Connection - editor *Editor - input Inputer - output io.Writer - commands chan Executer + formater *formater.Formater + wsConn *ws.Connection + editor *Editor + cmdEditor *Editor + input Inputer + output io.Writer + commands chan Executer } type RunOptions struct { @@ -53,16 +55,18 @@ func NewCLI(wsConn *ws.Connection, input Inputer, output io.Writer) (*CLI, error homeDir := currentUser.HomeDir history := NewHistory(homeDir+"/"+HistoryFilename, HistoryLimit) + cmdHistory := NewHistory(homeDir+"/"+HistoryCmdFilename, HistoryLimit) commands := make(chan Executer, CommandsLimit) return &CLI{ - formater: formater.NewFormatter(), - editor: NewEditor(output, history, false), - wsConn: wsConn, - input: input, - output: output, - commands: commands, + formater: formater.NewFormatter(), + editor: NewEditor(output, history, false), + cmdEditor: NewEditor(output, cmdHistory, true), + wsConn: wsConn, + input: input, + output: output, + commands: commands, }, nil } @@ -92,11 +96,8 @@ func (c *CLI) Run(opts RunOptions) error { exCtx := &ExecutionContext{ input: keysEvents, - output: c.output, - editor: c.editor, - wsConn: c.wsConn, + cli: c, outputFile: opts.OutputFile, - formater: c.formater, } for { @@ -122,20 +123,7 @@ func (c *CLI) Run(opts RunOptions) error { switch event.Rune { case ':': - cmdEditor := NewEditor(c.output, c.editor.History, true) - - fmt.Fprint(c.output, ":") - - cmd, err := cmdEditor.EditRequest(keysEvents, "") - if err != nil { - if err.Error() == ErrInterrupted { - return nil - } - - fmt.Fprintln(c.output, err) - } - - fmt.Fprintln(c.output, cmd) + c.commands <- NewCommandCmdEdit() default: continue } diff --git a/pkg/cli/commands.go b/pkg/cli/commands.go index 5c46c1a..0ae82b0 100644 --- a/pkg/cli/commands.go +++ b/pkg/cli/commands.go @@ -9,7 +9,6 @@ import ( "github.com/eiannone/keyboard" "github.com/fatih/color" - "github.com/ksysoev/wsget/pkg/formater" "github.com/ksysoev/wsget/pkg/ws" ) @@ -19,21 +18,14 @@ const ( type ExecutionContext struct { input <-chan keyboard.KeyEvent - output io.Writer - editor *Editor - wsConn *ws.Connection + cli *CLI outputFile io.Writer - formater *formater.Formater } type Executer interface { Execute(*ExecutionContext) (Executer, error) } -type CommandEdit struct { - content string -} - func CommandFactory(raw string) (Executer, error) { if raw == "" { return nil, fmt.Errorf("empty command") @@ -76,17 +68,21 @@ func CommandFactory(raw string) (Executer, error) { } } +type CommandEdit struct { + content string +} + func NewCommandEdit(content string) *CommandEdit { return &CommandEdit{content} } func (c *CommandEdit) Execute(exCtx *ExecutionContext) (Executer, error) { - color.New(color.FgGreen).Fprint(exCtx.output, "->\n") + color.New(color.FgGreen).Fprint(exCtx.cli.output, "->\n") - fmt.Fprint(exCtx.output, ShowCursor) - req, err := exCtx.editor.EditRequest(exCtx.input, c.content) - fmt.Fprint(exCtx.output, LineUp+LineClear) - fmt.Fprint(exCtx.output, HideCursor) + fmt.Fprint(exCtx.cli.output, ShowCursor) + req, err := exCtx.cli.editor.EditRequest(exCtx.input, c.content) + fmt.Fprint(exCtx.cli.output, LineUp+LineClear) + fmt.Fprint(exCtx.cli.output, HideCursor) if err != nil || req == "" { return nil, err @@ -104,7 +100,7 @@ func NewCommandSend(request string) *CommandSend { } func (c *CommandSend) Execute(exCtx *ExecutionContext) (Executer, error) { - msg, err := exCtx.wsConn.Send(c.request) + msg, err := exCtx.cli.wsConn.Send(c.request) if err != nil { return nil, fmt.Errorf("fail to send request: %s", err) } @@ -122,7 +118,7 @@ func NewCommandPrintMsg(msg ws.Message) *CommandPrintMsg { func (c *CommandPrintMsg) Execute(exCtx *ExecutionContext) (Executer, error) { msg := c.msg - output, err := exCtx.formater.FormatMessage(msg) + output, err := exCtx.cli.formater.FormatMessage(msg) if err != nil { return nil, fmt.Errorf("fail to format for output file: %s, data: %q", err, msg.Data) @@ -130,17 +126,17 @@ func (c *CommandPrintMsg) Execute(exCtx *ExecutionContext) (Executer, error) { switch msg.Type { case ws.Request: - color.New(color.FgGreen).Fprint(exCtx.output, "->\n") + color.New(color.FgGreen).Fprint(exCtx.cli.output, "->\n") case ws.Response: - color.New(color.FgRed).Fprint(exCtx.output, "<-\n") + color.New(color.FgRed).Fprint(exCtx.cli.output, "<-\n") default: return nil, fmt.Errorf("unknown message type: %s, data: %q", msg.Type, msg.Data) } - fmt.Fprintf(exCtx.output, "%s\n", output) + fmt.Fprintf(exCtx.cli.output, "%s\n", output) if exCtx.outputFile != nil { - output, err := exCtx.formater.FormatForFile(msg) + output, err := exCtx.cli.formater.FormatForFile(msg) if err != nil { return nil, fmt.Errorf("fail to write to output file: %s", err) } @@ -171,7 +167,7 @@ func NewCommandWaitForResp(timeout time.Duration) *CommandWaitForResp { func (c *CommandWaitForResp) Execute(exCtx *ExecutionContext) (Executer, error) { if c.timeout.Seconds() == 0 { - msg, ok := <-exCtx.wsConn.Messages + msg, ok := <-exCtx.cli.wsConn.Messages if !ok { return nil, fmt.Errorf("connection closed") } @@ -182,7 +178,7 @@ func (c *CommandWaitForResp) Execute(exCtx *ExecutionContext) (Executer, error) select { case <-time.After(c.timeout): return nil, fmt.Errorf("timeout") - case msg, ok := <-exCtx.wsConn.Messages: + case msg, ok := <-exCtx.cli.wsConn.Messages: if !ok { return nil, fmt.Errorf("connection closed") } @@ -190,3 +186,31 @@ func (c *CommandWaitForResp) Execute(exCtx *ExecutionContext) (Executer, error) return NewCommandPrintMsg(msg), nil } } + +type CommandCmdEdit struct{} + +func NewCommandCmdEdit() *CommandCmdEdit { + return &CommandCmdEdit{} +} + +func (c *CommandCmdEdit) Execute(exCtx *ExecutionContext) (Executer, error) { + fmt.Fprint(exCtx.cli.output, ":") + + fmt.Fprint(exCtx.cli.output, ShowCursor) + rawCmd, err := exCtx.cli.cmdEditor.EditRequest(exCtx.input, "") + fmt.Fprint(exCtx.cli.output, LineClear+"\r") + fmt.Fprint(exCtx.cli.output, HideCursor) + + if err != nil { + return nil, err + } + + cmd, err := CommandFactory(rawCmd) + + if err != nil { + color.New(color.FgRed).Fprintln(exCtx.cli.output, err) + return nil, nil + } + + return cmd, nil +}