Add context-specific keybindings
This commit is contained in:
parent
79b459ecb0
commit
f5bf4a9324
|
@ -44,6 +44,16 @@ func NewKeyBindings() *KeyBindings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MergeBindings(bindings ...*KeyBindings) *KeyBindings {
|
||||||
|
merged := NewKeyBindings()
|
||||||
|
for _, b := range bindings {
|
||||||
|
merged.bindings = append(merged.bindings, b.bindings...)
|
||||||
|
}
|
||||||
|
merged.ExKey = bindings[0].ExKey
|
||||||
|
merged.Globals = bindings[0].Globals
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
func (bindings *KeyBindings) Add(binding *Binding) {
|
func (bindings *KeyBindings) Add(binding *Binding) {
|
||||||
// TODO: Search for conflicts?
|
// TODO: Search for conflicts?
|
||||||
bindings.bindings = append(bindings.bindings, binding)
|
bindings.bindings = append(bindings.bindings, binding)
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Binds are of the form <key sequence> = <command to run>
|
||||||
|
# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>"
|
||||||
|
# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit
|
||||||
|
q = :quit<Enter>
|
||||||
|
L = :next-tab<Enter>
|
||||||
|
H = :prev-tab<Enter>
|
||||||
|
<C-t> = :term<Enter>
|
||||||
|
|
||||||
|
[messages]
|
||||||
|
j = :next-message<Enter>
|
||||||
|
<Down> = :next-message<Enter>
|
||||||
|
<C-d> = :next-message 50%<Enter>
|
||||||
|
<C-f> = :next-message 100%<Enter>
|
||||||
|
<PgDn> = :next-message -s 100%<Enter>
|
||||||
|
|
||||||
|
k = :prev-message<Enter>
|
||||||
|
<Up> = :prev-message<Enter>
|
||||||
|
<C-u> = :prev-message 50%<Enter>
|
||||||
|
<C-b> = :prev-message 100%<Enter>
|
||||||
|
<PgUp> = :prev-message -s 100%<Enter>
|
||||||
|
g = :select-message 0<Enter>
|
||||||
|
G = :select-message -1<Enter>
|
||||||
|
|
||||||
|
J = :next-folder<Enter>
|
||||||
|
K = :prev-folder<Enter>
|
||||||
|
|
||||||
|
<Enter> = :view-message<Enter>
|
||||||
|
d = :confirm 'Really delete this message?' ':delete-message<Enter>'<Enter>
|
||||||
|
D = :delete-message<Enter>
|
||||||
|
|
||||||
|
c = :cf<space>
|
||||||
|
$ = :term<space>
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -29,8 +30,16 @@ type AccountConfig struct {
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BindingConfig struct {
|
||||||
|
Global *KeyBindings
|
||||||
|
Compose *KeyBindings
|
||||||
|
MessageList *KeyBindings
|
||||||
|
MessageView *KeyBindings
|
||||||
|
Terminal *KeyBindings
|
||||||
|
}
|
||||||
|
|
||||||
type AercConfig struct {
|
type AercConfig struct {
|
||||||
Lbinds *KeyBindings
|
Bindings BindingConfig
|
||||||
Ini *ini.File `ini:"-"`
|
Ini *ini.File `ini:"-"`
|
||||||
Accounts []AccountConfig `ini:"-"`
|
Accounts []AccountConfig `ini:"-"`
|
||||||
Ui UIConfig
|
Ui UIConfig
|
||||||
|
@ -98,7 +107,13 @@ func LoadConfig(root *string) (*AercConfig, error) {
|
||||||
}
|
}
|
||||||
file.NameMapper = mapName
|
file.NameMapper = mapName
|
||||||
config := &AercConfig{
|
config := &AercConfig{
|
||||||
Lbinds: NewKeyBindings(),
|
Bindings: BindingConfig{
|
||||||
|
Global: NewKeyBindings(),
|
||||||
|
Compose: NewKeyBindings(),
|
||||||
|
MessageList: NewKeyBindings(),
|
||||||
|
MessageView: NewKeyBindings(),
|
||||||
|
Terminal: NewKeyBindings(),
|
||||||
|
},
|
||||||
Ini: file,
|
Ini: file,
|
||||||
|
|
||||||
Ui: UIConfig{
|
Ui: UIConfig{
|
||||||
|
@ -121,20 +136,65 @@ func LoadConfig(root *string) (*AercConfig, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lbinds, err := file.GetSection("lbinds"); err == nil {
|
|
||||||
for key, value := range lbinds.KeysHash() {
|
|
||||||
binding, err := ParseBinding(key, value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Lbinds.Add(binding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
accountsPath := path.Join(*root, "accounts.conf")
|
accountsPath := path.Join(*root, "accounts.conf")
|
||||||
if accounts, err := loadAccountConfig(accountsPath); err != nil {
|
if accounts, err := loadAccountConfig(accountsPath); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
config.Accounts = accounts
|
config.Accounts = accounts
|
||||||
}
|
}
|
||||||
|
binds, err := ini.Load(path.Join(*root, "binds.conf"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
groups := map[string]**KeyBindings{
|
||||||
|
"default": &config.Bindings.Global,
|
||||||
|
"compose": &config.Bindings.Compose,
|
||||||
|
"messages": &config.Bindings.MessageList,
|
||||||
|
"terminal": &config.Bindings.Terminal,
|
||||||
|
"view": &config.Bindings.MessageView,
|
||||||
|
}
|
||||||
|
for _, name := range binds.SectionStrings() {
|
||||||
|
sec, err := binds.GetSection(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
group, ok := groups[strings.ToLower(name)]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("Unknown keybinding group " + name)
|
||||||
|
}
|
||||||
|
bindings := NewKeyBindings()
|
||||||
|
for key, value := range sec.KeysHash() {
|
||||||
|
if key == "$ex" {
|
||||||
|
strokes, err := ParseKeyStrokes(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(strokes) != 1 {
|
||||||
|
return nil, errors.New(
|
||||||
|
"Error: only one keystroke supported for $ex")
|
||||||
|
}
|
||||||
|
bindings.ExKey = strokes[0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if key == "$noinherit" {
|
||||||
|
if value == "false" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if value != "true" {
|
||||||
|
return nil, errors.New(
|
||||||
|
"Error: expected 'true' or 'false' for $noinherit")
|
||||||
|
}
|
||||||
|
bindings.Globals = false
|
||||||
|
}
|
||||||
|
binding, err := ParseBinding(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bindings.Add(binding)
|
||||||
|
}
|
||||||
|
*group = MergeBindings(bindings, *group)
|
||||||
|
}
|
||||||
|
// Globals can't inherit from themselves
|
||||||
|
config.Bindings.Global.Globals = false
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,24 @@ func (aerc *Aerc) Draw(ctx *libui.Context) {
|
||||||
aerc.grid.Draw(ctx)
|
aerc.grid.Draw(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (aerc *Aerc) getBindings() *config.KeyBindings {
|
||||||
|
switch aerc.SelectedTab().(type) {
|
||||||
|
case *AccountView:
|
||||||
|
return aerc.conf.Bindings.MessageList
|
||||||
|
default:
|
||||||
|
return aerc.conf.Bindings.Global
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
|
||||||
|
aerc.pendingKeys = []config.KeyStroke{}
|
||||||
|
for _, stroke := range strokes {
|
||||||
|
simulated := tcell.NewEventKey(
|
||||||
|
stroke.Key, stroke.Rune, tcell.ModNone)
|
||||||
|
aerc.Event(simulated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (aerc *Aerc) Event(event tcell.Event) bool {
|
func (aerc *Aerc) Event(event tcell.Event) bool {
|
||||||
if aerc.focused != nil {
|
if aerc.focused != nil {
|
||||||
return aerc.focused.Event(event)
|
return aerc.focused.Event(event)
|
||||||
|
@ -105,18 +123,30 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
|
||||||
Key: event.Key(),
|
Key: event.Key(),
|
||||||
Rune: event.Rune(),
|
Rune: event.Rune(),
|
||||||
})
|
})
|
||||||
result, output := aerc.conf.Lbinds.GetBinding(aerc.pendingKeys)
|
bindings := aerc.getBindings()
|
||||||
|
incomplete := false
|
||||||
|
result, strokes := bindings.GetBinding(aerc.pendingKeys)
|
||||||
switch result {
|
switch result {
|
||||||
case config.BINDING_FOUND:
|
case config.BINDING_FOUND:
|
||||||
aerc.pendingKeys = []config.KeyStroke{}
|
aerc.simulate(strokes)
|
||||||
for _, stroke := range output {
|
return true
|
||||||
simulated := tcell.NewEventKey(
|
|
||||||
stroke.Key, stroke.Rune, tcell.ModNone)
|
|
||||||
aerc.Event(simulated)
|
|
||||||
}
|
|
||||||
case config.BINDING_INCOMPLETE:
|
case config.BINDING_INCOMPLETE:
|
||||||
return false
|
incomplete = true
|
||||||
case config.BINDING_NOT_FOUND:
|
case config.BINDING_NOT_FOUND:
|
||||||
|
}
|
||||||
|
if bindings.Globals {
|
||||||
|
result, strokes = aerc.conf.Bindings.Global.
|
||||||
|
GetBinding(aerc.pendingKeys)
|
||||||
|
switch result {
|
||||||
|
case config.BINDING_FOUND:
|
||||||
|
aerc.simulate(strokes)
|
||||||
|
return true
|
||||||
|
case config.BINDING_INCOMPLETE:
|
||||||
|
incomplete = true
|
||||||
|
case config.BINDING_NOT_FOUND:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !incomplete {
|
||||||
aerc.pendingKeys = []config.KeyStroke{}
|
aerc.pendingKeys = []config.KeyStroke{}
|
||||||
if event.Rune() == ':' {
|
if event.Rune() == ':' {
|
||||||
aerc.BeginExCommand()
|
aerc.BeginExCommand()
|
||||||
|
|
Loading…
Reference in New Issue