~sircmpwn/aerc

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
2 2

[PATCH v3] Account Specific Bindings

Details
Message ID
<20210511184135.56476-1-jonathan@jonnobrow.co.uk>
DKIM signature
missing
Download raw message
Patch: +165 -54
---
 config/config.go      | 150 +++++++++++++++++++++++++++++-------------
 doc/aerc-config.5.scd |  15 +++++
 widgets/aerc.go       |  54 ++++++++++++---
 3 files changed, 165 insertions(+), 54 deletions(-)

diff --git a/config/config.go b/config/config.go
index af9c63b..528c134 100644
--- a/config/config.go
+++ b/config/config.go
@@ -90,14 +90,19 @@ type AccountConfig struct {
}

type BindingConfig struct {
	Global        *KeyBindings
	AccountWizard *KeyBindings
	Compose       *KeyBindings
	ComposeEditor *KeyBindings
	ComposeReview *KeyBindings
	MessageList   *KeyBindings
	MessageView   *KeyBindings
	Terminal      *KeyBindings
	Global         *BindingGroup 
	AccountWizard  *BindingGroup 
	Compose        *BindingGroup 
	ComposeEditor  *BindingGroup 
	ComposeReview  *BindingGroup 
	MessageList    *BindingGroup 
	MessageView    *BindingGroup 
	Terminal       *BindingGroup 
}

type BindingGroup struct {
    Base          *KeyBindings
    Account       map[string]*KeyBindings
}

type ComposeConfig struct {
@@ -478,16 +483,10 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
	}
	file.NameMapper = mapName
	config := &AercConfig{
		Bindings: BindingConfig{
			Global:        NewKeyBindings(),
			AccountWizard: NewKeyBindings(),
			Compose:       NewKeyBindings(),
			ComposeEditor: NewKeyBindings(),
			ComposeReview: NewKeyBindings(),
			MessageList:   NewKeyBindings(),
			MessageView:   NewKeyBindings(),
			Terminal:      NewKeyBindings(),
		},
        Bindings: BindingConfig{
            AccountWizard: &BindingGroup{Base: NewKeyBindings()},
        },

		Ini: file,

		Ui: UIConfig{
@@ -542,12 +541,12 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
		},
	}

	// These bindings are not configurable
	config.Bindings.AccountWizard.ExKey = KeyStroke{
    // These bindings are not configurable
	config.Bindings.AccountWizard.Base.ExKey = KeyStroke{
		Key: tcell.KeyCtrlE,
	}
	quit, _ := ParseBinding("<C-q>", ":quit<Enter>")
	config.Bindings.AccountWizard.Add(quit)
	config.Bindings.AccountWizard.Base.Add(quit)

	if err = config.LoadConfig(file); err != nil {
		return nil, err
@@ -565,6 +564,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
	} else {
		config.Accounts = accounts
	}

	filename = path.Join(*root, "binds.conf")
	binds, err := ini.Load(filename)
	if err != nil {
@@ -575,25 +575,86 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
			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,

		"compose::editor": &config.Bindings.ComposeEditor,
		"compose::review": &config.Bindings.ComposeReview,
	}
    bindSectionMap := map[string][]string {
        "default": []string{},
        "compose": []string{},
        "messages": []string{},
        "terminal": []string{},
        "view": []string{},
        "compose::editor": []string{},
        "compose::review": []string{},
    }
	for _, name := range binds.SectionStrings() {
		sec, err := binds.GetSection(name)
        parts := strings.Split(name, "//")
        base := parts[0]
        section, ok := bindSectionMap[strings.ToLower(base)]
        if !ok {
            return nil, errors.New("Invalid section name " + name + ", Base was: " + base)
        }
        section = append(section, name)
        bindSectionMap[strings.ToLower(base)] = section
	}

    acctNames := []string{}
    for _, acctConf := range config.Accounts {
        acctNames = append(acctNames, acctConf.Name)
    }
    
    bindingGroups := make(map[string]*BindingGroup)
    for baseName, secNames := range bindSectionMap {
        group, err := NewBindingGroup(binds, acctNames, secNames, baseName)
        if err != nil {
            return nil, err
        }
        bindingGroups[baseName] = group
    }
    
    config.Bindings.Global          = bindingGroups["default"]
    config.Bindings.Compose         = bindingGroups["compose"]
    config.Bindings.ComposeEditor   = bindingGroups["compose::editor"] 
    config.Bindings.ComposeReview   = bindingGroups["compose::review"]
    config.Bindings.MessageList     = bindingGroups["messages"]
    config.Bindings.MessageView     = bindingGroups["view"]
    config.Bindings.Terminal        = bindingGroups["terminal"]

    fmt.Printf("%:v", config.Bindings.MessageList.Account)

    // Globals can't inherit from themselves
	config.Bindings.Global.Base.Globals = false
    for _, acctBinds := range config.Bindings.Global.Account {
        acctBinds.Globals = false
    }
	return config, nil
}

