From 6e6e586f1d5216492c9b5ef12ba5b13bf3df768d Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Tue, 29 Sep 2020 23:08:57 +0000 Subject: [PATCH] Added temporary command x-replace This runs a search and replace over the entire model. This is just temporary at the moment in order to get something working. Also added hitting backspace on an empty entrybox to cancel it. --- commandmap.go | 45 +++++++++++++++++++++++++++++++++++++-------- go.mod | 1 + go.sum | 2 ++ session.go | 29 +++++++++++++++++++++-------- ui/stdcomps.go | 14 ++++++++++---- 5 files changed, 71 insertions(+), 20 deletions(-) diff --git a/commandmap.go b/commandmap.go index f9e4aa3..e997b9a 100644 --- a/commandmap.go +++ b/commandmap.go @@ -5,6 +5,8 @@ import ( "fmt" "regexp" + "github.com/lmika/shellwords" + "github.com/lmika/ted/ui" ) @@ -60,20 +62,19 @@ func (cm *CommandMapping) KeyMapping(key rune) *Command { // Evaluate a command func (cm *CommandMapping) Eval(ctx *CommandContext, expr string) error { // TODO: Use propper expression language here - cmd := cm.Commands[expr] + toks := shellwords.Split(expr) + if len(toks) == 0 { + return nil + } + + cmd := cm.Commands[toks[0]] if cmd != nil { - return cmd.Do(ctx) + return cmd.Do(ctx.WithArgs(toks[1:])) } return fmt.Errorf("no such command: %v", expr) } -//func (cm *CommandMapping) DoEval(ctx *CommandContext, expr string) { -// if err := cm.Eval(ctx, expr); err != nil { -// ctx.ShowError(err) -// } -//} - // Registers the standard view navigation commands. These commands require the frame func (cm *CommandMapping) RegisterViewCommands() { cm.Define("move-down", "Moves the cursor down one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 1) })) @@ -153,6 +154,34 @@ func (cm *CommandMapping) RegisterViewCommands() { } } }) + cm.Define("x-replace", "Performs a search and replace", "", func(ctx *CommandContext) error { + if len(ctx.Args()) != 2 { + return errors.New("Usage: x-replace MATCH REPLACEMENT") + } + + match := ctx.Args()[0] + repl := ctx.Args()[1] + + re, err := regexp.Compile(match) + if err != nil { + return fmt.Errorf("invalid regexp: %v", err) + } + + matchCount := 0 + height, width := ctx.ModelVC().Model().Dimensions() + for r := 0; r < height; r++ { + for c := 0; c < width; c++ { + cell := ctx.ModelVC().Model().CellValue(r, c) + if re.FindStringIndex(cell) != nil { + ctx.ModelVC().SetCellValue(r, c, re.ReplaceAllString(cell, repl)) + matchCount++ + } + } + } + + ctx.Frame().ShowMessage(fmt.Sprintf("Replaced %d matches", matchCount)) + return nil + }) cm.Define("open-right", "Inserts a column to the right of the curser", "", func(ctx *CommandContext) error { grid := ctx.Frame().Grid() diff --git a/go.mod b/go.mod index ff3693c..1aaa82d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/lmika/ted go 1.15 require ( + github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe github.com/mattn/go-runewidth v0.0.9 // indirect github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 ) diff --git a/go.sum b/go.sum index 72bf7b1..87c946c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe h1:1UXS/6OFkbi6JrihPykmYO1VtsABB02QQ+YmYYzTY18= +github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe/go.mod h1:qpdOkLougV5Yry4Px9f1w1pNMavcr6Z67VW5Ro+vW5I= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag= diff --git a/session.go b/session.go index d36d296..a8154b7 100644 --- a/session.go +++ b/session.go @@ -1,8 +1,9 @@ package main import ( - "github.com/lmika/ted/ui" "regexp" + + "github.com/lmika/ted/ui" ) // The session is responsible for managing the UI and the model and handling @@ -15,7 +16,7 @@ type Session struct { UIManager *ui.Ui modelController *ModelViewCtrl - LastSearch *regexp.Regexp + LastSearch *regexp.Regexp } func NewSession(uiManager *ui.Ui, frame *Frame, source ModelSource) *Session { @@ -61,7 +62,7 @@ func (session *Session) KeyPressed(key rune, mod int) { cmd := session.Commands.KeyMapping(key) if cmd != nil { - err := cmd.Do(&CommandContext{session}) + err := cmd.Do(&CommandContext{session, nil}) if err != nil { session.Frame.ShowMessage(err.Error()) } @@ -71,6 +72,18 @@ func (session *Session) KeyPressed(key rune, mod int) { // The command context used by the session type CommandContext struct { session *Session + args []string +} + +func (scc *CommandContext) WithArgs(args []string) *CommandContext { + return &CommandContext{ + session: scc.session, + args: args, + } +} + +func (scc *CommandContext) Args() []string { + return scc.args } func (scc *CommandContext) ModelVC() *ModelViewCtrl { @@ -125,11 +138,11 @@ func (sgm *SessionGridModel) CellAttributes(x int, y int) (fg, bg ui.Attribute) } else if colAttrs.Marker != MarkerNone { return markerAttributes[colAttrs.Marker], 0 } - return 0,0 + return 0, 0 } -var markerAttributes = map[Marker]ui.Attribute { - MarkerRed: ui.ColorRed, +var markerAttributes = map[Marker]ui.Attribute{ + MarkerRed: ui.ColorRed, MarkerGreen: ui.ColorGreen, - MarkerBlue: ui.ColorBlue, -} \ No newline at end of file + MarkerBlue: ui.ColorBlue, +} diff --git a/ui/stdcomps.go b/ui/stdcomps.go index b25c784..7c7090b 100644 --- a/ui/stdcomps.go +++ b/ui/stdcomps.go @@ -4,7 +4,7 @@ package ui import ( "unicode" - ) +) // A text component. This simply renders a text string. type TextView struct { @@ -124,6 +124,8 @@ func (te *TextEntry) KeyPressed(key rune, mod int) { if mod&ModKeyAlt != 0 { te.backspaceWhile(unicode.IsSpace) te.backspaceWhile(func(r rune) bool { return !unicode.IsSpace(r) }) + } else if te.cursorOffset == 0 { + te.cancelAndExit() } else { te.backspace() } @@ -136,14 +138,18 @@ func (te *TextEntry) KeyPressed(key rune, mod int) { te.OnEntry(te.value) } } else if key == KeyCtrlC { - if te.OnCancel != nil { - te.OnCancel() - } + te.cancelAndExit() } //panic(fmt.Sprintf("Entered key: '%x', mod: '%x'", key, mod)) } +func (te *TextEntry) cancelAndExit() { + if te.OnCancel != nil { + te.OnCancel() + } +} + // Backspace func (te *TextEntry) backspace() { te.removeCharAtPos(te.cursorOffset - 1)