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 (
"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"))
}

View file

@ -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)

View file

@ -6,23 +6,46 @@ 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)
// The dimensions of the model (height, width).
Dimensions() (int, int)
// Returns the value of a cell
CellValue(r, c int) string
// 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)
}

View file

@ -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