summaryrefslogtreecommitdiff
path: root/modules/mu4e-org-contacts-integration.el
blob: 7fe89389155bc7c9ecc2ef1f46f482834f6be961 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
;;; mu4e-org-contacts-integration.el --- Integrate org-contacts with mu4e completion -*- lexical-binding: t; -*-
;; author: Craig Jennings <c@cjennings.net>

;;; Commentary:
;; This module provides seamless integration between org-contacts and mu4e's
;; email composition, enabling automatic contact completion in email fields.

;;; Code:

(require 'mu4e)
(require 'org-contacts)

;; ---------------------- Completion at Point Function -------------------------

(defun cj/org-contacts-completion-at-point ()
  "Provide completion-at-point function for org-contacts emails.
This function is designed to work with mu4e's compose buffers."
  (when (and (derived-mode-p 'mu4e-compose-mode 'org-msg-edit-mode)
             (mail-abbrev-in-expansion-header-p))
    (let* ((end (point))
           (start (save-excursion
                    (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*" nil t)
                    (goto-char (match-end 0))
                    (point)))
           (initial (buffer-substring-no-properties start end))
           (contacts (cj/get-all-contact-emails)))
      (when contacts
        (list start end
              (completion-table-dynamic
               (lambda (_)
                 contacts))
              :exclusive 'no  ; Allow other completion sources
              :annotation-function
              (lambda (s)
                (when-let* ((email-match (string-match "<\\([^>]+\\)>" s))
                            (email (match-string 1 s)))
                  (propertize (format " [%s]" (file-name-nondirectory contacts-file))
                              'face 'completions-annotations))))))))

;; ---------------------- Smart TAB Completion -------------------------

(defun cj/mu4e-org-contacts-tab-complete ()
  "Smart TAB completion for mu4e compose buffers.
In email header fields (To, Cc, Bcc), complete using org-contacts.
Elsewhere, perform the default TAB action."
  (interactive)
  (cond
   ;; In email header fields, use completion-at-point
   ((mail-abbrev-in-expansion-header-p)
    (if (and (boundp 'completion-in-region-mode) completion-in-region-mode)
        ;; If we're already in completion mode, cycle through candidates
        (completion-at-point)
      ;; Start new completion
      (completion-at-point)))
   ;; In org-msg-edit-mode body, use org-cycle
   ((and (eq major-mode 'org-msg-edit-mode)
         (not (mail-abbrev-in-expansion-header-p)))
    (org-cycle))
   ;; Default: indent
   (t (indent-for-tab-command))))

;; ---------------------- Comma-triggered Completion -------------------------

(defun cj/mu4e-org-contacts-comma-complete ()
  "Insert comma and optionally trigger contact completion.
In email header fields, insert comma with space and offer completion.
Elsewhere, just insert comma."
  (interactive)
  (if (mail-abbrev-in-expansion-header-p)
      (progn
        (insert ", ")  ; Insert comma with space
        ;; Trigger completion immediately if there's no text after the comma
        (when (looking-at-p "\\s-*$")
          (completion-at-point)))
    (insert ",")))

;; ---------------------- Direct Insertion (Alternative) -------------------------

(defun cj/mu4e-org-contacts-insert-email ()
  "Directly insert a contact email using completing-read.
This bypasses the completion-at-point system for direct selection."
  (interactive)
  (when (mail-abbrev-in-expansion-header-p)
    (let* ((contacts (cj/get-all-contact-emails))
           (selected (completing-read "Contact: " contacts nil t)))
      ;; If we're not at the beginning of a field, check if we need a comma
      (when (and (not (save-excursion
                        (skip-chars-backward " \t")
                        (or (bolp) (looking-back "[:,]" 1))))
                 (not (looking-at-p "\\s-*$")))
        (insert ", "))
      (insert selected))))

;; ---------------------- Setup Functions -------------------------

(defun cj/mu4e-org-contacts-setup-completion ()
  "Setup org-contacts completion for mu4e compose buffers."
  ;; Add our completion function with high priority
  (add-hook 'completion-at-point-functions
            #'cj/org-contacts-completion-at-point
            -10 t)  ; High priority, buffer-local
  
  ;; Setup completion behavior
  (setq-local completion-ignore-case t)
  (setq-local completion-cycle-threshold 7)
  
  ;; Add substring matching if not already present
  (unless (member 'substring completion-styles)
    (setq-local completion-styles
                (append '(substring) completion-styles))))

(defun cj/mu4e-org-contacts-setup-keybindings ()
  "Setup keybindings for org-contacts completion in compose buffers."
  ;; TAB for smart completion
  (local-set-key (kbd "TAB") #'cj/mu4e-org-contacts-tab-complete)
  (local-set-key (kbd "<tab>") #'cj/mu4e-org-contacts-tab-complete)
  
  ;; Comma for comma-triggered completion
  (local-set-key (kbd ",") #'cj/mu4e-org-contacts-comma-complete)
  
  ;; Optional: Direct insertion binding
  (local-set-key (kbd "C-c e") #'cj/mu4e-org-contacts-insert-email))

;; ---------------------- Mode Hooks -------------------------

(defun cj/mu4e-org-contacts-compose-setup ()
  "Setup function to be called in mu4e compose mode hooks."
  (cj/mu4e-org-contacts-setup-completion)
  (cj/mu4e-org-contacts-setup-keybindings))

;; ---------------------- Activation -------------------------

(defun cj/activate-mu4e-org-contacts-integration ()
  "Activate org-contacts integration with mu4e email composition."
  (interactive)
  
  ;; Ensure mu4e's built-in completion is disabled
  (setq mu4e-compose-complete-addresses nil)
  
  ;; Setup hooks for mu4e-compose-mode
  (add-hook 'mu4e-compose-mode-hook #'cj/mu4e-org-contacts-compose-setup)
  
  ;; Setup hooks for org-msg-edit-mode (HTML email composition)
  (with-eval-after-load 'org-msg
    (add-hook 'org-msg-edit-mode-hook #'cj/mu4e-org-contacts-compose-setup))
  
  ;; Remove any existing mu4e completion setup
  (remove-hook 'mu4e-compose-mode-hook #'mu4e--compose-setup-completion)
  
  (message "mu4e org-contacts integration activated"))

(defun cj/deactivate-mu4e-org-contacts-integration ()
  "Deactivate org-contacts integration with mu4e email composition."
  (interactive)
  
  ;; Remove our hooks
  (remove-hook 'mu4e-compose-mode-hook #'cj/mu4e-org-contacts-compose-setup)
  (remove-hook 'org-msg-edit-mode-hook #'cj/mu4e-org-contacts-compose-setup)
  
  ;; Re-enable mu4e's built-in completion if desired
  (setq mu4e-compose-complete-addresses t)
  (add-hook 'mu4e-compose-mode-hook #'mu4e--compose-setup-completion)
  
  (message "mu4e org-contacts integration deactivated"))

(provide 'mu4e-org-contacts-integration)
;;; mu4e-org-contacts-integration.el ends here