Some small quality of life improvements

- Added Ctrl+A and Ctrl+E for moving around the edit bar a.la. Emacs
- Added the "yank" and "paste" command to copy and paste a single cell value.  These are mapped to "y" and "p".
- Modified the edit bar so that entering commands is the only case where typing backspace on an empty edit bar will cancel out of edit mode
This commit is contained in:
Leon Mika 2020-10-21 14:35:52 +11:00
parent 6e6e586f1d
commit e790b508a1
4 changed files with 63 additions and 7 deletions

View file

@ -276,7 +276,10 @@ func (cm *CommandMapping) RegisterViewCommands() {
})
cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error {
ctx.Frame().Prompt(PromptOptions{Prompt: ":"}, func(res string) error {
ctx.Frame().Prompt(PromptOptions{
Prompt: ":",
CancelOnEmptyBackspace: true,
}, func(res string) error {
return cm.Eval(ctx, res)
})
return nil
@ -300,7 +303,15 @@ func (cm *CommandMapping) RegisterViewCommands() {
grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition()
if _, isRwModel := ctx.ModelVC().Model().(RWModel); isRwModel {
if _, isRwModel := ctx.ModelVC().Model().(RWModel); !isRwModel {
return errors.New("Model is read-only")
}
if len(ctx.Args()) == 1 {
if err := ctx.ModelVC().SetCellValue(cellY, cellX, ctx.Args()[0]); err != nil {
return err
}
} else {
ctx.Frame().Prompt(PromptOptions{
Prompt: "> ",
InitialValue: grid.Model().CellValue(cellX, cellY),
@ -314,6 +325,29 @@ func (cm *CommandMapping) RegisterViewCommands() {
}
return nil
})
cm.Define("yank", "Yank cell value", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition()
// TODO: allow ranges
ctx.Session().pasteBoard.SetCellValue(0, 0, grid.Model().CellValue(cellX, cellY))
return nil
})
cm.Define("paste", "Paste cell value", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition()
// TODO: allow ranges
if _, isRwModel := ctx.ModelVC().Model().(RWModel); !isRwModel {
return errors.New("Model is read-only")
}
if err := ctx.ModelVC().SetCellValue(cellY, cellX, ctx.Session().pasteBoard.CellValue(0, 0)); err != nil {
return err
}
return nil
})
cm.Define("save", "Save current file", "", func(ctx *CommandContext) error {
wSource, isWSource := ctx.Session().Source.(WritableModelSource)
@ -378,6 +412,9 @@ func (cm *CommandMapping) RegisterViewKeyBindings() {
cm.MapKey('/', cm.Command("search"))
cm.MapKey('n', cm.Command("search-next"))
cm.MapKey('y', cm.Command("yank"))
cm.MapKey('p', cm.Command("paste"))
cm.MapKey('0', cm.Command("clear-row-marker"))
cm.MapKey('1', cm.Command("mark-row-red"))
cm.MapKey('2', cm.Command("mark-row-green"))

View file

@ -108,11 +108,14 @@ func (frame *Frame) Error(err error) {
type PromptOptions struct {
Prompt string
InitialValue string
CancelOnEmptyBackspace bool
}
// Prompt the user for input. This switches the mode to entry mode.
func (frame *Frame) Prompt(options PromptOptions, callback func(res string) error) {
frame.textEntry.Reset()
frame.textEntry.Prompt = options.Prompt
frame.textEntry.CancelOnEmptyBackspace = options.CancelOnEmptyBackspace
frame.textEntry.SetValue(options.InitialValue)
frame.textEntry.OnCancel = frame.exitEntryMode

View file

@ -15,6 +15,7 @@ type Session struct {
Commands *CommandMapping
UIManager *ui.Ui
modelController *ModelViewCtrl
pasteBoard RWModel
LastSearch *regexp.Regexp
}
@ -28,6 +29,7 @@ func NewSession(uiManager *ui.Ui, frame *Frame, source ModelSource) *Session {
Commands: NewCommandMapping(),
UIManager: uiManager,
modelController: NewGridViewModel(model),
pasteBoard: NewSingleCellStdModel(),
}
frame.SetModel(&SessionGridModel{session.modelController})

View file

@ -57,6 +57,11 @@ type TextEntry struct {
value string
cursorOffset int
displayOffset int
isDirty bool
// CancelOnEmptyBackspace will cancel the text entry prompt if no other
// key was pressed and the prompt was empty.
CancelOnEmptyBackspace bool
// Called when the user presses Enter
OnEntry func(val string)
@ -65,6 +70,10 @@ type TextEntry struct {
OnCancel func()
}
func (te *TextEntry) Reset() {
te.isDirty = false
}
func (te *TextEntry) Remeasure(w, h int) (int, int) {
return w, 1
}
@ -116,16 +125,18 @@ func (te *TextEntry) KeyPressed(key rune, mod int) {
te.moveCursorBy(-1)
} else if key == KeyArrowRight {
te.moveCursorBy(1)
} else if key == KeyHome {
} else if (key == KeyHome) || (key == KeyCtrlA) {
te.moveCursorTo(0)
} else if key == KeyEnd {
} else if (key == KeyEnd) || (key == KeyCtrlE) {
te.moveCursorTo(len(te.value))
} else if (key == KeyBackspace) || (key == KeyBackspace2) {
if mod&ModKeyAlt != 0 {
te.backspaceWhile(unicode.IsSpace)
te.backspaceWhile(func(r rune) bool { return !unicode.IsSpace(r) })
} else if te.cursorOffset == 0 {
if te.CancelOnEmptyBackspace && !te.isDirty {
te.cancelAndExit()
}
} else {
te.backspace()
}
@ -171,6 +182,7 @@ func (te *TextEntry) backspaceWhile(guard func(r rune) bool) {
// Kill the line. If the cursor is at the end of the line, kill to the start.
// Otherwise, trim the line.
func (te *TextEntry) killLine() {
te.isDirty = true
if te.cursorOffset < len(te.value) {
te.value = te.value[:te.cursorOffset]
} else {
@ -181,6 +193,7 @@ func (te *TextEntry) killLine() {
// Inserts a rune at the cursor position
func (te *TextEntry) insertRune(key rune) {
te.isDirty = true
if te.cursorOffset >= len(te.value) {
te.value += string(key)
} else {
@ -191,6 +204,7 @@ func (te *TextEntry) insertRune(key rune) {
// Remove the character at a specific position
func (te *TextEntry) removeCharAtPos(pos int) {
te.isDirty = true
if (pos >= 0) && (pos < len(te.value)) {
te.value = te.value[:pos] + te.value[pos+1:]
}