diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 326dd19..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), - 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 { @@ -116,7 +117,16 @@ func (c *CLI) Run(opts RunOptions) error { case keyboard.KeyEnter: c.commands <- NewCommandEdit("") default: - continue + if event.Key > 0 { + continue + } + + switch event.Rune { + case ':': + c.commands <- NewCommandCmdEdit() + default: + continue + } } case msg, ok := <-c.wsConn.Messages: 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 +} diff --git a/pkg/cli/editor.go b/pkg/cli/editor.go index a6baf9c..2a537b6 100644 --- a/pkg/cli/editor.go +++ b/pkg/cli/editor.go @@ -10,6 +10,7 @@ import ( const ( PastingTimingThresholdInMicrosec = 250 + ErrInterrupted = "interrupted" ) type Editor struct { @@ -19,9 +20,10 @@ type Editor struct { prevPressedTime time.Time 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(), @@ -29,6 +31,7 @@ func NewEditor(output io.Writer, history *History) *Editor { pos: 0, output: output, prevPressedTime: time.Now(), + isSingleLine: isSingleLine, } } @@ -45,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: @@ -74,6 +77,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)) } } @@ -120,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)