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
  (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)
58
                            ?\( ?\)
59 60 61 62
                            (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

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."
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 ()
119
  "Indent current line as CMake code."
120
  (interactive)
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))
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)
145
            ;; If next token closes the block, decrease indentation
146
            (when (looking-at cmake-regex-close)
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.
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)
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
  (cmake-command-run "--help-property" (cmake-help-type "property") "*CMake Help*"))

;;;###autoload
(defun cmake-help ()
Luz Paz's avatar
Luz Paz committed
355
  "Queries for any of the four available help topics and prints out the appropriate page."
356
  (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