aboutsummaryrefslogtreecommitdiffstats
path: root/config/binds.go
diff options
context:
space:
mode:
Diffstat (limited to 'config/binds.go')
-rw-r--r--config/binds.go205
1 files changed, 205 insertions, 0 deletions
diff --git a/config/binds.go b/config/binds.go
index 7816296a..63fc335a 100644
--- a/config/binds.go
+++ b/config/binds.go
@@ -5,11 +5,35 @@ import (
"errors"
"fmt"
"io"
+ "os"
+ "path"
+ "regexp"
"strings"
+ "git.sr.ht/~rjarry/aerc/logging"
"github.com/gdamore/tcell/v2"
+ "github.com/go-ini/ini"
)
+type BindingConfig struct {
+ Global *KeyBindings
+ AccountWizard *KeyBindings
+ Compose *KeyBindings
+ ComposeEditor *KeyBindings
+ ComposeReview *KeyBindings
+ MessageList *KeyBindings
+ MessageView *KeyBindings
+ MessageViewPassthrough *KeyBindings
+ Terminal *KeyBindings
+}
+
+type BindingConfigContext struct {
+ ContextType ContextType
+ Regex *regexp.Regexp
+ Bindings *KeyBindings
+ BindContext string
+}
+
type KeyStroke struct {
Modifiers tcell.ModMask
Key tcell.Key
@@ -38,6 +62,187 @@ const (
type BindingSearchResult int
+func (config *AercConfig) parseBinds(root string) error {
+ // These bindings are not configurable
+ config.Bindings.AccountWizard.ExKey = KeyStroke{
+ Key: tcell.KeyCtrlE,
+ }
+ quit, _ := ParseBinding("<C-q>", ":quit<Enter>")
+ config.Bindings.AccountWizard.Add(quit)
+
+ filename := path.Join(root, "binds.conf")
+ if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) {
+ logging.Debugf("%s not found, installing the system default", filename)
+ if err := installTemplate(root, "binds.conf"); err != nil {
+ return err
+ }
+ }
+ logging.Infof("Parsing key bindings configuration from %s", filename)
+ binds, err := ini.Load(filename)
+ if err != nil {
+ return err
+ }
+
+ baseGroups := map[string]**KeyBindings{
+ "default": &config.Bindings.Global,
+ "compose": &config.Bindings.Compose,
+ "messages": &config.Bindings.MessageList,
+ "terminal": &config.Bindings.Terminal,
+ "view": &config.Bindings.MessageView,
+ "view::passthrough": &config.Bindings.MessageViewPassthrough,
+ "compose::editor": &config.Bindings.ComposeEditor,
+ "compose::review": &config.Bindings.ComposeReview,
+ }
+
+ // Base Bindings
+ for _, sectionName := range binds.SectionStrings() {
+ // Handle :: delimeter
+ baseSectionName := strings.ReplaceAll(sectionName, "::", "////")
+ sections := strings.Split(baseSectionName, ":")
+ baseOnly := len(sections) == 1
+ baseSectionName = strings.ReplaceAll(sections[0], "////", "::")
+
+ group, ok := baseGroups[strings.ToLower(baseSectionName)]
+ if !ok {
+ return errors.New("Unknown keybinding group " + sectionName)
+ }
+
+ if baseOnly {
+ err = config.LoadBinds(binds, baseSectionName, group)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ config.Bindings.Global.Globals = false
+ for _, contextBind := range config.ContextualBinds {
+ if contextBind.BindContext == "default" {
+ contextBind.Bindings.Globals = false
+ }
+ }
+
+ logging.Debugf("binds.conf: %#v", config.Bindings)
+ return nil
+}
+
+func LoadBindingSection(sec *ini.Section) (*KeyBindings, error) {
+ 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("Invalid binding")
+ }
+ bindings.ExKey = strokes[0]
+ continue
+ }
+ if key == "$noinherit" {
+ if value == "false" {
+ continue
+ }
+ if value != "true" {
+ return nil, errors.New("Invalid binding")
+ }
+ bindings.Globals = false
+ continue
+ }
+ binding, err := ParseBinding(key, value)
+ if err != nil {
+ return nil, err
+ }
+ bindings.Add(binding)
+ }
+ return bindings, nil
+}
+
+func (config *AercConfig) LoadBinds(binds *ini.File, baseName string, baseGroup **KeyBindings) error {
+ if sec, err := binds.GetSection(baseName); err == nil {
+ binds, err := LoadBindingSection(sec)
+ if err != nil {
+ return err
+ }
+ *baseGroup = MergeBindings(binds, *baseGroup)
+ }
+
+ for _, sectionName := range binds.SectionStrings() {
+ if !strings.Contains(sectionName, baseName+":") ||
+ strings.Contains(sectionName, baseName+"::") {
+ continue
+ }
+
+ bindSection, err := binds.GetSection(sectionName)
+ if err != nil {
+ return err
+ }
+
+ binds, err := LoadBindingSection(bindSection)
+ if err != nil {
+ return err
+ }
+
+ contextualBind := BindingConfigContext{
+ Bindings: binds,
+ BindContext: baseName,
+ }
+
+ var index int
+ if strings.Contains(sectionName, "=") {
+ index = strings.Index(sectionName, "=")
+ value := string(sectionName[index+1:])
+ contextualBind.Regex, err = regexp.Compile(value)
+ if err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("Invalid Bind Context regex in %s", sectionName)
+ }
+
+ switch sectionName[len(baseName)+1 : index] {
+ case "account":
+ acctName := sectionName[index+1:]
+ valid := false
+ for _, acctConf := range config.Accounts {
+ matches := contextualBind.Regex.FindString(acctConf.Name)
+ if matches != "" {
+ valid = true
+ }
+ }
+ if !valid {
+ logging.Warnf("binds.conf: unexistent account: %s", acctName)
+ continue
+ }
+ contextualBind.ContextType = BIND_CONTEXT_ACCOUNT
+ case "folder":
+ // No validation needed. If the folder doesn't exist, the binds
+ // never get used
+ contextualBind.ContextType = BIND_CONTEXT_FOLDER
+ default:
+ return fmt.Errorf("Unknown Context Bind Section: %s", sectionName)
+ }
+ config.ContextualBinds = append(config.ContextualBinds, contextualBind)
+ }
+
+ return nil
+}
+
+func defaultBindsConfig() BindingConfig {
+ return BindingConfig{
+ Global: NewKeyBindings(),
+ AccountWizard: NewKeyBindings(),
+ Compose: NewKeyBindings(),
+ ComposeEditor: NewKeyBindings(),
+ ComposeReview: NewKeyBindings(),
+ MessageList: NewKeyBindings(),
+ MessageView: NewKeyBindings(),
+ MessageViewPassthrough: NewKeyBindings(),
+ Terminal: NewKeyBindings(),
+ }
+}
+
func NewKeyBindings() *KeyBindings {
return &KeyBindings{
ExKey: KeyStroke{tcell.ModNone, tcell.KeyRune, ':'},