cmake-mode.el 14.3 KB
Newer Older
1
2
;;; cmake-mode.el --- major-mode for editing CMake sources

3
4
; Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
; file Copyright.txt or https://cmake.org/licensing for details.
Ken Martin's avatar
Ken Martin committed
5

6
;------------------------------------------------------------------------------
7

8
9
10
11
12
13
14
15
16
;;; Commentary:

;; Provides syntax highlighting and indentation for CMakeLists.txt and
;; *.cmake source files.
;;
;; Add this code to your .emacs file to use the mode:
;;
;;  (setq load-path (cons (expand-file-name "/dir/with/cmake-mode") load-path))
;;  (require 'cmake-mode)
17

18
19
20
;------------------------------------------------------------------------------

;;; Code:
21
22
23
24
;;
;; cmake executable variable used to run cmake --help-command
;; on commands in cmake-mode
;;
25
;; cmake-command-help Written by James Bigler
26
;;
27

28
29
30
31
32
33
34
35
36
(defcustom cmake-mode-cmake-executable "cmake"
  "*The name of the cmake executable.

This can be either absolute or looked up in $PATH.  You can also
set the path with these commands:
 (setenv \"PATH\" (concat (getenv \"PATH\") \";C:\\\\Program Files\\\\CMake 2.8\\\\bin\"))
 (setenv \"PATH\" (concat (getenv \"PATH\") \":/usr/local/cmake/bin\"))"
  :type 'file
  :group 'cmake)
37
38
39
40
41
42
43
44

;; Keywords
(defconst cmake-keywords-block-open '("IF" "MACRO" "FOREACH" "ELSE" "ELSEIF" "WHILE" "FUNCTION"))
(defconst cmake-keywords-block-close '("ENDIF" "ENDFOREACH" "ENDMACRO" "ELSE" "ELSEIF" "ENDWHILE" "ENDFUNCTION"))
(defconst cmake-keywords
  (let ((kwds (append cmake-keywords-block-open cmake-keywords-block-close nil)))
    (delete-dups kwds)))

45
46
47
;; Regular expressions used by line indentation function.
;;
(defconst cmake-regex-blank "^[ \t]*$")
48
(defconst cmake-regex-comment "#.*")
49
50
(defconst cmake-regex-paren-left "(")
(defconst cmake-regex-paren-right ")")
51
(defconst cmake-regex-argument-quoted
52
  (rx ?\" (* (or (not (any ?\" ?\\)) (and ?\\ anything))) ?\"))
53
(defconst cmake-regex-argument-unquoted
54
55
56
57
58
59
60
61
62
  (rx (or (not (any space "()#\"\\\n")) (and ?\\ nonl))
      (* (or (not (any space "()#\\\n")) (and ?\\ nonl)))))
(defconst cmake-regex-token
  (rx-to-string `(group (or (regexp ,cmake-regex-comment)
                            ?( ?)
                            (regexp ,cmake-regex-argument-unquoted)
                            (regexp ,cmake-regex-argument-quoted)))))
(defconst cmake-regex-indented
  (rx-to-string `(and bol (* (group (or (regexp ,cmake-regex-token) (any space ?\n)))))))
63
(defconst cmake-regex-block-open
64
65
  (rx-to-string `(and symbol-start (or ,@(append cmake-keywords-block-open
                                        (mapcar 'downcase cmake-keywords-block-open))) symbol-end)))
66
(defconst cmake-regex-block-close
67
68
  (rx-to-string `(and symbol-start (or ,@(append cmake-keywords-block-close
                                        (mapcar 'downcase cmake-keywords-block-close))) symbol-end)))
69
70
71
(defconst cmake-regex-close
  (rx-to-string `(and bol (* space) (regexp ,cmake-regex-block-close)
                      (* space) (regexp ,cmake-regex-paren-left))))
72

73
;------------------------------------------------------------------------------
74

Roy Crihfield's avatar
Roy Crihfield committed
75
76
;; Line indentation helper functions

77
78
(defun cmake-line-starts-inside-string ()
  "Determine whether the beginning of the current line is in a string."
Roy Crihfield's avatar
Roy Crihfield committed
79
80
81
82
83
84
  (save-excursion
    (beginning-of-line)
    (let ((parse-end (point)))
      (goto-char (point-min))
      (nth 3 (parse-partial-sexp (point) parse-end))
      )
85
86
87
88
89
90
91
92
93
94
95
    )
  )

(defun cmake-find-last-indented-line ()
  "Move to the beginning of the last line that has meaningful indentation."
  (let ((point-start (point))
        region)
    (forward-line -1)
    (setq region (buffer-substring-no-properties (point) point-start))
    (while (and (not (bobp))
                (or (looking-at cmake-regex-blank)
96
                    (cmake-line-starts-inside-string)
97
98
99
100
101
102
103
                    (not (and (string-match cmake-regex-indented region)
                              (= (length region) (match-end 0))))))
      (forward-line -1)
      (setq region (buffer-substring-no-properties (point) point-start))
      )
    )
  )
Ken Martin's avatar
Ken Martin committed
104

105
106
;------------------------------------------------------------------------------

107
108
109
110
111
112
113
114
;;
;; Indentation increment.
;;
(defcustom cmake-tab-width 2
  "Number of columns to indent cmake blocks"
  :type 'integer
  :group 'cmake)

115
116
117
;;
;; Line indentation function.
;;
Ken Martin's avatar
Ken Martin committed
118
(defun cmake-indent ()
Roy Crihfield's avatar
Roy Crihfield committed
119
  "Indent current line as CMake code."
120
  (interactive)
Roy Crihfield's avatar
Roy Crihfield committed
121
  (unless (cmake-line-starts-inside-string)
122
    (if (bobp)
123
124
        (cmake-indent-line-to 0)
      (let (cur-indent)
125
        (save-excursion
126
127
          (beginning-of-line)
          (let ((point-start (point))
128
                (case-fold-search t)  ;; case-insensitive
129
130
131
132
133
134
135
136
                token)
            ; Search back for the last indented line.
            (cmake-find-last-indented-line)
            ; Start with the indentation on this line.
            (setq cur-indent (current-indentation))
            ; Search forward counting tokens that adjust indentation.
            (while (re-search-forward cmake-regex-token point-start t)
              (setq token (match-string 0))
Roy Crihfield's avatar
Roy Crihfield committed
137
138
139
140
141
142
              (when (or (string-match (concat "^" cmake-regex-paren-left "$") token)
                        (and (string-match cmake-regex-block-open token)
                             (looking-at (concat "[ \t]*" cmake-regex-paren-left))))
                (setq cur-indent (+ cur-indent cmake-tab-width)))
              (when (string-match (concat "^" cmake-regex-paren-right "$") token)
                (setq cur-indent (- cur-indent cmake-tab-width)))
143
              )
144
            (goto-char point-start)
Roy Crihfield's avatar
Roy Crihfield committed
145
            ;; If next token closes the block, decrease indentation
146
            (when (looking-at cmake-regex-close)
Roy Crihfield's avatar
Roy Crihfield committed
147
              (setq cur-indent (- cur-indent cmake-tab-width))
148
149
              )
            )
Ken Martin's avatar
Ken Martin committed
150
          )
151
        ; Indent this line by the amount selected.
Roy Crihfield's avatar
Roy Crihfield committed
152
        (cmake-indent-line-to (max cur-indent 0))
Ken Martin's avatar
Ken Martin committed
153
154
155
156
        )
      )
    )
  )
157

158
159
160
161
162
163
164
165
166
167
168
(defun cmake-point-in-indendation ()
  (string-match "^[ \\t]*$" (buffer-substring (point-at-bol) (point))))

(defun cmake-indent-line-to (column)
  "Indent the current line to COLUMN.
If point is within the existing indentation it is moved to the end of
the indentation.  Otherwise it retains the same position on the line"
  (if (cmake-point-in-indendation)
      (indent-line-to column)
    (save-excursion (indent-line-to column))))

169
;------------------------------------------------------------------------------
Ken Martin's avatar
Ken Martin committed
170

171
172
173
;;
;; Helper functions for buffer
;;
174
(defun cmake-unscreamify-buffer ()
175
176
  "Convert all CMake commands to lowercase in buffer."
  (interactive)
177
178
  (save-excursion
    (goto-char (point-min))
179
    (while (re-search-forward "^\\([ \t]*\\)\\_<\\(\\(?:\\w\\|\\s_\\)+\\)\\_>\\([ \t]*(\\)" nil t)
180
181
182
183
184
185
186
      (replace-match
       (concat
        (match-string 1)
        (downcase (match-string 2))
        (match-string 3))
       t))
    )
187
188
189
190
  )

;------------------------------------------------------------------------------

191
192
193
;;
;; Keyword highlighting regex-to-face map.
;;
194
(defconst cmake-font-lock-keywords
195
196
197
198
199
  `((,(rx-to-string `(and symbol-start
                          (or ,@cmake-keywords
                              ,@(mapcar #'downcase cmake-keywords))
                          symbol-end))
     . font-lock-keyword-face)
200
    (,(rx symbol-start (group (+ (or word (syntax symbol)))) (* blank) ?\()
201
     1 font-lock-function-name-face)
202
    (,(rx "${" (group (+(any alnum "-_+/."))) "}")
203
204
205
     1 font-lock-variable-name-face t)
    )
  "Highlighting expressions for CMake mode.")
Ken Martin's avatar
Ken Martin committed
206

207
;------------------------------------------------------------------------------
Ken Martin's avatar
Ken Martin committed
208

209
210
211
212
213
214
215
216
217
218
219
220
;; Syntax table for this mode.
(defvar cmake-mode-syntax-table nil
  "Syntax table for CMake mode.")
(or cmake-mode-syntax-table
    (setq cmake-mode-syntax-table
          (let ((table (make-syntax-table)))
            (modify-syntax-entry ?\(  "()" table)
            (modify-syntax-entry ?\)  ")(" table)
            (modify-syntax-entry ?# "<" table)
            (modify-syntax-entry ?\n ">" table)
            (modify-syntax-entry ?$ "'" table)
            table)))
Ken Martin's avatar
Ken Martin committed
221

222
223
224
;;
;; User hook entry point.
;;
Ken Martin's avatar
Ken Martin committed
225
226
(defvar cmake-mode-hook nil)

227
228
;------------------------------------------------------------------------------

229
230
231
232
233
234
;; For compatibility with Emacs < 24
(defalias 'cmake--parent-mode
  (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))

;;------------------------------------------------------------------------------
;; Mode definition.
235
;;
236
;;;###autoload
237
238
(define-derived-mode cmake-mode cmake--parent-mode "CMake"
  "Major mode for editing CMake source files."
239

Ken Martin's avatar
Ken Martin committed
240
  ; Setup font-lock mode.
241
  (set (make-local-variable 'font-lock-defaults) '(cmake-font-lock-keywords))
Ken Martin's avatar
Ken Martin committed
242
  ; Setup indentation function.
243
  (set (make-local-variable 'indent-line-function) 'cmake-indent)
Brad King's avatar
Brad King committed
244
  ; Setup comment syntax.
245
  (set (make-local-variable 'comment-start) "#"))
Ken Martin's avatar
Ken Martin committed
246

247
248
249
; Help mode starts here


250
;;;###autoload
251
(defun cmake-command-run (type &optional topic buffer)
252
253
254
  "Runs the command cmake with the arguments specified.  The
optional argument topic will be appended to the argument list."
  (interactive "s")
255
256
257
258
259
  (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*")))
         (buffer  (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname)))
         (command (concat cmake-mode-cmake-executable " " type " " topic))
         ;; Turn of resizing of mini-windows for shell-command.
         (resize-mini-windows nil)
260
         )
261
262
263
264
    (shell-command command buffer)
    (save-selected-window
      (select-window (display-buffer buffer 'not-this-window))
      (cmake-mode)
265
      (read-only-mode 1))
266
267
268
    )
  )

269
;;;###autoload
270
271
272
273
274
275
(defun cmake-help-list-commands ()
  "Prints out a list of the cmake commands."
  (interactive)
  (cmake-command-run "--help-command-list")
  )

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
(defvar cmake-commands '() "List of available topics for --help-command.")
(defvar cmake-help-command-history nil "Command read history.")
(defvar cmake-modules '() "List of available topics for --help-module.")
(defvar cmake-help-module-history nil "Module read history.")
(defvar cmake-variables '() "List of available topics for --help-variable.")
(defvar cmake-help-variable-history nil "Variable read history.")
(defvar cmake-properties '() "List of available topics for --help-property.")
(defvar cmake-help-property-history nil "Property read history.")
(defvar cmake-help-complete-history nil "Complete help read history.")
(defvar cmake-string-to-list-symbol
  '(("command" cmake-commands cmake-help-command-history)
    ("module" cmake-modules cmake-help-module-history)
    ("variable"  cmake-variables cmake-help-variable-history)
    ("property" cmake-properties cmake-help-property-history)
    ))

(defun cmake-get-list (listname)
  "If the value of LISTVAR is nil, run cmake --help-LISTNAME-list
and store the result as a list in LISTVAR."
  (let ((listvar (car (cdr (assoc listname cmake-string-to-list-symbol)))))
    (if (not (symbol-value listvar))
        (let ((temp-buffer-name "*CMake Temporary*"))
          (save-window-excursion
            (cmake-command-run (concat "--help-" listname "-list") nil temp-buffer-name)
            (with-current-buffer temp-buffer-name
301
302
              ; FIXME: Ignore first line if it is "cmake version ..." from CMake < 3.0.
              (set listvar (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n" t)))))
303
304
      (symbol-value listvar)
      ))
305
  )
306

307
(require 'thingatpt)
308
309
310
311
312
(defun cmake-symbol-at-point ()
  (let ((symbol (symbol-at-point)))
    (and (not (null symbol))
         (symbol-name symbol))))

313
(defun cmake-help-type (type)
314
  (let* ((default-entry (cmake-symbol-at-point))
315
         (history (car (cdr (cdr (assoc type cmake-string-to-list-symbol)))))
316
         (input (completing-read
317
318
                 (format "CMake %s: " type) ; prompt
                 (cmake-get-list type) ; completions
319
320
321
                 nil ; predicate
                 t   ; require-match
                 default-entry ; initial-input
322
                 history
323
324
325
326
327
328
                 )))
    (if (string= input "")
        (error "No argument given")
      input))
  )

329
;;;###autoload
330
(defun cmake-help-command ()
331
  "Prints out the help message for the command the cursor is on."
332
  (interactive)
333
  (cmake-command-run "--help-command" (cmake-help-type "command") "*CMake Help*"))
334

335
336
337
338
339
;;;###autoload
(defun cmake-help-module ()
  "Prints out the help message for the module the cursor is on."
  (interactive)
  (cmake-command-run "--help-module" (cmake-help-type "module") "*CMake Help*"))
340

341
342
343
344
345
;;;###autoload
(defun cmake-help-variable ()
  "Prints out the help message for the variable the cursor is on."
  (interactive)
  (cmake-command-run "--help-variable" (cmake-help-type "variable") "*CMake Help*"))
346

347
348
349
;;;###autoload
(defun cmake-help-property ()
  "Prints out the help message for the property the cursor is on."
350
  (interactive)
351
352
353
354
355
356
  (cmake-command-run "--help-property" (cmake-help-type "property") "*CMake Help*"))

;;;###autoload
(defun cmake-help ()
  "Queries for any of the four available help topics and prints out the approriate page."
  (interactive)
357
  (let* ((default-entry (cmake-symbol-at-point))
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
         (command-list (cmake-get-list "command"))
         (variable-list (cmake-get-list "variable"))
         (module-list (cmake-get-list "module"))
         (property-list (cmake-get-list "property"))
         (all-words (append command-list variable-list module-list property-list))
         (input (completing-read
                 "CMake command/module/variable/property: " ; prompt
                 all-words ; completions
                 nil ; predicate
                 t   ; require-match
                 default-entry ; initial-input
                 'cmake-help-complete-history
                 )))
    (if (string= input "")
        (error "No argument given")
      (if (member input command-list)
          (cmake-command-run "--help-command" input "*CMake Help*")
        (if (member input variable-list)
            (cmake-command-run "--help-variable" input "*CMake Help*")
          (if (member input module-list)
              (cmake-command-run "--help-module" input "*CMake Help*")
            (if (member input property-list)
                (cmake-command-run "--help-property" input "*CMake Help*")
              (error "Not a know help topic.") ; this really should not happen
              ))))))
  )
384

385
386
387
388
;;;###autoload
(progn
  (add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-mode))
  (add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-mode)))
389

Ken Martin's avatar
Ken Martin committed
390
391
; This file provides cmake-mode.
(provide 'cmake-mode)
392
393

;;; cmake-mode.el ends here