mirror of
https://github.com/zyedidia/micro.git
synced 2026-03-01 02:10:23 +09:00
When we temporarily disable the screen (e.g. during RunInteractiveShell) and then enable it again, we reinitialize tcell.Screen from scratch, so we need to register all previously registered raw escape sequences once again. Otherwise raw escape bindings stop working, since the list of raw escape sequences of this newly create tcell.Screen is empty. Fixes #3392
502 lines
12 KiB
Go
502 lines
12 KiB
Go
package action
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/zyedidia/json5"
|
|
"github.com/zyedidia/micro/v2/internal/config"
|
|
"github.com/zyedidia/micro/v2/internal/screen"
|
|
"github.com/zyedidia/tcell/v2"
|
|
)
|
|
|
|
var Binder = map[string]func(e Event, action string){
|
|
"command": InfoMapEvent,
|
|
"buffer": BufMapEvent,
|
|
"terminal": TermMapEvent,
|
|
}
|
|
|
|
func createBindingsIfNotExist(fname string) {
|
|
if _, e := os.Stat(fname); os.IsNotExist(e) {
|
|
ioutil.WriteFile(fname, []byte("{}"), 0644)
|
|
}
|
|
}
|
|
|
|
// InitBindings intializes the bindings map by reading from bindings.json
|
|
func InitBindings() {
|
|
var parsed map[string]interface{}
|
|
|
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
|
createBindingsIfNotExist(filename)
|
|
|
|
if _, e := os.Stat(filename); e == nil {
|
|
input, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
screen.TermMessage("Error reading bindings.json file: " + err.Error())
|
|
return
|
|
}
|
|
|
|
err = json5.Unmarshal(input, &parsed)
|
|
if err != nil {
|
|
screen.TermMessage("Error reading bindings.json:", err.Error())
|
|
}
|
|
}
|
|
|
|
for p, bind := range Binder {
|
|
defaults := DefaultBindings(p)
|
|
|
|
for k, v := range defaults {
|
|
BindKey(k, v, bind)
|
|
}
|
|
}
|
|
|
|
for k, v := range parsed {
|
|
switch val := v.(type) {
|
|
case string:
|
|
BindKey(k, val, Binder["buffer"])
|
|
case map[string]interface{}:
|
|
bind, ok := Binder[k]
|
|
if !ok || bind == nil {
|
|
screen.TermMessage(fmt.Sprintf("%s is not a valid pane type", k))
|
|
continue
|
|
}
|
|
for e, a := range val {
|
|
s, ok := a.(string)
|
|
if !ok {
|
|
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
|
} else {
|
|
BindKey(e, s, bind)
|
|
}
|
|
}
|
|
default:
|
|
screen.TermMessage("Error reading bindings.json: non-string and non-map entry", k)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BindKey(k, v string, bind func(e Event, a string)) {
|
|
event, err := findEvent(k)
|
|
if err != nil {
|
|
screen.TermMessage(err)
|
|
return
|
|
}
|
|
|
|
if strings.HasPrefix(k, "\x1b") {
|
|
screen.RegisterRawSeq(k)
|
|
}
|
|
|
|
bind(event, v)
|
|
|
|
// switch e := event.(type) {
|
|
// case KeyEvent:
|
|
// InfoMapKey(e, v)
|
|
// case KeySequenceEvent:
|
|
// InfoMapKey(e, v)
|
|
// case MouseEvent:
|
|
// InfoMapMouse(e, v)
|
|
// case RawEvent:
|
|
// InfoMapKey(e, v)
|
|
// }
|
|
}
|
|
|
|
var r = regexp.MustCompile("<(.+?)>")
|
|
|
|
func findEvents(k string) (b KeySequenceEvent, ok bool, err error) {
|
|
var events []Event = nil
|
|
for len(k) > 0 {
|
|
groups := r.FindStringSubmatchIndex(k)
|
|
|
|
if len(groups) > 3 {
|
|
if events == nil {
|
|
events = make([]Event, 0, 3)
|
|
}
|
|
|
|
e, ok := findSingleEvent(k[groups[2]:groups[3]])
|
|
if !ok {
|
|
return KeySequenceEvent{}, false, errors.New("Invalid event " + k[groups[2]:groups[3]])
|
|
}
|
|
|
|
events = append(events, e)
|
|
|
|
k = k[groups[3]+1:]
|
|
} else {
|
|
return KeySequenceEvent{}, false, nil
|
|
}
|
|
}
|
|
|
|
return KeySequenceEvent{events}, true, nil
|
|
}
|
|
|
|
// findSingleEvent will find binding Key 'b' using string 'k'
|
|
func findSingleEvent(k string) (b Event, ok bool) {
|
|
modifiers := tcell.ModNone
|
|
|
|
// First, we'll strip off all the modifiers in the name and add them to the
|
|
// ModMask
|
|
modSearch:
|
|
for {
|
|
switch {
|
|
case strings.HasPrefix(k, "-") && k != "-":
|
|
// We optionally support dashes between modifiers
|
|
k = k[1:]
|
|
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
|
|
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
|
|
k = k[4:]
|
|
modifiers |= tcell.ModCtrl
|
|
case strings.HasPrefix(k, "Alt"):
|
|
k = k[3:]
|
|
modifiers |= tcell.ModAlt
|
|
case strings.HasPrefix(k, "Shift"):
|
|
k = k[5:]
|
|
modifiers |= tcell.ModShift
|
|
case strings.HasPrefix(k, "\x1b"):
|
|
return RawEvent{
|
|
esc: k,
|
|
}, true
|
|
default:
|
|
break modSearch
|
|
}
|
|
}
|
|
|
|
if k == "" {
|
|
return KeyEvent{}, false
|
|
}
|
|
|
|
// Control is handled in a special way, since the terminal sends explicitly
|
|
// marked escape sequences for control keys
|
|
// 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 := keyEvents["Ctrl"+k]; ok {
|
|
return KeyEvent{
|
|
code: code,
|
|
mod: modifiers,
|
|
}, true
|
|
}
|
|
}
|
|
|
|
// See if we can find the key in bindingKeys
|
|
if code, ok := keyEvents[k]; ok {
|
|
return KeyEvent{
|
|
code: code,
|
|
mod: modifiers,
|
|
}, true
|
|
}
|
|
|
|
var mstate MouseState = MousePress
|
|
if strings.HasSuffix(k, "Drag") {
|
|
k = k[:len(k)-4]
|
|
mstate = MouseDrag
|
|
} else if strings.HasSuffix(k, "Release") {
|
|
k = k[:len(k)-7]
|
|
mstate = MouseRelease
|
|
}
|
|
// See if we can find the key in bindingMouse
|
|
if code, ok := mouseEvents[k]; ok {
|
|
return MouseEvent{
|
|
btn: code,
|
|
mod: modifiers,
|
|
state: mstate,
|
|
}, true
|
|
}
|
|
|
|
// If we were given one character, then we've got a rune.
|
|
if len(k) == 1 {
|
|
return KeyEvent{
|
|
code: tcell.KeyRune,
|
|
mod: modifiers,
|
|
r: rune(k[0]),
|
|
}, true
|
|
}
|
|
|
|
// We don't know what happened.
|
|
return KeyEvent{}, false
|
|
}
|
|
|
|
func findEvent(k string) (Event, error) {
|
|
var event Event
|
|
event, ok, err := findEvents(k)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !ok {
|
|
event, ok = findSingleEvent(k)
|
|
if !ok {
|
|
return nil, errors.New(k + " is not a bindable event")
|
|
}
|
|
}
|
|
|
|
return event, nil
|
|
}
|
|
|
|
func eventsEqual(e1 Event, e2 Event) bool {
|
|
seq1, ok1 := e1.(KeySequenceEvent)
|
|
seq2, ok2 := e2.(KeySequenceEvent)
|
|
if ok1 && ok2 {
|
|
if len(seq1.keys) != len(seq2.keys) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(seq1.keys); i++ {
|
|
if seq1.keys[i] != seq2.keys[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
return e1 == e2
|
|
}
|
|
|
|
// TryBindKey tries to bind a key by writing to config.ConfigDir/bindings.json
|
|
// Returns true if the keybinding already existed and a possible error
|
|
func TryBindKey(k, v string, overwrite bool) (bool, error) {
|
|
var e error
|
|
var parsed map[string]interface{}
|
|
|
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
|
createBindingsIfNotExist(filename)
|
|
if _, e = os.Stat(filename); e == nil {
|
|
input, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return false, errors.New("Error reading bindings.json file: " + err.Error())
|
|
}
|
|
|
|
err = json5.Unmarshal(input, &parsed)
|
|
if err != nil {
|
|
return false, errors.New("Error reading bindings.json: " + err.Error())
|
|
}
|
|
|
|
key, err := findEvent(k)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
found := false
|
|
var ev string
|
|
for ev = range parsed {
|
|
if e, err := findEvent(ev); err == nil {
|
|
if eventsEqual(e, key) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if found {
|
|
if overwrite {
|
|
parsed[ev] = v
|
|
} else {
|
|
return true, nil
|
|
}
|
|
} else {
|
|
parsed[k] = v
|
|
}
|
|
|
|
BindKey(k, v, Binder["buffer"])
|
|
|
|
txt, _ := json.MarshalIndent(parsed, "", " ")
|
|
return true, ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
|
}
|
|
return false, e
|
|
}
|
|
|
|
// UnbindKey removes the binding for a key from the bindings.json file
|
|
func UnbindKey(k string) error {
|
|
var e error
|
|
var parsed map[string]interface{}
|
|
|
|
filename := filepath.Join(config.ConfigDir, "bindings.json")
|
|
createBindingsIfNotExist(filename)
|
|
if _, e = os.Stat(filename); e == nil {
|
|
input, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return errors.New("Error reading bindings.json file: " + err.Error())
|
|
}
|
|
|
|
err = json5.Unmarshal(input, &parsed)
|
|
if err != nil {
|
|
return errors.New("Error reading bindings.json: " + err.Error())
|
|
}
|
|
|
|
key, err := findEvent(k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for ev := range parsed {
|
|
if e, err := findEvent(ev); err == nil {
|
|
if eventsEqual(e, key) {
|
|
delete(parsed, ev)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if strings.HasPrefix(k, "\x1b") {
|
|
screen.UnregisterRawSeq(k)
|
|
}
|
|
|
|
defaults := DefaultBindings("buffer")
|
|
if a, ok := defaults[k]; ok {
|
|
BindKey(k, a, Binder["buffer"])
|
|
} else if _, ok := config.Bindings["buffer"][k]; ok {
|
|
BufUnmap(key)
|
|
delete(config.Bindings["buffer"], k)
|
|
}
|
|
|
|
txt, _ := json.MarshalIndent(parsed, "", " ")
|
|
return ioutil.WriteFile(filename, append(txt, '\n'), 0644)
|
|
}
|
|
return e
|
|
}
|
|
|
|
var mouseEvents = map[string]tcell.ButtonMask{
|
|
"MouseLeft": tcell.ButtonPrimary,
|
|
"MouseMiddle": tcell.ButtonMiddle,
|
|
"MouseRight": tcell.ButtonSecondary,
|
|
"MouseWheelUp": tcell.WheelUp,
|
|
"MouseWheelDown": tcell.WheelDown,
|
|
"MouseWheelLeft": tcell.WheelLeft,
|
|
"MouseWheelRight": tcell.WheelRight,
|
|
}
|
|
|
|
var keyEvents = map[string]tcell.Key{
|
|
"Up": tcell.KeyUp,
|
|
"Down": tcell.KeyDown,
|
|
"Right": tcell.KeyRight,
|
|
"Left": tcell.KeyLeft,
|
|
"UpLeft": tcell.KeyUpLeft,
|
|
"UpRight": tcell.KeyUpRight,
|
|
"DownLeft": tcell.KeyDownLeft,
|
|
"DownRight": tcell.KeyDownRight,
|
|
"Center": tcell.KeyCenter,
|
|
"PageUp": tcell.KeyPgUp,
|
|
"PageDown": tcell.KeyPgDn,
|
|
"Home": tcell.KeyHome,
|
|
"End": tcell.KeyEnd,
|
|
"Insert": tcell.KeyInsert,
|
|
"Delete": tcell.KeyDelete,
|
|
"Help": tcell.KeyHelp,
|
|
"Exit": tcell.KeyExit,
|
|
"Clear": tcell.KeyClear,
|
|
"Cancel": tcell.KeyCancel,
|
|
"Print": tcell.KeyPrint,
|
|
"Pause": tcell.KeyPause,
|
|
"Backtab": tcell.KeyBacktab,
|
|
"F1": tcell.KeyF1,
|
|
"F2": tcell.KeyF2,
|
|
"F3": tcell.KeyF3,
|
|
"F4": tcell.KeyF4,
|
|
"F5": tcell.KeyF5,
|
|
"F6": tcell.KeyF6,
|
|
"F7": tcell.KeyF7,
|
|
"F8": tcell.KeyF8,
|
|
"F9": tcell.KeyF9,
|
|
"F10": tcell.KeyF10,
|
|
"F11": tcell.KeyF11,
|
|
"F12": tcell.KeyF12,
|
|
"F13": tcell.KeyF13,
|
|
"F14": tcell.KeyF14,
|
|
"F15": tcell.KeyF15,
|
|
"F16": tcell.KeyF16,
|
|
"F17": tcell.KeyF17,
|
|
"F18": tcell.KeyF18,
|
|
"F19": tcell.KeyF19,
|
|
"F20": tcell.KeyF20,
|
|
"F21": tcell.KeyF21,
|
|
"F22": tcell.KeyF22,
|
|
"F23": tcell.KeyF23,
|
|
"F24": tcell.KeyF24,
|
|
"F25": tcell.KeyF25,
|
|
"F26": tcell.KeyF26,
|
|
"F27": tcell.KeyF27,
|
|
"F28": tcell.KeyF28,
|
|
"F29": tcell.KeyF29,
|
|
"F30": tcell.KeyF30,
|
|
"F31": tcell.KeyF31,
|
|
"F32": tcell.KeyF32,
|
|
"F33": tcell.KeyF33,
|
|
"F34": tcell.KeyF34,
|
|
"F35": tcell.KeyF35,
|
|
"F36": tcell.KeyF36,
|
|
"F37": tcell.KeyF37,
|
|
"F38": tcell.KeyF38,
|
|
"F39": tcell.KeyF39,
|
|
"F40": tcell.KeyF40,
|
|
"F41": tcell.KeyF41,
|
|
"F42": tcell.KeyF42,
|
|
"F43": tcell.KeyF43,
|
|
"F44": tcell.KeyF44,
|
|
"F45": tcell.KeyF45,
|
|
"F46": tcell.KeyF46,
|
|
"F47": tcell.KeyF47,
|
|
"F48": tcell.KeyF48,
|
|
"F49": tcell.KeyF49,
|
|
"F50": tcell.KeyF50,
|
|
"F51": tcell.KeyF51,
|
|
"F52": tcell.KeyF52,
|
|
"F53": tcell.KeyF53,
|
|
"F54": tcell.KeyF54,
|
|
"F55": tcell.KeyF55,
|
|
"F56": tcell.KeyF56,
|
|
"F57": tcell.KeyF57,
|
|
"F58": tcell.KeyF58,
|
|
"F59": tcell.KeyF59,
|
|
"F60": tcell.KeyF60,
|
|
"F61": tcell.KeyF61,
|
|
"F62": tcell.KeyF62,
|
|
"F63": tcell.KeyF63,
|
|
"F64": tcell.KeyF64,
|
|
"CtrlSpace": tcell.KeyCtrlSpace,
|
|
"CtrlA": tcell.KeyCtrlA,
|
|
"CtrlB": tcell.KeyCtrlB,
|
|
"CtrlC": tcell.KeyCtrlC,
|
|
"CtrlD": tcell.KeyCtrlD,
|
|
"CtrlE": tcell.KeyCtrlE,
|
|
"CtrlF": tcell.KeyCtrlF,
|
|
"CtrlG": tcell.KeyCtrlG,
|
|
"CtrlH": tcell.KeyCtrlH,
|
|
"CtrlI": tcell.KeyCtrlI,
|
|
"CtrlJ": tcell.KeyCtrlJ,
|
|
"CtrlK": tcell.KeyCtrlK,
|
|
"CtrlL": tcell.KeyCtrlL,
|
|
"CtrlM": tcell.KeyCtrlM,
|
|
"CtrlN": tcell.KeyCtrlN,
|
|
"CtrlO": tcell.KeyCtrlO,
|
|
"CtrlP": tcell.KeyCtrlP,
|
|
"CtrlQ": tcell.KeyCtrlQ,
|
|
"CtrlR": tcell.KeyCtrlR,
|
|
"CtrlS": tcell.KeyCtrlS,
|
|
"CtrlT": tcell.KeyCtrlT,
|
|
"CtrlU": tcell.KeyCtrlU,
|
|
"CtrlV": tcell.KeyCtrlV,
|
|
"CtrlW": tcell.KeyCtrlW,
|
|
"CtrlX": tcell.KeyCtrlX,
|
|
"CtrlY": tcell.KeyCtrlY,
|
|
"CtrlZ": tcell.KeyCtrlZ,
|
|
"CtrlLeftSq": tcell.KeyCtrlLeftSq,
|
|
"CtrlBackslash": tcell.KeyCtrlBackslash,
|
|
"CtrlRightSq": tcell.KeyCtrlRightSq,
|
|
"CtrlCarat": tcell.KeyCtrlCarat,
|
|
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
|
|
"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,
|
|
"PgDown": tcell.KeyPgDn,
|
|
}
|