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:
parent
6e6e586f1d
commit
e790b508a1
|
|
@ -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"))
|
||||
|
|
|
|||
3
frame.go
3
frame.go
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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:]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue