Added search and replaced split out edit and replace cell commands
This commit is contained in:
parent
b658536ad1
commit
598d9bd962
101
commandmap.go
101
commandmap.go
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
frame.go
21
frame.go
|
|
@ -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)
|
||||||
|
|
|
||||||
45
model.go
45
model.go
|
|
@ -6,23 +6,46 @@ 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).
|
// The dimensions of the model (height, width).
|
||||||
Dimensions() (int, int)
|
Dimensions() (int, int)
|
||||||
|
|
||||||
// Returns the value of a cell
|
// Returns the value of a cell
|
||||||
CellValue(r, c int) string
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
session.go
13
session.go
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue