Added search and replaced split out edit and replace cell commands

This commit is contained in:
Leon Mika 2020-06-16 10:05:29 +10:00
parent b658536ad1
commit 598d9bd962
4 changed files with 151 additions and 31 deletions

View file

@ -3,6 +3,7 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"regexp"
"github.com/lmika/ted/ui" "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) return fmt.Errorf("no such command: %v", expr)
} }
func (cm *CommandMapping) DoEval(ctx *CommandContext, expr string) { //func (cm *CommandMapping) DoEval(ctx *CommandContext, expr string) {
if err := cm.Eval(ctx, expr); err != nil { // if err := cm.Eval(ctx, expr); err != nil {
ctx.ShowError(err) // ctx.ShowError(err)
} // }
} //}
// Registers the standard view navigation commands. These commands require the frame // Registers the standard view navigation commands. These commands require the frame
func (cm *CommandMapping) RegisterViewCommands() { func (cm *CommandMapping) RegisterViewCommands() {
@ -105,6 +106,64 @@ func (cm *CommandMapping) RegisterViewCommands() {
grid.MoveTo(dimX-1, cellY) 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 { cm.Define("open-right", "Inserts a column to the right of the curser", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid() grid := ctx.Frame().Grid()
cellX, _ := grid.CellPosition() cellX, _ := grid.CellPosition()
@ -149,18 +208,34 @@ func (cm *CommandMapping) RegisterViewCommands() {
}) })
cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error { cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error {
ctx.Frame().Prompt(":", func(res string) { ctx.Frame().Prompt(PromptOptions{ Prompt: ":" }, func(res string) error {
cm.DoEval(ctx, res) return cm.Eval(ctx, res)
}) })
return nil 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() grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition() cellX, cellY := grid.CellPosition()
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel { 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) 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 return nil
@ -219,10 +294,16 @@ func (cm *CommandMapping) RegisterViewKeyBindings() {
cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left")) cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left"))
cm.MapKey(ui.KeyArrowRight, cm.Command("move-right")) 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('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")) cm.MapKey(':', cm.Command("enter-command"))
} }

View file

@ -99,15 +99,28 @@ func (frame *Frame) Message(s string) {
frame.messageView.Text = s 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. // Prompt the user for input. This switches the mode to entry mode.
func (frame *Frame) Prompt(prompt string, callback func(res string)) { func (frame *Frame) Prompt(options PromptOptions, callback func(res string) error) {
frame.textEntry.Prompt = prompt frame.textEntry.Prompt = options.Prompt
frame.textEntry.SetValue("") frame.textEntry.SetValue(options.InitialValue)
frame.textEntry.OnCancel = frame.exitEntryMode frame.textEntry.OnCancel = frame.exitEntryMode
frame.textEntry.OnEntry = func(res string) { frame.textEntry.OnEntry = func(res string) {
frame.exitEntryMode() frame.exitEntryMode()
callback(res) if err := callback(res); err != nil {
frame.Error(err)
}
} }
frame.setMode(EntryMode) frame.setMode(EntryMode)

View file

@ -5,24 +5,47 @@ package main
// An abstract model interface. At a minimum, models must be read only. // An abstract model interface. At a minimum, models must be read only.
type Model interface { type Model interface {
// The dimensions of the model (height, width).
Dimensions() (int, int)
// Returns the value of a cell // The dimensions of the model (height, width).
CellValue(r, c int) string Dimensions() (int, int)
// Returns the value of a cell
CellValue(r, c int) string
} }
// A read/write model. // A read/write model.
type RWModel interface { type RWModel interface {
Model Model
// Resize the model. // Resize the model.
Resize(newRow, newCol int) Resize(newRow, newCol int)
// Sets the cell value // Sets the cell value
SetCellValue(r, c int, value string) SetCellValue(r, c int, value string)
// Returns true if the model has been modified in some way // Returns true if the model has been modified in some way
IsDirty() bool 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)
} }

View file

@ -1,6 +1,9 @@
package main 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 session is responsible for managing the UI and the model and handling
// the interaction between the two and the user. // the interaction between the two and the user.
@ -10,6 +13,8 @@ type Session struct {
Frame *Frame Frame *Frame
Commands *CommandMapping Commands *CommandMapping
UIManager *ui.Ui UIManager *ui.Ui
LastSearch *regexp.Regexp
} }
func NewSession(uiManager *ui.Ui, frame *Frame, source ModelSource) *Session { 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 // Error displays an error if err is not nil
func (scc *CommandContext) ShowError(err error) { func (scc *CommandContext) Error(err error) {
if err != nil { scc.Frame().Error(err)
scc.Frame().Message(err.Error())
}
} }
// Session grid model // Session grid model