func NewBindingGroup(binds *ini.File, accountNames []string, secNames []string, baseName string) (*BindingGroup, error) {
    group := &BindingGroup{ 
        Base: NewKeyBindings(), Account: make(map[string]*KeyBindings) }
    for _, name := range accountNames {
        group.Account[strings.ToLower(name)] = NewKeyBindings()
    }
    for _, name := range secNames {
        parts := strings.Split(name, "//")

        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)
		}

        subgroup := &KeyBindings{}
        if (len(parts) == 2) {
            sub, ok := group.Account[strings.ToLower(parts[1])]
            if !ok {
                return nil, errors.New("Invalid binding subgroup name " + name)
            }
            subgroup = sub
        } else if (len(parts) == 1) {
            subgroup = group.Base
        } else {
            return nil, errors.New("Invalid binding group name " + name)
        }

		bindings := NewKeyBindings()
		for key, value := range sec.KeysHash() {
			if key == "$ex" {
@@ -602,8 +663,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
					return nil, err
				}
				if len(strokes) != 1 {
					return nil, errors.New(
						"Error: only one keystroke supported for $ex")
					return nil, errors.New("Invalid binding")
				}
				bindings.ExKey = strokes[0]
				continue
@@ -613,8 +673,7 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
					continue
				}
				if value != "true" {
					return nil, errors.New(
						"Error: expected 'true' or 'false' for $noinherit")
					return nil, errors.New("Invalid binding")
				}
				bindings.Globals = false
				continue
@@ -625,11 +684,14 @@ func LoadConfigFromFile(root *string, sharedir string) (*AercConfig, error) {
			}
			bindings.Add(binding)
		}
		*group = MergeBindings(bindings, *group)
	}
	// Globals can't inherit from themselves
	config.Bindings.Global.Globals = false
	return config, nil
        
        if len(parts) == 1 {
            group.Base = MergeBindings(subgroup, bindings)
        } else {
            group.Account[strings.ToLower(parts[1])] = MergeBindings(subgroup, bindings)
        }
    }
    return group, nil   
}

// checkConfigPerms checks for too open permissions
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index d4de883..511864c 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -487,6 +487,21 @@ are:
*[terminal]*
	keybindings for terminal tabs

You may also configure account specific key bindings for each context:

