package main
import (
+ "fmt"
"io/ioutil"
"os"
"strings"
+ "unicode"
- "github.com/zyedidia/json5/encoding/json5"
+ "github.com/flynn/json5"
"github.com/zyedidia/tcell"
)
+var bindingsStr map[string]string
var bindings map[Key][]func(*View, bool) bool
+var mouseBindings map[Key][]func(*View, bool, *tcell.EventMouse) bool
var helpBinding string
+var kmenuBinding string
+
+var mouseBindingActions = map[string]func(*View, bool, *tcell.EventMouse) bool{
+ "MousePress": (*View).MousePress,
+ "MouseMultiCursor": (*View).MouseMultiCursor,
+}
var bindingActions = map[string]func(*View, bool) bool{
- "CursorUp": (*View).CursorUp,
- "CursorDown": (*View).CursorDown,
- "CursorPageUp": (*View).CursorPageUp,
- "CursorPageDown": (*View).CursorPageDown,
- "CursorLeft": (*View).CursorLeft,
- "CursorRight": (*View).CursorRight,
- "CursorStart": (*View).CursorStart,
- "CursorEnd": (*View).CursorEnd,
- "SelectToStart": (*View).SelectToStart,
- "SelectToEnd": (*View).SelectToEnd,
- "SelectUp": (*View).SelectUp,
- "SelectDown": (*View).SelectDown,
- "SelectLeft": (*View).SelectLeft,
- "SelectRight": (*View).SelectRight,
- "WordRight": (*View).WordRight,
- "WordLeft": (*View).WordLeft,
- "SelectWordRight": (*View).SelectWordRight,
- "SelectWordLeft": (*View).SelectWordLeft,
- "DeleteWordRight": (*View).DeleteWordRight,
- "DeleteWordLeft": (*View).DeleteWordLeft,
- "SelectToStartOfLine": (*View).SelectToStartOfLine,
- "SelectToEndOfLine": (*View).SelectToEndOfLine,
- "InsertNewline": (*View).InsertNewline,
- "InsertSpace": (*View).InsertSpace,
- "Backspace": (*View).Backspace,
- "Delete": (*View).Delete,
- "InsertTab": (*View).InsertTab,
- "Save": (*View).Save,
- "Find": (*View).Find,
- "FindNext": (*View).FindNext,
- "FindPrevious": (*View).FindPrevious,
- "Center": (*View).Center,
- "Undo": (*View).Undo,
- "Redo": (*View).Redo,
- "Copy": (*View).Copy,
- "Cut": (*View).Cut,
- "CutLine": (*View).CutLine,
- "DuplicateLine": (*View).DuplicateLine,
- "DeleteLine": (*View).DeleteLine,
- "MoveLinesUp": (*View).MoveLinesUp,
- "MoveLinesDown": (*View).MoveLinesDown,
- "IndentSelection": (*View).IndentSelection,
- "OutdentSelection": (*View).OutdentSelection,
- "Paste": (*View).Paste,
- "PastePrimary": (*View).PastePrimary,
- "SelectAll": (*View).SelectAll,
- "OpenFile": (*View).OpenFile,
- "Start": (*View).Start,
- "End": (*View).End,
- "PageUp": (*View).PageUp,
- "PageDown": (*View).PageDown,
- "HalfPageUp": (*View).HalfPageUp,
- "HalfPageDown": (*View).HalfPageDown,
- "StartOfLine": (*View).StartOfLine,
- "EndOfLine": (*View).EndOfLine,
- "ToggleHelp": (*View).ToggleHelp,
- "ToggleRuler": (*View).ToggleRuler,
- "JumpLine": (*View).JumpLine,
- "ClearStatus": (*View).ClearStatus,
- "ShellMode": (*View).ShellMode,
- "CommandMode": (*View).CommandMode,
- "Escape": (*View).Escape,
- "Quit": (*View).Quit,
- "QuitAll": (*View).QuitAll,
- "AddTab": (*View).AddTab,
- "PreviousTab": (*View).PreviousTab,
- "NextTab": (*View).NextTab,
- "NextSplit": (*View).NextSplit,
- "PreviousSplit": (*View).PreviousSplit,
- "Unsplit": (*View).Unsplit,
- "VSplit": (*View).VSplitBinding,
- "HSplit": (*View).HSplitBinding,
- "ToggleMacro": (*View).ToggleMacro,
- "PlayMacro": (*View).PlayMacro,
+ "CursorUp": (*View).CursorUp,
+ "CursorDown": (*View).CursorDown,
+ "CursorPageUp": (*View).CursorPageUp,
+ "CursorPageDown": (*View).CursorPageDown,
+ "CursorLeft": (*View).CursorLeft,
+ "CursorRight": (*View).CursorRight,
+ "CursorStart": (*View).CursorStart,
+ "CursorEnd": (*View).CursorEnd,
+ "SelectToStart": (*View).SelectToStart,
+ "SelectToEnd": (*View).SelectToEnd,
+ "SelectUp": (*View).SelectUp,
+ "SelectDown": (*View).SelectDown,
+ "SelectLeft": (*View).SelectLeft,
+ "SelectRight": (*View).SelectRight,
+ "WordRight": (*View).WordRight,
+ "WordLeft": (*View).WordLeft,
+ "SelectWordRight": (*View).SelectWordRight,
+ "SelectWordLeft": (*View).SelectWordLeft,
+ "DeleteWordRight": (*View).DeleteWordRight,
+ "DeleteWordLeft": (*View).DeleteWordLeft,
+ "SelectLine": (*View).SelectLine,
+ "SelectToStartOfLine": (*View).SelectToStartOfLine,
+ "SelectToEndOfLine": (*View).SelectToEndOfLine,
+ "ParagraphPrevious": (*View).ParagraphPrevious,
+ "ParagraphNext": (*View).ParagraphNext,
+ "InsertNewline": (*View).InsertNewline,
+ "InsertSpace": (*View).InsertSpace,
+ "Backspace": (*View).Backspace,
+ "Delete": (*View).Delete,
+ "InsertTab": (*View).InsertTab,
+ "Save": (*View).Save,
+ "SaveAll": (*View).SaveAll,
+ "SaveAs": (*View).SaveAs,
+ "Find": (*View).Find,
+ "FindNext": (*View).FindNext,
+ "FindPrevious": (*View).FindPrevious,
+ "Center": (*View).Center,
+ "Undo": (*View).Undo,
+ "Redo": (*View).Redo,
+ "Copy": (*View).Copy,
+ "Cut": (*View).Cut,
+ "CutLine": (*View).CutLine,
+ "DuplicateLine": (*View).DuplicateLine,
+ "DeleteLine": (*View).DeleteLine,
+ "MoveLinesUp": (*View).MoveLinesUp,
+ "MoveLinesDown": (*View).MoveLinesDown,
+ "IndentSelection": (*View).IndentSelection,
+ "OutdentSelection": (*View).OutdentSelection,
+ "OutdentLine": (*View).OutdentLine,
+ "Paste": (*View).Paste,
+ "PastePrimary": (*View).PastePrimary,
+ "SelectAll": (*View).SelectAll,
+ "OpenFile": (*View).OpenFile,
+ "Start": (*View).Start,
+ "End": (*View).End,
+ "PageUp": (*View).PageUp,
+ "PageDown": (*View).PageDown,
+ "SelectPageUp": (*View).SelectPageUp,
+ "SelectPageDown": (*View).SelectPageDown,
+ "HalfPageUp": (*View).HalfPageUp,
+ "HalfPageDown": (*View).HalfPageDown,
+ "StartOfLine": (*View).StartOfLine,
+ "EndOfLine": (*View).EndOfLine,
+ "ToggleHelp": (*View).ToggleHelp,
+ "ToggleKeyMenu": (*View).ToggleKeyMenu,
+ "ToggleRuler": (*View).ToggleRuler,
+ "JumpLine": (*View).JumpLine,
+ "ClearStatus": (*View).ClearStatus,
+ "ShellMode": (*View).ShellMode,
+ "CommandMode": (*View).CommandMode,
+ "ToggleOverwriteMode": (*View).ToggleOverwriteMode,
+ "Escape": (*View).Escape,
+ "Quit": (*View).Quit,
+ "QuitAll": (*View).QuitAll,
+ "AddTab": (*View).AddTab,
+ "PreviousTab": (*View).PreviousTab,
+ "NextTab": (*View).NextTab,
+ "NextSplit": (*View).NextSplit,
+ "PreviousSplit": (*View).PreviousSplit,
+ "Unsplit": (*View).Unsplit,
+ "VSplit": (*View).VSplitBinding,
+ "HSplit": (*View).HSplitBinding,
+ "ToggleMacro": (*View).ToggleMacro,
+ "PlayMacro": (*View).PlayMacro,
+ "Suspend": (*View).Suspend,
+ "ScrollUp": (*View).ScrollUpAction,
+ "ScrollDown": (*View).ScrollDownAction,
+ "SpawnMultiCursor": (*View).SpawnMultiCursor,
+ "SpawnMultiCursorSelect": (*View).SpawnMultiCursorSelect,
+ "RemoveMultiCursor": (*View).RemoveMultiCursor,
+ "RemoveAllMultiCursors": (*View).RemoveAllMultiCursors,
+ "SkipMultiCursor": (*View).SkipMultiCursor,
+ "JumpToMatchingBrace": (*View).JumpToMatchingBrace,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*View).InsertNewline,
}
+var bindingMouse = map[string]tcell.ButtonMask{
+ "MouseLeft": tcell.Button1,
+ "MouseMiddle": tcell.Button2,
+ "MouseRight": tcell.Button3,
+ "MouseWheelUp": tcell.WheelUp,
+ "MouseWheelDown": tcell.WheelDown,
+ "MouseWheelLeft": tcell.WheelLeft,
+ "MouseWheelRight": tcell.WheelRight,
+}
+
var bindingKeys = map[string]tcell.Key{
"Up": tcell.KeyUp,
"Down": tcell.KeyDown,
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
+ "CtrlPageUp": tcell.KeyCtrlPgUp,
+ "CtrlPageDown": tcell.KeyCtrlPgDn,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
"Enter": tcell.KeyEnter,
"Backspace": tcell.KeyBackspace2,
+ "OldBackspace": tcell.KeyBackspace,
// I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
"PgUp": tcell.KeyPgUp,
type Key struct {
keyCode tcell.Key
modifiers tcell.ModMask
+ buttons tcell.ButtonMask
r rune
+ escape string
}
// InitBindings initializes the keybindings for micro
func InitBindings() {
bindings = make(map[Key][]func(*View, bool) bool)
+ bindingsStr = make(map[string]string)
+ mouseBindings = make(map[Key][]func(*View, bool, *tcell.EventMouse) bool)
var parsed map[string]string
defaults := DefaultBindings()
case strings.HasPrefix(k, "Shift"):
k = k[5:]
modifiers |= tcell.ModShift
+ case strings.HasPrefix(k, "\x1b"):
+ return Key{
+ keyCode: -1,
+ modifiers: modifiers,
+ buttons: -1,
+ r: 0,
+ escape: k,
+ }, true
default:
break modSearch
}
}
+ if len(k) == 0 {
+ return Key{buttons: -1}, false
+ }
+
// Control is handled specially, since some character codes in bindingKeys
// are different when Control is depressed. We should check for Control keys
// first.
if modifiers&tcell.ModCtrl != 0 {
// see if the key is in bindingKeys with the Ctrl prefix.
+ k = string(unicode.ToUpper(rune(k[0]))) + k[1:]
if code, ok := bindingKeys["Ctrl"+k]; ok {
// It is, we're done.
return Key{
keyCode: code,
modifiers: modifiers,
+ buttons: -1,
r: 0,
}, true
}
return Key{
keyCode: code,
modifiers: modifiers,
+ buttons: -1,
+ r: 0,
+ }, true
+ }
+
+ // See if we can find the key in bindingMouse
+ if code, ok := bindingMouse[k]; ok {
+ return Key{
+ modifiers: modifiers,
+ buttons: code,
r: 0,
}, true
}
return Key{
keyCode: tcell.KeyRune,
modifiers: modifiers,
+ buttons: -1,
r: rune(k[0]),
}, true
}
// We don't know what happened.
- return Key{}, false
+ return Key{buttons: -1}, false
}
// findAction will find 'action' using string 'v'
return action
}
+func findMouseAction(v string) func(*View, bool, *tcell.EventMouse) bool {
+ action, ok := mouseBindingActions[v]
+ if !ok {
+ // If the user seems to be binding a function that doesn't exist
+ // We hope that it's a lua function that exists and bind it to that
+ action = LuaFunctionMouseBinding(v)
+ }
+ return action
+}
+
+// TryBindKey tries to bind a key by writing to configDir/bindings.json
+// This function is unused for now
+func TryBindKey(k, v string) {
+ filename := configDir + "/bindings.json"
+ if _, e := os.Stat(filename); e == nil {
+ input, err := ioutil.ReadFile(filename)
+ if err != nil {
+ TermMessage("Error reading bindings.json file: " + err.Error())
+ return
+ }
+
+ conflict := -1
+ lines := strings.Split(string(input), "\n")
+ for i, l := range lines {
+ parts := strings.Split(l, ":")
+ if len(parts) >= 2 {
+ if strings.Contains(parts[0], k) {
+ conflict = i
+ TermMessage("Warning: Keybinding conflict:", k, " has been overwritten")
+ }
+ }
+ }
+
+ binding := fmt.Sprintf(" \"%s\": \"%s\",", k, v)
+ if conflict == -1 {
+ lines = append([]string{lines[0], binding}, lines[conflict:]...)
+ } else {
+ lines = append(append(lines[:conflict], binding), lines[conflict+1:]...)
+ }
+ txt := strings.Join(lines, "\n")
+ err = ioutil.WriteFile(filename, []byte(txt), 0644)
+ if err != nil {
+ TermMessage("Error")
+ }
+ }
+}
+
// BindKey takes a key and an action and binds the two together
func BindKey(k, v string) {
key, ok := findKey(k)
if !ok {
+ TermMessage("Unknown keybinding: " + k)
return
}
if v == "ToggleHelp" {
helpBinding = k
}
+ if v == "ToggleKeyMenu" {
+ kmenuBinding = k
+ }
+ if helpBinding == k && v != "ToggleHelp" {
+ helpBinding = ""
+ }
+ if kmenuBinding == k && v != "ToggleKeyMenu" {
+ kmenuBinding = ""
+ }
actionNames := strings.Split(v, ",")
+ if actionNames[0] == "UnbindKey" {
+ delete(bindings, key)
+ delete(mouseBindings, key)
+ delete(bindingsStr, k)
+ if len(actionNames) == 1 {
+ return
+ }
+ actionNames = append(actionNames[:0], actionNames[1:]...)
+ }
actions := make([]func(*View, bool) bool, 0, len(actionNames))
+ mouseActions := make([]func(*View, bool, *tcell.EventMouse) bool, 0, len(actionNames))
for _, actionName := range actionNames {
- actions = append(actions, findAction(actionName))
+ if strings.HasPrefix(actionName, "Mouse") {
+ mouseActions = append(mouseActions, findMouseAction(actionName))
+ } else if strings.HasPrefix(actionName, "command:") {
+ cmd := strings.SplitN(actionName, ":", 2)[1]
+ actions = append(actions, CommandAction(cmd))
+ } else if strings.HasPrefix(actionName, "command-edit:") {
+ cmd := strings.SplitN(actionName, ":", 2)[1]
+ actions = append(actions, CommandEditAction(cmd))
+ } else {
+ actions = append(actions, findAction(actionName))
+ }
}
- bindings[key] = actions
+ if len(actions) > 0 {
+ // Can't have a binding be both mouse and normal
+ delete(mouseBindings, key)
+ bindings[key] = actions
+ bindingsStr[k] = v
+ } else if len(mouseActions) > 0 {
+ // Can't have a binding be both mouse and normal
+ delete(bindings, key)
+ mouseBindings[key] = mouseActions
+ }
}
// DefaultBindings returns a map containing micro's default keybindings
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
+ "ShiftHome": "SelectToStartOfLine",
"CtrlShiftRight": "SelectToEndOfLine",
+ "ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
+ "Alt-{": "ParagraphPrevious",
+ "Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
- "Space": "InsertSpace",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "IndentSelection,InsertTab",
- "Backtab": "OutdentSelection",
+ "Backtab": "OutdentSelection,OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
- "CtrlRightSq": "PreviousTab",
- "CtrlBackslash": "NextTab",
+ "Alt,": "PreviousTab",
+ "Alt.": "NextTab",
"Home": "StartOfLine",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
+ "CtrlPageUp": "PreviousTab",
+ "CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
+ "Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "JumpLine",
"Delete": "Delete",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
+ "Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
- "Alt-p": "CursorUp",
- "Alt-n": "CursorDown",
+ // "Alt-p": "CursorUp",
+ // "Alt-n": "CursorDown",
// Integration with file managers
- "F1": "ToggleHelp",
"F2": "Save",
+ "F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
+
+ // Mouse bindings
+ "MouseWheelUp": "ScrollUp",
+ "MouseWheelDown": "ScrollDown",
+ "MouseLeft": "MousePress",
+ "MouseMiddle": "PastePrimary",
+ "Ctrl-MouseLeft": "MouseMultiCursor",
+
+ "Alt-n": "SpawnMultiCursor",
+ "Alt-m": "SpawnMultiCursorSelect",
+ "Alt-p": "RemoveMultiCursor",
+ "Alt-c": "RemoveAllMultiCursors",
+ "Alt-x": "SkipMultiCursor",
}
}