diff options
author | Jason Cox <me@jasoncarloscox.com> | 2024-02-17 12:34:23 -0500 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2024-02-26 09:30:35 +0100 |
commit | f3a61a341c81d70b51d91a71e1b0909acdcdb8f4 (patch) | |
tree | 58a3156dc63500fd4e0cd0034a59e86cdbb6a3cb | |
parent | d8d5fc8d31f358c6425dfa18a9f8d2767bc6df40 (diff) | |
download | aerc-f3a61a341c81d70b51d91a71e1b0909acdcdb8f4.tar.gz |
commands: add :query to create named notmuch dirs
The current :cf command can be used to create folders for arbitrary
notmuch queries. These folders use the query as their namee. In some
cases, though, it's useful to give a more human-readable name. Create a
new :query command to allow doing so.
The :query command accepts an optional -n flag to specify a name. The
remaining arguments are interpreted verbatim as a notmuch query. If no
name is specified, the query itself is used as the name.
For example, to create a new folder with the full thread of the current
message, named by its subject, run the following command:
:query -n "{{.SubjectBase}}" thread:"{mid:{{.MessageId}}}"
:query could have been implemented as an additional flag to :cf. Giving
a name to the created folder would make the smantics of :cf strange,
though. For example, to create a named query folder, one would use
:cf -n <name> <query>. This syntax feels odd; the name of the folder
seems like it ought to be the positional argument of the change folder
command. Alternatively, the usage could be :cf -q <query> <name>, but
this feels wrong as well: the query, which is provided as a positional
parameter when no name is specified, becomes a flag parameter when a
name is specified. What's more, both of these potential usages add a
notmuch-specific flag to an otherwise general command. Creating a new
command feels cleaner. Perhaps the current query functionality of the
:cf command could eventually be deprecated to remove the duplicate
functionality and keep :cf limited to changing to existing folders.
Changelog-added: Create notmuch named queries with the `:query`
command.
Signed-off-by: Jason Cox <me@jasoncarloscox.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Robin Jarry <robin@jarry.cc>
-rw-r--r-- | app/dirlist.go | 7 | ||||
-rw-r--r-- | app/dirtree.go | 6 | ||||
-rw-r--r-- | commands/account/cf.go | 44 | ||||
-rw-r--r-- | commands/account/mkdir.go | 2 | ||||
-rw-r--r-- | commands/account/query.go | 67 | ||||
-rw-r--r-- | commands/account/rmdir.go | 4 | ||||
-rw-r--r-- | doc/aerc.1.scd | 12 | ||||
-rw-r--r-- | worker/notmuch/eventhandlers.go | 7 | ||||
-rw-r--r-- | worker/notmuch/worker.go | 60 | ||||
-rw-r--r-- | worker/types/messages.go | 1 |
10 files changed, 165 insertions, 45 deletions
diff --git a/app/dirlist.go b/app/dirlist.go index 898b8130..1fc2dd63 100644 --- a/app/dirlist.go +++ b/app/dirlist.go @@ -24,7 +24,7 @@ type DirectoryLister interface { Selected() string Select(string) - Open(string, time.Duration, func(types.WorkerMessage)) + Open(string, string, time.Duration, func(types.WorkerMessage)) Update(types.WorkerMessage) List() []string @@ -175,10 +175,10 @@ func (dirlist *DirectoryList) ExpandFolder() { } func (dirlist *DirectoryList) Select(name string) { - dirlist.Open(name, dirlist.UiConfig(name).DirListDelay, nil) + dirlist.Open(name, "", dirlist.UiConfig(name).DirListDelay, nil) } -func (dirlist *DirectoryList) Open(name string, delay time.Duration, +func (dirlist *DirectoryList) Open(name string, query string, delay time.Duration, cb func(types.WorkerMessage), ) { dirlist.selecting = name @@ -193,6 +193,7 @@ func (dirlist *DirectoryList) Open(name string, delay time.Duration, dirlist.worker.PostAction(&types.OpenDirectory{ Context: ctx, Directory: name, + Query: query, }, func(msg types.WorkerMessage) { switch msg := msg.(type) { diff --git a/app/dirtree.go b/app/dirtree.go index 53ab4aad..b735dacd 100644 --- a/app/dirtree.go +++ b/app/dirtree.go @@ -239,10 +239,10 @@ func (dt *DirectoryTree) Select(name string) { if name == "" { return } - dt.Open(name, dt.UiConfig(name).DirListDelay, nil) + dt.Open(name, "", dt.UiConfig(name).DirListDelay, nil) } -func (dt *DirectoryTree) Open(name string, delay time.Duration, cb func(types.WorkerMessage)) { +func (dt *DirectoryTree) Open(name string, query string, delay time.Duration, cb func(types.WorkerMessage)) { if name == "" { return } @@ -252,7 +252,7 @@ func (dt *DirectoryTree) Open(name string, delay time.Duration, cb func(types.Wo } else { dt.reindex(name) } - dt.DirectoryList.Open(name, delay, func(msg types.WorkerMessage) { + dt.DirectoryList.Open(name, query, delay, func(msg types.WorkerMessage) { if cb != nil { cb(msg) } diff --git a/commands/account/cf.go b/commands/account/cf.go index 2f32e8bc..0f818006 100644 --- a/commands/account/cf.go +++ b/commands/account/cf.go @@ -103,35 +103,39 @@ func (c ChangeFolder) Execute([]string) error { } finalize := func(msg types.WorkerMessage) { - // As we're waiting for the worker to report status we must run - // the rest of the actions in this callback. - switch msg := msg.(type) { - case *types.Error: - app.PushError(msg.Error.Error()) - case *types.Done: - curAccount := app.SelectedAccount() - previous := curAccount.Directories().Selected() - history[curAccount.Name()] = previous - // reset store filtering if we switched folders - store := acct.Store() - if store != nil { - store.ApplyClear() - acct.SetStatus(state.SearchFilterClear()) - } - // focus account tab - acct.Select() - } + handleDirOpenResponse(acct, msg) } if target == "-" { if dir, ok := history[acct.Name()]; ok { - acct.Directories().Open(dir, 0*time.Second, finalize) + acct.Directories().Open(dir, "", 0*time.Second, finalize) } else { return errors.New("No previous folder to return to") } } else { - acct.Directories().Open(target, 0*time.Second, finalize) + acct.Directories().Open(target, "", 0*time.Second, finalize) } return nil } + +func handleDirOpenResponse(acct *app.AccountView, msg types.WorkerMessage) { + // As we're waiting for the worker to report status we must run + // the rest of the actions in this callback. + switch msg := msg.(type) { + case *types.Error: + app.PushError(msg.Error.Error()) + case *types.Done: + curAccount := app.SelectedAccount() + previous := curAccount.Directories().Selected() + history[curAccount.Name()] = previous + // reset store filtering if we switched folders + store := acct.Store() + if store != nil { + store.ApplyClear() + acct.SetStatus(state.SearchFilterClear()) + } + // focus account tab + acct.Select() + } +} diff --git a/commands/account/mkdir.go b/commands/account/mkdir.go index c08c6d4b..9776e8f7 100644 --- a/commands/account/mkdir.go +++ b/commands/account/mkdir.go @@ -53,7 +53,7 @@ func (m MakeDir) Execute(args []string) error { case *types.Done: app.PushStatus("Directory created.", 10*time.Second) history[acct.Name()] = previous - acct.Directories().Open(m.Folder, 0, nil) + acct.Directories().Open(m.Folder, "", 0, nil) case *types.Error: app.PushError(msg.Error.Error()) } diff --git a/commands/account/query.go b/commands/account/query.go new file mode 100644 index 00000000..f116d405 --- /dev/null +++ b/commands/account/query.go @@ -0,0 +1,67 @@ +package account + +import ( + "errors" + "reflect" + "time" + + "git.sr.ht/~rjarry/aerc/app" + "git.sr.ht/~rjarry/aerc/commands" + "git.sr.ht/~rjarry/aerc/worker/handlers" + "git.sr.ht/~rjarry/aerc/worker/types" +) + +type Query struct { + Account string `opt:"-a" complete:"CompleteAccount"` + Name string `opt:"-n"` + Query string `opt:"..."` +} + +func init() { + commands.Register(Query{}) +} + +func (Query) Context() commands.CommandContext { + return commands.ACCOUNT +} + +func (Query) Aliases() []string { + return []string{"query"} +} + +func (Query) CompleteAccount(arg string) []string { + return commands.FilterList(app.AccountNames(), arg, commands.QuoteSpace) +} + +func (q Query) Execute([]string) error { + var acct *app.AccountView + + if q.Account == "" { + acct = app.SelectedAccount() + if acct == nil { + return errors.New("No account selected") + } + } else { + var err error + acct, err = app.Account(q.Account) + if err != nil { + return err + } + } + + notmuch, _ := handlers.GetHandlerForScheme("notmuch", new(types.Worker)) + if reflect.TypeOf(notmuch) != reflect.TypeOf(acct.Worker().Backend) { + return errors.New(":query is only available for notmuch accounts") + } + + finalize := func(msg types.WorkerMessage) { + handleDirOpenResponse(acct, msg) + } + + name := q.Name + if name == "" { + name = q.Query + } + acct.Directories().Open(name, q.Query, 0*time.Second, finalize) + return nil +} diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go index 00366bd0..48ea3581 100644 --- a/commands/account/rmdir.go +++ b/commands/account/rmdir.go @@ -89,9 +89,9 @@ func (r RemoveDir) Execute(args []string) error { return errors.New("No directory to move to afterwards!") } - reopenCurrentDir := func() { acct.Directories().Open(curDir, 0, nil) } + reopenCurrentDir := func() { acct.Directories().Open(curDir, "", 0, nil) } - acct.Directories().Open(newDir, 0, func(msg types.WorkerMessage) { + acct.Directories().Open(newDir, "", 0, func(msg types.WorkerMessage) { switch msg.(type) { case *types.Done: break diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd index 91e75d60..2a20213a 100644 --- a/doc/aerc.1.scd +++ b/doc/aerc.1.scd @@ -596,6 +596,18 @@ message list, the message in the message viewer, etc). *:prev-result* Selects the next or previous search result. +*:query* [*-a* _<account>_] [*-n* _name_] _<notmuch query>_ + Create a virtual folder using the specified top-level notmuch query. This + command is exclusive to the notmuch backend. + + *-a* _<account>_ + Change to _<folder>_ of _<account>_ and focus its corresponding + tab. + + *-n* _<name>_ + Specify the display name for the virtual folder. If not provided, + _<notmuch query>_ is used as the display name. + *:search* [_<options>_] _<terms>_... Searches the current folder for messages matching the given set of conditions. The search syntax is dependent on the underlying backend. diff --git a/worker/notmuch/eventhandlers.go b/worker/notmuch/eventhandlers.go index be01a71b..76517953 100644 --- a/worker/notmuch/eventhandlers.go +++ b/worker/notmuch/eventhandlers.go @@ -55,6 +55,13 @@ func (w *worker) updateDirCounts() error { }, nil) } + for name, query := range w.dynamicNameQueryMap { + w.w.PostMessage(&types.DirectoryInfo{ + Info: w.getDirectoryInfo(name, query), + Refetch: w.query == query, + }, nil) + } + return nil } diff --git a/worker/notmuch/worker.go b/worker/notmuch/worker.go index c5792709..aa2da391 100644 --- a/worker/notmuch/worker.go +++ b/worker/notmuch/worker.go @@ -27,6 +27,7 @@ import ( "git.sr.ht/~rjarry/aerc/worker/lib" notmuch "git.sr.ht/~rjarry/aerc/worker/notmuch/lib" "git.sr.ht/~rjarry/aerc/worker/types" + "github.com/emersion/go-maildir" ) func init() { @@ -42,6 +43,7 @@ type worker struct { currentQueryName string queryMapOrder []string nameQueryMap map[string]string + dynamicNameQueryMap map[string]string store *lib.MaildirStore maildirAccountPath string db *notmuch.DB @@ -70,6 +72,7 @@ func NewWorker(w *types.Worker) (types.Backend, error) { Sort: true, Thread: true, }, + dynamicNameQueryMap: make(map[string]string), }, nil } @@ -284,6 +287,17 @@ func (w *worker) handleListDirectories(msg *types.ListDirectories) error { }, }, nil) } + + for name := range w.dynamicNameQueryMap { + w.w.PostMessage(&types.Directory{ + Message: types.RespondTo(msg), + Dir: &models.Directory{ + Name: name, + Role: models.QueryRole, + }, + }, nil) + } + // Update dir counts when listing directories err := w.updateDirCounts() if err != nil { @@ -318,14 +332,15 @@ func (w *worker) handleOpenDirectory(msg *types.OpenDirectory) error { if msg.Context.Err() != nil { return context.Canceled } - w.w.Tracef("opening %s", msg.Directory) + w.w.Tracef("opening %s with query %s", msg.Directory, msg.Query) - var isDynamicFolder bool + var exists bool q := "" if w.store != nil { folders, _ := w.store.FolderMap() - dir, ok := folders[msg.Directory] - if ok { + var dir maildir.Dir + dir, exists = folders[msg.Directory] + if exists { folder := filepath.Join(w.maildirAccountPath, msg.Directory) q = fmt.Sprintf("folder:%s", strconv.Quote(folder)) if err := w.processNewMaildirFiles(string(dir)); err != nil { @@ -334,19 +349,26 @@ func (w *worker) handleOpenDirectory(msg *types.OpenDirectory) error { } } if q == "" { - var ok bool - q, ok = w.nameQueryMap[msg.Directory] - if !ok { + q, exists = w.nameQueryMap[msg.Directory] + if !exists { + q, exists = w.dynamicNameQueryMap[msg.Directory] + } + } + if !exists { + q = msg.Query + if q == "" { q = msg.Directory - isDynamicFolder = true - w.w.PostMessage(&types.Directory{ - Message: types.RespondTo(msg), - Dir: &models.Directory{ - Name: q, - Role: models.QueryRole, - }, - }, nil) } + w.dynamicNameQueryMap[msg.Directory] = q + w.w.PostMessage(&types.Directory{ + Message: types.RespondTo(msg), + Dir: &models.Directory{ + Name: msg.Directory, + Role: models.QueryRole, + }, + }, nil) + } else if msg.Query != "" && msg.Query != q { + return errors.New("cannot use existing folder name for new query") } w.query = q w.currentQueryName = msg.Directory @@ -355,7 +377,7 @@ func (w *worker) handleOpenDirectory(msg *types.OpenDirectory) error { Info: w.getDirectoryInfo(msg.Directory, w.query), Message: types.RespondTo(msg), }, nil) - if isDynamicFolder { + if !exists { w.w.PostMessage(&types.DirectoryInfo{ Info: w.getDirectoryInfo(msg.Directory, w.query), Message: types.RespondTo(msg), @@ -921,6 +943,12 @@ func (w *worker) handleRemoveDirectory(msg *types.RemoveDirectory) error { return errUnsupported } + if _, ok := w.dynamicNameQueryMap[msg.Directory]; ok { + delete(w.dynamicNameQueryMap, msg.Directory) + w.done(msg) + return nil + } + if w.store == nil { w.done(msg) return nil diff --git a/worker/types/messages.go b/worker/types/messages.go index 7ebb3aae..90d3d7bb 100644 --- a/worker/types/messages.go +++ b/worker/types/messages.go @@ -101,6 +101,7 @@ type OpenDirectory struct { Message Context context.Context Directory string + Query string } type FetchDirectoryContents struct { |