*[context//accountname]*
	keybindings for this context and account, where *accountname* matches
	the name provided in *accounts.conf*

Example:
```
[messages//mailbox]
c = :cf path:mailbox/** and<space>

[compose::editor//mailbox2]
...
```

You may also configure global keybindings by placing them at the beginning of
the file, before specifying any context-specific sections. For each *key=value*
option specified, the _key_ is the keystrokes pressed (in order) to invoke this
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 6df0c95..d515536 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -182,26 +182,60 @@ func (aerc *Aerc) Draw(ctx *ui.Context) {
}

func (aerc *Aerc) getBindings() *config.KeyBindings {
    selectedAccountName := ""
    if aerc.SelectedAccount() != nil {
        selectedAccountName = strings.ToLower(aerc.SelectedAccount().acct.Name);
    }
	switch view := aerc.SelectedTab().(type) {
	case *AccountView:
		return aerc.conf.Bindings.MessageList
        acctBinds, ok := aerc.conf.Bindings.MessageList.Account[selectedAccountName]
        if !ok {
            return aerc.conf.Bindings.MessageList.Base
        } else {
            return config.MergeBindings(aerc.conf.Bindings.MessageList.Base, acctBinds)
        }
	case *AccountWizard:
		return aerc.conf.Bindings.AccountWizard
		return aerc.conf.Bindings.AccountWizard.Base
	case *Composer:
		switch view.Bindings() {
		case "compose::editor":
			return aerc.conf.Bindings.ComposeEditor
            acctBinds, ok := aerc.conf.Bindings.ComposeEditor.Account[selectedAccountName]
            if !ok {
			    return aerc.conf.Bindings.ComposeEditor.Base
            } else {
                return config.MergeBindings(aerc.conf.Bindings.ComposeEditor.Base, acctBinds)
            }
		case "compose::review":
			return aerc.conf.Bindings.ComposeReview
            acctBinds, ok := aerc.conf.Bindings.ComposeReview.Account[selectedAccountName]
            if !ok {
                return aerc.conf.Bindings.ComposeReview.Base
            } else {
                return config.MergeBindings(aerc.conf.Bindings.ComposeReview.Base, acctBinds)
            }
		default:
			return aerc.conf.Bindings.Compose
            acctBinds, ok := aerc.conf.Bindings.Compose.Account[selectedAccountName]
            if !ok {
                return aerc.conf.Bindings.Compose.Base
            } else {
                return config.MergeBindings(aerc.conf.Bindings.Compose.Base, acctBinds)
            }
		}
	case *MessageViewer:
		return aerc.conf.Bindings.MessageView
        acctBinds, ok := aerc.conf.Bindings.MessageView.Account[selectedAccountName]
        if !ok {
            return aerc.conf.Bindings.MessageView.Base
        } else {
                return config.MergeBindings(aerc.conf.Bindings.MessageView.Base, acctBinds)
        }
	case *Terminal:
		return aerc.conf.Bindings.Terminal
        return aerc.conf.Bindings.Terminal.Base
	default:
		return aerc.conf.Bindings.Global
        acctBinds, ok := aerc.conf.Bindings.Global.Account[selectedAccountName]
        if !ok {
            return aerc.conf.Bindings.Global.Base
        } else {
            return config.MergeBindings(aerc.conf.Bindings.Global.Base, acctBinds)
        }
	}
}

@@ -245,7 +279,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
		case config.BINDING_NOT_FOUND:
		}
		if bindings.Globals {
			result, strokes = aerc.conf.Bindings.Global.
			result, strokes = aerc.conf.Bindings.Global.Base.
				GetBinding(aerc.pendingKeys)
			switch result {
			case config.BINDING_FOUND:
@@ -261,7 +295,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
			exKey := bindings.ExKey
			if aerc.simulating > 0 {
				// Keybindings still use : even if you change the ex key
				exKey = aerc.conf.Bindings.Global.ExKey
				exKey = aerc.conf.Bindings.Global.Base.ExKey
			}
			if event.Key() == exKey.Key && event.Rune() == exKey.Rune {
				aerc.BeginExCommand("")
-- 
2.31.1
Details
Message ID
<20210516105554.awet7xpje5usal46@feather>
In-Reply-To
<20210511184135.56476-1-jonathan@jonnobrow.co.uk> (view parent)
DKIM signature
pass
Download raw message
Hi,
Thank you for the patch.
I like the functionality this wants to implement, however I think this patch
could be improved a bit.

For starters, it has severe whitespace issues, please run go fmt on anything you
send to the list. Also, do remove debug statements please ;)

> +    fmt.Printf("%:v", config.Bindings.MessageList.Account)

Second, could we structure this a bit like the styles? It's the same problem
and I think a bit cleaner than what you are doing.

In my opinion it would be nice if we don't invent yet another notation, meaning
reuse the same contextual logic we already have in the aerc-config(5) contextual
UI config section.

In other words, separate context with ":" (yes, the :: section separator is a
bit annoying, but we should be able to manage easily), followed by account=$something.

That way we can add more contexts in the future and it is symmetrical.

What do you think?

Cheers,
Reto
Jonathan Bartlett <jsb@jonnobrow.co.uk>
Details
Message ID
<CBEMQCV3FMNA.20O7CRMTBQWFR@jblp>
In-Reply-To
<20210516105554.awet7xpje5usal46@feather> (view parent)
DKIM signature
pass
Download raw message
Hi Reto,

> For starters, it has severe whitespace issues, please run go fmt on
> anything you
> send to the list. Also, do remove debug statements please ;)

Yeah I can definitely do this, I am reasonably new to go development and
therefore just getting used to the best practices and setting up my editor
to do formatting and stuff.

> In other words, separate context with ":" (yes, the :: section separator
> is a
> bit annoying, but we should be able to manage easily), followed by
> account=$something.

I will switch to this too, I completely agree that it makes more sense.

Thanks for your feedback, I will make those changes and submit an updated
patch in the coming days.

Jonathan.
Reply to thread Export thread (mbox)