ted/commandmap.go

365 lines
11 KiB
Go
Raw Normal View History

package main
import (
"errors"
"fmt"
"regexp"
"github.com/lmika/ted/ui"
)
const (
2017-08-25 23:48:22 +00:00
ModAlt rune = 1<<31 - 1
)
// A command
type Command struct {
2017-08-25 23:48:22 +00:00
Name string
Doc string
2017-08-25 23:48:22 +00:00
// TODO: Add argument mapping which will fetch properties from the environment
Action func(ctx *CommandContext) error
}
2017-08-25 23:48:22 +00:00
// Execute the command
func (cmd *Command) Do(ctx *CommandContext) error {
return cmd.Action(ctx)
}
// A command mapping
type CommandMapping struct {
2017-08-25 23:48:22 +00:00
Commands map[string]*Command
KeyMappings map[rune]*Command
}
// Creates a new, empty command mapping
func NewCommandMapping() *CommandMapping {
2017-08-25 23:48:22 +00:00
return &CommandMapping{make(map[string]*Command), make(map[rune]*Command)}
}
// Adds a new command
2017-08-25 23:48:22 +00:00
func (cm *CommandMapping) Define(name string, doc string, opts string, fn func(ctx *CommandContext) error) {
cm.Commands[name] = &Command{name, doc, fn}
}
// Adds a key mapping
func (cm *CommandMapping) MapKey(key rune, cmd *Command) {
2017-08-25 23:48:22 +00:00
cm.KeyMappings[key] = cmd
}
// Searches for a command by name. Returns the command or null
func (cm *CommandMapping) Command(name string) *Command {
2017-08-25 23:48:22 +00:00
return cm.Commands[name]
}
// Searches for a command by key mapping
func (cm *CommandMapping) KeyMapping(key rune) *Command {
2017-08-25 23:48:22 +00:00
return cm.KeyMappings[key]
}
// Evaluate a command
func (cm *CommandMapping) Eval(ctx *CommandContext, expr string) error {
// TODO: Use propper expression language here
cmd := cm.Commands[expr]
if cmd != nil {
return cmd.Do(ctx)
}
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() {
2017-08-25 23:48:22 +00:00
cm.Define("move-down", "Moves the cursor down one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 1) }))
cm.Define("move-up", "Moves the cursor up one row", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, -1) }))
cm.Define("move-left", "Moves the cursor left one column", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(-1, 0) }))
cm.Define("move-right", "Moves the cursor right one column", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(1, 0) }))
// TODO: Pages are just 25 rows and 15 columns at the moment
cm.Define("page-down", "Moves the cursor down one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, 25) }))
cm.Define("page-up", "Moves the cursor up one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(0, -25) }))
cm.Define("page-left", "Moves the cursor left one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(-15, 0) }))
cm.Define("page-right", "Moves the cursor right one page", "", gridNavOperation(func(grid *ui.Grid) { grid.MoveBy(15, 0) }))
cm.Define("row-top", "Moves the cursor to the top of the row", "", gridNavOperation(func(grid *ui.Grid) {
cellX, _ := grid.CellPosition()
grid.MoveTo(cellX, 0)
}))
cm.Define("row-bottom", "Moves the cursor to the bottom of the row", "", gridNavOperation(func(grid *ui.Grid) {
cellX, _ := grid.CellPosition()
_, dimY := grid.Model().Dimensions()
grid.MoveTo(cellX, dimY-1)
}))
cm.Define("col-left", "Moves the cursor to the left-most column", "", gridNavOperation(func(grid *ui.Grid) {
_, cellY := grid.CellPosition()
grid.MoveTo(0, cellY)
}))
cm.Define("col-right", "Moves the cursor to the right-most column", "", gridNavOperation(func(grid *ui.Grid) {
_, cellY := grid.CellPosition()
dimX, _ := grid.Model().Dimensions()
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()
2020-06-16 04:23:17 +00:00
return ctx.ModelVC().DeleteRow(cellY)
})
cm.Define("delete-col", "Removes the currently selected column", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid()
cellX, _ := grid.CellPosition()
2020-06-16 04:23:17 +00:00
return ctx.ModelVC().DeleteCol(cellX)
})
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")
}
2020-06-16 04:23:17 +00:00
height, width := ctx.ModelVC().Model().Dimensions()
startX, startY := ctx.Frame().Grid().CellPosition()
cellX, cellY := startX, startY
for {
cellX++
if cellX >= width {
cellX = 0
cellY = (cellY + 1) % height
}
2020-06-16 04:23:17 +00:00
if ctx.session.LastSearch.MatchString(ctx.ModelVC().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()
2020-06-16 04:23:17 +00:00
height, width := ctx.ModelVC().Model().Dimensions()
if cellX == width-1 {
return ctx.ModelVC().Resize(height, width+1)
}
2020-06-16 04:23:17 +00:00
return nil
})
cm.Define("open-down", "Inserts a row below the curser", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid()
_, cellY := grid.CellPosition()
2020-06-16 04:23:17 +00:00
height, width := ctx.ModelVC().Model().Dimensions()
if cellY == height-1 {
return ctx.ModelVC().Resize(height+1, width)
}
2020-06-16 04:23:17 +00:00
return nil
})
cm.Define("append", "Inserts a row below the curser", "", func(ctx *CommandContext) error {
if err := ctx.Session().Commands.Eval(ctx, "open-down"); err != nil {
return err
}
if err := ctx.Session().Commands.Eval(ctx, "move-down"); err != nil {
return err
}
ctx.Session().UIManager.Redraw()
2020-09-29 00:33:14 +00:00
return ctx.Session().Commands.Eval(ctx, "edit-cell")
})
2020-06-16 04:23:17 +00:00
cm.Define("inc-col-width", "Increase the width of the current column", "", func(ctx *CommandContext) error {
cellX, _ := ctx.Frame().Grid().CellPosition()
attrs := ctx.ModelVC().ColAttrs(cellX)
attrs.Size += 2
ctx.ModelVC().SetColAttrs(cellX, attrs)
return nil
})
cm.Define("dec-col-width", "Decrease the width of the current column", "", func(ctx *CommandContext) error {
cellX, _ := ctx.Frame().Grid().CellPosition()
attrs := ctx.ModelVC().ColAttrs(cellX)
attrs.Size -= 2
if attrs.Size < 4 {
attrs.Size = 4
}
ctx.ModelVC().SetColAttrs(cellX, attrs)
return nil
})
cm.Define("clear-row-marker", "Clears any row markers", "", func(ctx *CommandContext) error {
_, cellY := ctx.Frame().Grid().CellPosition()
attrs := ctx.ModelVC().RowAttrs(cellY)
attrs.Marker = MarkerNone
ctx.ModelVC().SetRowAttrs(cellY, attrs)
return nil
})
cm.Define("mark-row-red", "Set row marker to red", "", func(ctx *CommandContext) error {
_, cellY := ctx.Frame().Grid().CellPosition()
attrs := ctx.ModelVC().RowAttrs(cellY)
attrs.Marker = MarkerRed
ctx.ModelVC().SetRowAttrs(cellY, attrs)
return nil
})
cm.Define("mark-row-green", "Set row marker to green", "", func(ctx *CommandContext) error {
_, cellY := ctx.Frame().Grid().CellPosition()
attrs := ctx.ModelVC().RowAttrs(cellY)
attrs.Marker = MarkerGreen
ctx.ModelVC().SetRowAttrs(cellY, attrs)
return nil
})
cm.Define("mark-row-blue", "Set row marker to blue", "", func(ctx *CommandContext) error {
_, cellY := ctx.Frame().Grid().CellPosition()
attrs := ctx.ModelVC().RowAttrs(cellY)
attrs.Marker = MarkerBlue
ctx.ModelVC().SetRowAttrs(cellY, attrs)
return nil
})
2017-08-25 23:48:22 +00:00
cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error {
ctx.Frame().Prompt(PromptOptions{ Prompt: ":" }, func(res string) error {
return cm.Eval(ctx, res)
2017-08-25 23:48:22 +00:00
})
return nil
})
cm.Define("replace-cell", "Replace the value of the selected cell", "", func(ctx *CommandContext) error {
2017-08-25 23:48:22 +00:00
grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition()
2020-06-16 04:23:17 +00:00
if _, isRwModel := ctx.ModelVC().Model().(RWModel); isRwModel {
ctx.Frame().Prompt(PromptOptions{ Prompt: "> " }, func(res string) error {
2020-06-16 04:23:17 +00:00
return ctx.ModelVC().SetCellValue(cellY, cellX, res)
})
}
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()
2020-06-16 04:23:17 +00:00
if _, isRwModel := ctx.ModelVC().Model().(RWModel); isRwModel {
ctx.Frame().Prompt(PromptOptions{
Prompt: "> ",
2020-06-16 04:23:17 +00:00
InitialValue: grid.Model().CellValue(cellX, cellY),
}, func(res string) error {
2020-06-16 04:23:17 +00:00
return ctx.ModelVC().SetCellValue(cellY, cellX, res)
2017-08-25 23:48:22 +00:00
})
}
return nil
})
cm.Define("save", "Save current file", "", func(ctx *CommandContext) error {
wSource, isWSource := ctx.Session().Source.(WritableModelSource)
if !isWSource {
return fmt.Errorf("model is not writable")
}
2020-06-16 04:23:17 +00:00
if err := wSource.Write(ctx.ModelVC().Model()); err != nil {
return err
}
ctx.Frame().Message("Wrote " + wSource.String())
return nil
})
2017-08-25 23:48:22 +00:00
cm.Define("quit", "Quit TED", "", func(ctx *CommandContext) error {
ctx.Session().UIManager.Shutdown()
return nil
})
cm.Define("save-and-quit", "Save current file, then quit", "", func(ctx *CommandContext) error {
if err := cm.Eval(ctx, "save"); err != nil {
return nil
}
return cm.Eval(ctx, "quit")
})
// Aliases
cm.Commands["w"] = cm.Command("save")
cm.Commands["q"] = cm.Command("quit")
cm.Commands["wq"] = cm.Command("save-and-quit")
}
// Registers the standard view key bindings. These commands require the frame
func (cm *CommandMapping) RegisterViewKeyBindings() {
2017-08-25 23:48:22 +00:00
cm.MapKey('i', cm.Command("move-up"))
cm.MapKey('k', cm.Command("move-down"))
cm.MapKey('j', cm.Command("move-left"))
cm.MapKey('l', cm.Command("move-right"))
cm.MapKey('I', cm.Command("page-up"))
cm.MapKey('K', cm.Command("page-down"))
cm.MapKey('J', cm.Command("page-left"))
cm.MapKey('L', cm.Command("page-right"))
cm.MapKey(ui.KeyCtrlI, cm.Command("row-top"))
cm.MapKey(ui.KeyCtrlK, cm.Command("row-bottom"))
cm.MapKey(ui.KeyCtrlJ, cm.Command("col-left"))
cm.MapKey(ui.KeyCtrlL, cm.Command("col-right"))
cm.MapKey(ui.KeyArrowUp, cm.Command("move-up"))
cm.MapKey(ui.KeyArrowDown, cm.Command("move-down"))
cm.MapKey(ui.KeyArrowLeft, cm.Command("move-left"))
cm.MapKey(ui.KeyArrowRight, cm.Command("move-right"))
cm.MapKey('e', cm.Command("edit-cell"))
cm.MapKey('r', cm.Command("replace-cell"))
2017-08-25 23:48:22 +00:00
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"))
2020-06-16 04:23:17 +00:00
cm.MapKey('0', cm.Command("clear-row-marker"))
cm.MapKey('1', cm.Command("mark-row-red"))
cm.MapKey('2', cm.Command("mark-row-green"))
cm.MapKey('3', cm.Command("mark-row-blue"))
cm.MapKey('{', cm.Command("dec-col-width"))
cm.MapKey('}', cm.Command("inc-col-width"))
2017-08-25 23:48:22 +00:00
cm.MapKey(':', cm.Command("enter-command"))
}
// A nativation command factory. This will perform the passed in operation with the current grid and
// will display the cell value in the message box.
2017-08-25 23:48:22 +00:00
func gridNavOperation(op func(grid *ui.Grid)) func(ctx *CommandContext) error {
return func(ctx *CommandContext) error {
op(ctx.Frame().Grid())
ctx.Frame().ShowCellValue()
return nil
}
}