From 598d9bd962bd6670e5c5dee0f697b6e1d043e4f8 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Tue, 16 Jun 2020 10:05:29 +1000 Subject: [PATCH] Added search and replaced split out edit and replace cell commands --- commandmap.go | 101 +++++++++++++++++++++++++++++++++++++++++++++----- frame.go | 21 +++++++++-- model.go | 47 +++++++++++++++++------ session.go | 13 ++++--- 4 files changed, 151 insertions(+), 31 deletions(-) diff --git a/commandmap.go b/commandmap.go index e8f4cb4..ff33e48 100644 --- a/commandmap.go +++ b/commandmap.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "regexp" "github.com/lmika/ted/ui" ) @@ -67,11 +68,11 @@ func (cm *CommandMapping) Eval(ctx *CommandContext, expr string) error { 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) - } -} +//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() { @@ -105,6 +106,64 @@ func (cm *CommandMapping) RegisterViewCommands() { grid.MoveTo(dimX-1, cellY) })) + cm.Define("delete-row", "Removes the currently selected row", "", func(ctx *CommandContext) error { + grid := ctx.Frame().Grid() + _, cellY := grid.CellPosition() + + if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel { + DeleteRow(rwModel, cellY) + return nil + } + + return errors.New("model is read-only") + }) + cm.Define("delete-col", "Removes the currently selected column", "", func(ctx *CommandContext) error { + grid := ctx.Frame().Grid() + cellX, _ := grid.CellPosition() + + if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel { + DeleteCol(rwModel, cellX) + return nil + } + + return errors.New("model is read-only") + }) + cm.Define("search", "Search for a cell", "", func(ctx *CommandContext) error { + ctx.Frame().Prompt(PromptOptions{ Prompt: "/" }, func(res string) error { + re, err := regexp.Compile(res) + if err != nil { + return fmt.Errorf("invalid regexp: %v", err) + } + + ctx.session.LastSearch = re + return ctx.Session().Commands.Eval(ctx, "search-next") + }) + return nil + }) + cm.Define("search-next", "Goto the next cell", "", func(ctx *CommandContext) error { + if ctx.session.LastSearch == nil { + ctx.Session().Commands.Eval(ctx, "search") + } + + height, width := ctx.session.Model.Dimensions() + startX, startY := ctx.Frame().Grid().CellPosition() + cellX, cellY := startX, startY + + for { + cellX++ + if cellX >= width { + cellX = 0 + cellY = (cellY + 1) % height + } + if ctx.session.LastSearch.MatchString(ctx.session.Model.CellValue(cellY, cellX)) { + ctx.Frame().Grid().MoveTo(cellX, cellY) + return nil + } else if (cellX == startX) && (cellY == startY) { + return errors.New("No match found") + } + } + }) + cm.Define("open-right", "Inserts a column to the right of the curser", "", func(ctx *CommandContext) error { grid := ctx.Frame().Grid() cellX, _ := grid.CellPosition() @@ -149,18 +208,34 @@ func (cm *CommandMapping) RegisterViewCommands() { }) cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error { - ctx.Frame().Prompt(":", func(res string) { - cm.DoEval(ctx, res) + ctx.Frame().Prompt(PromptOptions{ Prompt: ":" }, func(res string) error { + return cm.Eval(ctx, res) }) return nil }) - cm.Define("set-cell", "Change the value of the selected cell", "", func(ctx *CommandContext) error { + cm.Define("replace-cell", "Replace the value of the selected cell", "", func(ctx *CommandContext) error { grid := ctx.Frame().Grid() cellX, cellY := grid.CellPosition() if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel { - ctx.Frame().Prompt("> ", func(res string) { + ctx.Frame().Prompt(PromptOptions{ Prompt: "> " }, func(res string) error { rwModel.SetCellValue(cellY, cellX, res) + return nil + }) + } + return nil + }) + cm.Define("edit-cell", "Modify the value of the selected cell", "", func(ctx *CommandContext) error { + grid := ctx.Frame().Grid() + cellX, cellY := grid.CellPosition() + + if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel { + ctx.Frame().Prompt(PromptOptions{ + Prompt: "> ", + InitialValue: grid.Model().CellValue(cellY, cellX), + }, func(res string) error { + rwModel.SetCellValue(cellY, cellX, res) + return nil }) } return nil @@ -219,10 +294,16 @@ func (cm *CommandMapping) RegisterViewKeyBindings() { cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left")) cm.MapKey(ui.KeyArrowRight, cm.Command("move-right")) - cm.MapKey('e', cm.Command("set-cell")) + cm.MapKey('e', cm.Command("edit-cell")) + cm.MapKey('r', cm.Command("replace-cell")) cm.MapKey('a', cm.Command("append")) + cm.MapKey('D', cm.Command("delete-row")) + + cm.MapKey('/', cm.Command("search")) + cm.MapKey('n', cm.Command("search-next")) + cm.MapKey(':', cm.Command("enter-command")) } diff --git a/frame.go b/frame.go index 6e7e53e..6d72caf 100644 --- a/frame.go +++ b/frame.go @@ -99,15 +99,28 @@ func (frame *Frame) Message(s string) { frame.messageView.Text = s } +func (frame *Frame) Error(err error) { + if err != nil { + frame.messageView.Text = err.Error() + } +} + +type PromptOptions struct { + Prompt string + InitialValue string +} + // Prompt the user for input. This switches the mode to entry mode. -func (frame *Frame) Prompt(prompt string, callback func(res string)) { - frame.textEntry.Prompt = prompt - frame.textEntry.SetValue("") +func (frame *Frame) Prompt(options PromptOptions, callback func(res string) error) { + frame.textEntry.Prompt = options.Prompt + frame.textEntry.SetValue(options.InitialValue) frame.textEntry.OnCancel = frame.exitEntryMode frame.textEntry.OnEntry = func(res string) { frame.exitEntryMode() - callback(res) + if err := callback(res); err != nil { + frame.Error(err) + } } frame.setMode(EntryMode) diff --git a/model.go b/model.go index 0969d78..a838e20 100644 --- a/model.go +++ b/model.go @@ -5,24 +5,47 @@ package main // An abstract model interface. At a minimum, models must be read only. type Model interface { - - // The dimensions of the model (height, width). - Dimensions() (int, int) - // Returns the value of a cell - CellValue(r, c int) string + // The dimensions of the model (height, width). + Dimensions() (int, int) + + // Returns the value of a cell + CellValue(r, c int) string } // A read/write model. type RWModel interface { - Model + Model - // Resize the model. - Resize(newRow, newCol int) + // Resize the model. + Resize(newRow, newCol int) - // Sets the cell value - SetCellValue(r, c int, value string) + // Sets the cell value + SetCellValue(r, c int, value string) - // Returns true if the model has been modified in some way - IsDirty() bool + // Returns true if the model has been modified in some way + IsDirty() bool +} + +// Deletes a row of a model +func DeleteRow(model RWModel, row int) { + h, w := model.Dimensions() + for r := row; r < h-1; r++ { + for c := 0; c < w; c++ { + model.SetCellValue(r, c, model.CellValue(r+1, c)) + } + } + + model.Resize(h-1, w) +} + +// Deletes a column of a model +func DeleteCol(model RWModel, col int) { + h, w := model.Dimensions() + for c := col; c < w-1; c++ { + for r := 0; r < h; r++ { + model.SetCellValue(r, c, model.CellValue(r, c+1)) + } + } + model.Resize(h, w-1) } diff --git a/session.go b/session.go index f5693dd..6a212bd 100644 --- a/session.go +++ b/session.go @@ -1,6 +1,9 @@ package main -import "github.com/lmika/ted/ui" +import ( + "github.com/lmika/ted/ui" + "regexp" +) // The session is responsible for managing the UI and the model and handling // the interaction between the two and the user. @@ -10,6 +13,8 @@ type Session struct { Frame *Frame Commands *CommandMapping UIManager *ui.Ui + + LastSearch *regexp.Regexp } func NewSession(uiManager *ui.Ui, frame *Frame, source ModelSource) *Session { @@ -73,10 +78,8 @@ func (scc *CommandContext) Frame() *Frame { } // Error displays an error if err is not nil -func (scc *CommandContext) ShowError(err error) { - if err != nil { - scc.Frame().Message(err.Error()) - } +func (scc *CommandContext) Error(err error) { + scc.Frame().Error(err) } // Session grid model