Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 1 | ;;; Clang Code-Completion minor mode, for use with C/Objective-C/C++. |
| 2 | |
| 3 | ;;; Commentary: |
| 4 | |
| 5 | ;; This minor mode uses Clang's command line interface for code |
| 6 | ;; completion to provide code completion results for C, Objective-C, |
| 7 | ;; and C++ source files. When enabled, Clang will provide |
| 8 | ;; code-completion results in a secondary buffer based on the code |
| 9 | ;; being typed. For example, after typing "struct " (triggered via the |
| 10 | ;; space), Clang will provide the names of all structs visible from |
| 11 | ;; the current scope. After typing "p->" (triggered via the ">"), |
| 12 | ;; Clang will provide the names of all of the members of whatever |
| 13 | ;; class/struct/union "p" points to. Note that this minor mode isn't |
| 14 | ;; meant for serious use: it is meant to help experiment with code |
| 15 | ;; completion based on Clang. It needs your help to make it better! |
| 16 | ;; |
| 17 | ;; To use the Clang code completion mode, first make sure that the |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 18 | ;; "clang" variable below refers to the "clang" executable, |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 19 | ;; which is typically installed in libexec/. Then, place |
| 20 | ;; clang-completion-mode.el somewhere in your Emacs load path. You can |
| 21 | ;; add a new load path to Emacs by adding some like the following to |
| 22 | ;; your .emacs: |
| 23 | ;; |
| 24 | ;; (setq load-path (cons "~/.emacs.d" load-path)) |
| 25 | ;; |
| 26 | ;; Then, use |
| 27 | ;; |
| 28 | ;; M-x load-library |
| 29 | ;; |
| 30 | ;; to load the library in your Emacs session or add the following to |
| 31 | ;; your .emacs to always load this mode (not recommended): |
| 32 | ;; |
| 33 | ;; (load-library "clang-completion-mode") |
| 34 | ;; |
| 35 | ;; Finally, to try Clang-based code completion in a particular buffer, |
| 36 | ;; use M-x clang-completion-mode. When "Clang-CC" shows up in the mode |
| 37 | ;; line, Clang's code-completion is enabled. |
| 38 | ;; |
| 39 | ;; Clang's code completion is based on parsing the complete source |
| 40 | ;; file up to the point where the cursor is located. Therefore, Clang |
| 41 | ;; needs all of the various compilation flags (include paths, dialect |
| 42 | ;; options, etc.) to provide code-completion results. Currently, these |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 43 | ;; need to be placed into the clang-flags variable in a format |
| 44 | ;; acceptable to clang. This is a hack: patches are welcome to |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 45 | ;; improve the interface between this Emacs mode and Clang! |
| 46 | ;; |
| 47 | |
| 48 | ;;; Code: |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 49 | ;;; The clang executable |
| 50 | (defcustom clang "clang" |
| 51 | "The location of the Clang compiler executable" |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 52 | :type 'file |
| 53 | :group 'clang-completion-mode) |
| 54 | |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 55 | ;;; Extra compilation flags to pass to clang. |
| 56 | (defcustom clang-flags "" |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 57 | "Extra flags to pass to the Clang executable. |
| 58 | This variable will typically contain include paths, e.g., -I~/MyProject." |
| 59 | :type 'string |
| 60 | :group 'clang-completion-mode) |
| 61 | |
| 62 | ;;; The prefix header to use with Clang code completion. |
| 63 | (setq clang-completion-prefix-header "") |
| 64 | |
| 65 | ;;; The substring we will use to filter completion results |
| 66 | (setq clang-completion-substring "") |
| 67 | |
| 68 | ;;; The current completion buffer |
| 69 | (setq clang-completion-buffer nil) |
| 70 | |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 71 | (setq clang-result-string "") |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 72 | |
| 73 | ;;; Compute the current line in the buffer |
| 74 | (defun current-line () |
| 75 | "Return the vertical position of point..." |
| 76 | (+ (count-lines (point-min) (point)) |
| 77 | (if (= (current-column) 0) 1 0) |
| 78 | -1)) |
| 79 | |
| 80 | ;;; Set the Clang prefix header |
| 81 | (defun clang-prefix-header () |
| 82 | (interactive) |
| 83 | (setq clang-completion-prefix-header |
| 84 | (read-string "Clang prefix header> " "" clang-completion-prefix-header |
| 85 | ""))) |
| 86 | |
| 87 | ;; Process "filter" that keeps track of the code-completion results |
| 88 | ;; produced. We store all of the results in a string, then the |
| 89 | ;; sentinel processes the entire string at once. |
| 90 | (defun clang-completion-stash-filter (proc string) |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 91 | (setq clang-result-string (concat clang-result-string string))) |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 92 | |
| 93 | ;; Filter the given list based on a predicate. |
| 94 | (defun filter (condp lst) |
| 95 | (delq nil |
| 96 | (mapcar (lambda (x) (and (funcall condp x) x)) lst))) |
| 97 | |
| 98 | ;; Determine whether |
| 99 | (defun is-completion-line (line) |
| 100 | (or (string-match "OVERLOAD:" line) |
| 101 | (string-match (concat "COMPLETION: " clang-completion-substring) line))) |
| 102 | |
| 103 | (defun clang-completion-display (buffer) |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 104 | (let* ((all-lines (split-string clang-result-string "\n")) |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 105 | (completion-lines (filter 'is-completion-line all-lines))) |
| 106 | (if (consp completion-lines) |
| 107 | (progn |
| 108 | ;; Erase the process buffer |
| 109 | (let ((cur (current-buffer))) |
| 110 | (set-buffer buffer) |
| 111 | (goto-char (point-min)) |
| 112 | (erase-buffer) |
| 113 | (set-buffer cur)) |
| 114 | |
| 115 | ;; Display the process buffer |
| 116 | (display-buffer buffer) |
| 117 | |
| 118 | ;; Insert the code-completion string into the process buffer. |
| 119 | (with-current-buffer buffer |
| 120 | (insert (mapconcat 'identity completion-lines "\n"))) |
| 121 | )))) |
| 122 | |
| 123 | ;; Process "sentinal" that, on successful code completion, replaces the |
| 124 | ;; contents of the code-completion buffer with the new code-completion results |
| 125 | ;; and ensures that the buffer is visible. |
| 126 | (defun clang-completion-sentinel (proc event) |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 127 | (let* ((all-lines (split-string clang-result-string "\n")) |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 128 | (completion-lines (filter 'is-completion-line all-lines))) |
| 129 | (if (consp completion-lines) |
| 130 | (progn |
| 131 | ;; Erase the process buffer |
| 132 | (let ((cur (current-buffer))) |
| 133 | (set-buffer (process-buffer proc)) |
| 134 | (goto-char (point-min)) |
| 135 | (erase-buffer) |
| 136 | (set-buffer cur)) |
| 137 | |
| 138 | ;; Display the process buffer |
| 139 | (display-buffer (process-buffer proc)) |
| 140 | |
| 141 | ;; Insert the code-completion string into the process buffer. |
| 142 | (with-current-buffer (process-buffer proc) |
| 143 | (insert (mapconcat 'identity completion-lines "\n"))) |
| 144 | )))) |
| 145 | |
| 146 | (defun clang-complete () |
John McCall | 7ae4373 | 2010-01-13 06:44:51 +0000 | [diff] [blame] | 147 | (let* ((cc-point (concat (buffer-file-name) |
| 148 | ":" |
| 149 | (number-to-string (+ 1 (current-line))) |
| 150 | ":" |
| 151 | (number-to-string (+ 1 (current-column))))) |
| 152 | (cc-pch (if (equal clang-completion-prefix-header "") nil |
| 153 | (list "-include-pch" |
| 154 | (concat clang-completion-prefix-header ".pch")))) |
| 155 | (cc-flags (if (listp clang-flags) clang-flags nil)) |
| 156 | (cc-command (append `(,clang "-cc1" "-fsyntax-only") |
| 157 | cc-flags |
| 158 | cc-pch |
| 159 | `("-code-completion-at" ,cc-point) |
| 160 | (list (buffer-file-name)))) |
| 161 | (cc-buffer-name (concat "*Clang Completion for " (buffer-name) "*"))) |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 162 | ;; Start the code-completion process |
| 163 | (if (buffer-file-name) |
| 164 | (progn |
| 165 | ;; If there is already a code-completion process, kill it first. |
| 166 | (let ((cc-proc (get-process "Clang Code-Completion"))) |
| 167 | (if cc-proc |
| 168 | (delete-process cc-proc))) |
| 169 | |
| 170 | (setq clang-completion-substring "") |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 171 | (setq clang-result-string "") |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 172 | (setq clang-completion-buffer cc-buffer-name) |
| 173 | |
John McCall | 7ae4373 | 2010-01-13 06:44:51 +0000 | [diff] [blame] | 174 | (let ((cc-proc (apply 'start-process |
| 175 | (append (list "Clang Code-Completion" cc-buffer-name) |
| 176 | cc-command)))) |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 177 | (set-process-filter cc-proc 'clang-completion-stash-filter) |
| 178 | (set-process-sentinel cc-proc 'clang-completion-sentinel) |
| 179 | ))))) |
| 180 | |
| 181 | ;; Code-completion when one of the trigger characters is typed into |
| 182 | ;; the buffer, e.g., '(', ',' or '.'. |
| 183 | (defun clang-complete-self-insert (arg) |
| 184 | (interactive "p") |
| 185 | (self-insert-command arg) |
| 186 | (save-buffer) |
| 187 | (clang-complete)) |
| 188 | |
| 189 | ;; When the user has typed a character that requires the filter to be |
| 190 | ;; updated, do so (and update the display of results). |
| 191 | (defun clang-update-filter () |
| 192 | (setq clang-completion-substring (thing-at-point 'symbol)) |
| 193 | (if (get-process "Clang Code-Completion") |
| 194 | () |
| 195 | (clang-completion-display clang-completion-buffer) |
| 196 | )) |
| 197 | |
| 198 | ;; Invoked when the user types an alphanumeric character or "_" to |
| 199 | ;; update the filter for the currently-active code completion. |
| 200 | (defun clang-filter-self-insert (arg) |
| 201 | (interactive "p") |
| 202 | (self-insert-command arg) |
| 203 | (clang-update-filter) |
| 204 | ) |
| 205 | |
| 206 | ;; Invoked when the user types the backspace key to update the filter |
| 207 | ;; for the currently-active code completion. |
| 208 | (defun clang-backspace () |
| 209 | (interactive) |
| 210 | (delete-backward-char 1) |
| 211 | (clang-update-filter)) |
| 212 | |
| 213 | ;; Invoked when the user types the delete key to update the filter |
| 214 | ;; for the currently-active code completion. |
| 215 | (defun clang-delete () |
| 216 | (interactive) |
| 217 | (delete-backward-char 1) |
| 218 | (clang-update-filter)) |
| 219 | |
| 220 | ;; Set up the keymap for the Clang minor mode. |
| 221 | (defvar clang-completion-mode-map nil |
| 222 | "Keymap for Clang Completion Mode.") |
| 223 | |
| 224 | (if (null clang-completion-mode-map) |
| 225 | (fset 'clang-completion-mode-map |
| 226 | (setq clang-completion-mode-map (make-sparse-keymap)))) |
| 227 | |
| 228 | (if (not (assq 'clang-completion-mode minor-mode-map-alist)) |
| 229 | (setq minor-mode-map-alist |
| 230 | (cons (cons 'clang-completion-mode clang-completion-mode-map) |
| 231 | minor-mode-map-alist))) |
| 232 | |
| 233 | ;; Punctuation characters trigger code completion. |
| 234 | (dolist (char '("(" "," "." ">" ":" "=" ")" " ")) |
| 235 | (define-key clang-completion-mode-map char 'clang-complete-self-insert)) |
| 236 | |
| 237 | ;; Alphanumeric characters (and "_") filter the results of the |
| 238 | ;; currently-active code completion. |
| 239 | (dolist (char '("A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" |
| 240 | "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" |
| 241 | "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" |
| 242 | "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" |
| 243 | "_" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9")) |
| 244 | (define-key clang-completion-mode-map char 'clang-filter-self-insert)) |
| 245 | |
| 246 | ;; Delete and backspace filter the results of the currently-active |
| 247 | ;; code completion. |
| 248 | (define-key clang-completion-mode-map [(backspace)] 'clang-backspace) |
| 249 | (define-key clang-completion-mode-map [(delete)] 'clang-delete) |
| 250 | |
| 251 | ;; Set up the Clang minor mode. |
| 252 | (define-minor-mode clang-completion-mode |
| 253 | "Clang code-completion mode" |
| 254 | nil |
Douglas Gregor | d240724 | 2009-12-11 23:47:56 +0000 | [diff] [blame] | 255 | " Clang" |
Douglas Gregor | a584fb3 | 2009-10-09 22:17:40 +0000 | [diff] [blame] | 256 | clang-completion-mode-map) |
| 257 | |