about summary refs log tree commit diff stats
path: root/completer/completer.go
diff options
context:
space:
mode:
Diffstat (limited to 'completer/completer.go')
-rw-r--r--completer/completer.go43
1 files changed, 28 insertions, 15 deletions
diff --git a/completer/completer.go b/completer/completer.go
index bc6c96f..251658e 100644
--- a/completer/completer.go
+++ b/completer/completer.go
@@ -8,6 +8,7 @@ import (
 	"mime"
 	"net/mail"
 	"os/exec"
+	"regexp"
 	"strings"
 
 	"github.com/google/shlex"
@@ -28,8 +29,8 @@ type Completer struct {
 }
 
 // A CompleteFunc accepts a string to be completed and returns a slice of
-// possible completions.
-type CompleteFunc func(string) []string
+// completions candidates with a prefix to prepend to the chosen candidate
+type CompleteFunc func(string) ([]string, string)
 
 // New creates a new Completer with the specified address book command.
 func New(addressBookCmd string, errHandler func(error), logger *log.Logger) *Completer {
@@ -50,13 +51,13 @@ func (c *Completer) ForHeader(h string) CompleteFunc {
 			return nil
 		}
 		// wrap completeAddress in an error handler
-		return func(s string) []string {
-			completions, err := c.completeAddress(s)
+		return func(s string) ([]string, string) {
+			completions, prefix, err := c.completeAddress(s)
 			if err != nil {
 				c.handleErr(err)
-				return []string{}
+				return []string{}, ""
 			}
-			return completions
+			return completions, prefix
 		}
 	}
 	return nil
@@ -73,23 +74,24 @@ func isAddressHeader(h string) bool {
 }
 
 // completeAddress uses the configured address book completion command to fetch
-// completions for the specified string, returning a slice of completions or an
-// error.
-func (c *Completer) completeAddress(s string) ([]string, error) {
-	cmd, err := c.getAddressCmd(s)
+// completions for the specified string, returning a slice of completions and
+// a prefix to be prepended to the selected completion, or an error.
+func (c *Completer) completeAddress(s string) ([]string, string, error) {
+	prefix, candidate := c.parseAddress(s)
+	cmd, err := c.getAddressCmd(candidate)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 	stdout, err := cmd.StdoutPipe()
 	if err != nil {
-		return nil, fmt.Errorf("stdout: %v", err)
+		return nil, "", fmt.Errorf("stdout: %v", err)
 	}
 	if err := cmd.Start(); err != nil {
-		return nil, fmt.Errorf("cmd start: %v", err)
+		return nil, "", fmt.Errorf("cmd start: %v", err)
 	}
 	completions, err := readCompletions(stdout)
 	if err != nil {
-		return nil, fmt.Errorf("read completions: %v", err)
+		return nil, "", fmt.Errorf("read completions: %v", err)
 	}
 
 	// Wait returns an error if the exit status != 0, which some completion
@@ -100,7 +102,18 @@ func (c *Completer) completeAddress(s string) ([]string, error) {
 		c.logger.Printf("completion error: %v", err)
 	}
 
-	return completions, nil
+	return completions, prefix, nil
+}
+
+// parseAddress will break an address header into a prefix (containing
+// the already valid addresses) and an input for completion
+func (c *Completer) parseAddress(s string) (string, string) {
+	pattern := regexp.MustCompile(`^(.*),\s+([^,]*)$`)
+	matches := pattern.FindStringSubmatch(s)
+	if matches == nil {
+		return "", s
+	}
+	return matches[1] + ", ", matches[2]
 }
 
 // getAddressCmd constructs an exec.Cmd based on the configured command and