From ba9d79fd2d6d17b8d2ec940697cab2348293c510 Mon Sep 17 00:00:00 2001
From: Bence Ferdinandy <bence@ferdinandy.com>
Date: Mon, 5 Sep 2022 09:43:17 +0200
Subject: [PATCH] commands: add zoxide support via :z

zoxide is a command line utility, supported by many CLI programs. Enable
zoxide support via the :z command which is a drop-in replacement for :cd
(and calls ChangeDirectory in the background), but also manages adding
paths to and querying from the zoxide database. The command is not
registered if zoxide is not on $PATH.

Link: https://github.com/ajeetdsouza/zoxide
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
Acked-by: Robin Jarry <robin@jarry.cc>
---
 CHANGELOG.md   |  1 +
 commands/z.go  | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++
 doc/aerc.1.scd |  4 +++
 3 files changed, 90 insertions(+)
 create mode 100644 commands/z.go

diff --git a/CHANGELOG.md b/CHANGELOG.md
index af875b9..49f4470 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 ### Added
 
 - Support for bindings with the Alt modifier.
+- Zoxide support with `:z`.
 
 ### Fixed
 
diff --git a/commands/z.go b/commands/z.go
new file mode 100644
index 0000000..ca982ba
--- /dev/null
+++ b/commands/z.go
@@ -0,0 +1,85 @@
+package commands
+
+import (
+	"errors"
+	"os"
+	"os/exec"
+	"strings"
+
+	"git.sr.ht/~rjarry/aerc/widgets"
+)
+
+type Zoxide struct{}
+
+func ZoxideAdd(arg string) error {
+	zargs := []string{"add", arg}
+	cmd := exec.Command("zoxide", zargs...)
+	err := cmd.Run()
+	return err
+}
+
+func ZoxideQuery(args []string) (string, error) {
+	zargs := append([]string{"query"}, args[1:]...)
+	cmd := exec.Command("zoxide", zargs...)
+	res, err := cmd.Output()
+	return strings.TrimSuffix(string(res), "\n"), err
+}
+
+func init() {
+	_, err := exec.LookPath("zoxide")
+	if err == nil {
+		register(Zoxide{})
+	}
+}
+
+func (Zoxide) Aliases() []string {
+	return []string{"z"}
+}
+
+func (Zoxide) Complete(aerc *widgets.Aerc, args []string) []string {
+	return ChangeDirectory{}.Complete(aerc, args)
+}
+
+// Execute calls zoxide add and query and delegates actually changing the
+// directory to ChangeDirectory
+func (Zoxide) Execute(aerc *widgets.Aerc, args []string) error {
+	if len(args) < 1 {
+		return errors.New("Usage: z [directory or zoxide query]")
+	}
+	target := strings.Join(args[1:], " ")
+	switch target {
+	case "":
+		return ChangeDirectory{}.Execute(aerc, args)
+	case "-":
+		if previousDir != "" {
+			err := ZoxideAdd(previousDir)
+			if err != nil {
+				return err
+			}
+		}
+		return ChangeDirectory{}.Execute(aerc, args)
+	default:
+		_, err := os.Stat(target)
+		if err != nil {
+			// not a file, assume zoxide query
+			res, err := ZoxideQuery(args)
+			if err != nil {
+				return errors.New("zoxide: no match found")
+			} else {
+				err := ZoxideAdd(res)
+				if err != nil {
+					return err
+				}
+				return ChangeDirectory{}.Execute(aerc, []string{"z", res})
+			}
+
+		} else {
+			err := ZoxideAdd(target)
+			if err != nil {
+				return err
+			}
+			return ChangeDirectory{}.Execute(aerc, args)
+		}
+
+	}
+}
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 8882795..40b14d8 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -65,6 +65,10 @@ These commands work in any context.
 *cd* <directory>
 	Changes aerc's current working directory.
 
+*z* <directory or zoxide query>
+	Changes aerc's current working directory using zoxide. If zoxide is not on
+	*$PATH*., the command will not be registered.
+
 *change-tab* [+|-]<tab name or index>
 	Changes the focus to the tab with the given name. If a number is given,
 	it's treated as an index. If + or - is specified, the number is interpreted