1;;; cmake-mode.el --- major-mode for editing CMake sources 2 3;; Package-Requires: ((emacs "24.1")) 4 5; Distributed under the OSI-approved BSD 3-Clause License. See accompanying 6; file Copyright.txt or https://cmake.org/licensing for details. 7 8;------------------------------------------------------------------------------ 9 10;;; Commentary: 11 12;; Provides syntax highlighting and indentation for CMakeLists.txt and 13;; *.cmake source files. 14;; 15;; Add this code to your .emacs file to use the mode: 16;; 17;; (setq load-path (cons (expand-file-name "/dir/with/cmake-mode") load-path)) 18;; (require 'cmake-mode) 19 20;------------------------------------------------------------------------------ 21 22;;; Code: 23;; 24;; cmake executable variable used to run cmake --help-command 25;; on commands in cmake-mode 26;; 27;; cmake-command-help Written by James Bigler 28;; 29 30(require 'rst) 31(require 'rx) 32 33(defcustom cmake-mode-cmake-executable "cmake" 34 "*The name of the cmake executable. 35 36This can be either absolute or looked up in $PATH. You can also 37set the path with these commands: 38 (setenv \"PATH\" (concat (getenv \"PATH\") \";C:\\\\Program Files\\\\CMake 2.8\\\\bin\")) 39 (setenv \"PATH\" (concat (getenv \"PATH\") \":/usr/local/cmake/bin\"))" 40 :type 'file 41 :group 'cmake) 42 43;; Keywords 44(defconst cmake-keywords-block-open '("IF" "MACRO" "FOREACH" "ELSE" "ELSEIF" "WHILE" "FUNCTION")) 45(defconst cmake-keywords-block-close '("ENDIF" "ENDFOREACH" "ENDMACRO" "ELSE" "ELSEIF" "ENDWHILE" "ENDFUNCTION")) 46(defconst cmake-keywords 47 (let ((kwds (append cmake-keywords-block-open cmake-keywords-block-close nil))) 48 (delete-dups kwds))) 49 50;; Regular expressions used by line indentation function. 51;; 52(defconst cmake-regex-blank "^[ \t]*$") 53(defconst cmake-regex-comment "#.*") 54(defconst cmake-regex-paren-left "(") 55(defconst cmake-regex-paren-right ")") 56(defconst cmake-regex-argument-quoted 57 (rx ?\" (* (or (not (any ?\" ?\\)) (and ?\\ anything))) ?\")) 58(defconst cmake-regex-argument-unquoted 59 (rx (or (not (any space "()#\"\\\n")) (and ?\\ nonl)) 60 (* (or (not (any space "()#\\\n")) (and ?\\ nonl))))) 61(defconst cmake-regex-token 62 (rx-to-string `(group (or (regexp ,cmake-regex-comment) 63 ?\( ?\) 64 (regexp ,cmake-regex-argument-unquoted) 65 (regexp ,cmake-regex-argument-quoted))))) 66(defconst cmake-regex-indented 67 (rx-to-string `(and bol (* (group (or (regexp ,cmake-regex-token) (any space ?\n))))))) 68(defconst cmake-regex-block-open 69 (rx-to-string `(and symbol-start (or ,@(append cmake-keywords-block-open 70 (mapcar 'downcase cmake-keywords-block-open))) symbol-end))) 71(defconst cmake-regex-block-close 72 (rx-to-string `(and symbol-start (or ,@(append cmake-keywords-block-close 73 (mapcar 'downcase cmake-keywords-block-close))) symbol-end))) 74(defconst cmake-regex-close 75 (rx-to-string `(and bol (* space) (regexp ,cmake-regex-block-close) 76 (* space) (regexp ,cmake-regex-paren-left)))) 77 78;------------------------------------------------------------------------------ 79 80;; Line indentation helper functions 81 82(defun cmake-line-starts-inside-string () 83 "Determine whether the beginning of the current line is in a string." 84 (save-excursion 85 (beginning-of-line) 86 (let ((parse-end (point))) 87 (goto-char (point-min)) 88 (nth 3 (parse-partial-sexp (point) parse-end)) 89 ) 90 ) 91 ) 92 93(defun cmake-find-last-indented-line () 94 "Move to the beginning of the last line that has meaningful indentation." 95 (let ((point-start (point)) 96 region) 97 (forward-line -1) 98 (setq region (buffer-substring-no-properties (point) point-start)) 99 (while (and (not (bobp)) 100 (or (looking-at cmake-regex-blank) 101 (cmake-line-starts-inside-string) 102 (not (and (string-match cmake-regex-indented region) 103 (= (length region) (match-end 0)))))) 104 (forward-line -1) 105 (setq region (buffer-substring-no-properties (point) point-start)) 106 ) 107 ) 108 ) 109 110;------------------------------------------------------------------------------ 111 112;; 113;; Indentation increment. 114;; 115(defcustom cmake-tab-width 2 116 "Number of columns to indent cmake blocks" 117 :type 'integer 118 :group 'cmake) 119 120;; 121;; Line indentation function. 122;; 123(defun cmake-indent () 124 "Indent current line as CMake code." 125 (interactive) 126 (unless (cmake-line-starts-inside-string) 127 (if (bobp) 128 (cmake-indent-line-to 0) 129 (let (cur-indent) 130 (save-excursion 131 (beginning-of-line) 132 (let ((point-start (point)) 133 (case-fold-search t) ;; case-insensitive 134 token) 135 ; Search back for the last indented line. 136 (cmake-find-last-indented-line) 137 ; Start with the indentation on this line. 138 (setq cur-indent (current-indentation)) 139 ; Search forward counting tokens that adjust indentation. 140 (while (re-search-forward cmake-regex-token point-start t) 141 (setq token (match-string 0)) 142 (when (or (string-match (concat "^" cmake-regex-paren-left "$") token) 143 (and (string-match cmake-regex-block-open token) 144 (looking-at (concat "[ \t]*" cmake-regex-paren-left)))) 145 (setq cur-indent (+ cur-indent cmake-tab-width))) 146 (when (string-match (concat "^" cmake-regex-paren-right "$") token) 147 (setq cur-indent (- cur-indent cmake-tab-width))) 148 ) 149 (goto-char point-start) 150 ;; If next token closes the block, decrease indentation 151 (when (looking-at cmake-regex-close) 152 (setq cur-indent (- cur-indent cmake-tab-width)) 153 ) 154 ) 155 ) 156 ; Indent this line by the amount selected. 157 (cmake-indent-line-to (max cur-indent 0)) 158 ) 159 ) 160 ) 161 ) 162 163(defun cmake-point-in-indendation () 164 (string-match "^[ \\t]*$" (buffer-substring (point-at-bol) (point)))) 165 166(defun cmake-indent-line-to (column) 167 "Indent the current line to COLUMN. 168If point is within the existing indentation it is moved to the end of 169the indentation. Otherwise it retains the same position on the line" 170 (if (cmake-point-in-indendation) 171 (indent-line-to column) 172 (save-excursion (indent-line-to column)))) 173 174;------------------------------------------------------------------------------ 175 176;; 177;; Helper functions for buffer 178;; 179(defun cmake-unscreamify-buffer () 180 "Convert all CMake commands to lowercase in buffer." 181 (interactive) 182 (save-excursion 183 (goto-char (point-min)) 184 (while (re-search-forward "^\\([ \t]*\\)\\_<\\(\\(?:\\w\\|\\s_\\)+\\)\\_>\\([ \t]*(\\)" nil t) 185 (replace-match 186 (concat 187 (match-string 1) 188 (downcase (match-string 2)) 189 (match-string 3)) 190 t)) 191 ) 192 ) 193 194 195;------------------------------------------------------------------------------ 196 197;; 198;; Navigation / marking by function or macro 199;; 200 201(defconst cmake--regex-defun-start 202 (rx line-start 203 (zero-or-more space) 204 (or "function" "macro") 205 (zero-or-more space) 206 "(")) 207 208(defconst cmake--regex-defun-end 209 (rx line-start 210 (zero-or-more space) 211 "end" 212 (or "function" "macro") 213 (zero-or-more space) 214 "(" (zero-or-more (not-char ")")) ")")) 215 216(defun cmake-beginning-of-defun () 217 "Move backward to the beginning of a CMake function or macro. 218 219Return t unless search stops due to beginning of buffer." 220 (interactive) 221 (when (not (region-active-p)) 222 (push-mark)) 223 (let ((case-fold-search t)) 224 (when (re-search-backward cmake--regex-defun-start nil 'move) 225 t))) 226 227(defun cmake-end-of-defun () 228 "Move forward to the end of a CMake function or macro. 229 230Return t unless search stops due to end of buffer." 231 (interactive) 232 (when (not (region-active-p)) 233 (push-mark)) 234 (let ((case-fold-search t)) 235 (when (re-search-forward cmake--regex-defun-end nil 'move) 236 (forward-line) 237 t))) 238 239(defun cmake-mark-defun () 240 "Mark the current CMake function or macro. 241 242This puts the mark at the end, and point at the beginning." 243 (interactive) 244 (cmake-end-of-defun) 245 (push-mark nil :nomsg :activate) 246 (cmake-beginning-of-defun)) 247 248 249;------------------------------------------------------------------------------ 250 251;; 252;; Keyword highlighting regex-to-face map. 253;; 254(defconst cmake-font-lock-keywords 255 `((,(rx-to-string `(and symbol-start 256 (or ,@cmake-keywords 257 ,@(mapcar #'downcase cmake-keywords)) 258 symbol-end)) 259 . font-lock-keyword-face) 260 (,(rx symbol-start (group (+ (or word (syntax symbol)))) (* blank) ?\() 261 1 font-lock-function-name-face) 262 (,(rx "${" (group (+(any alnum "-_+/."))) "}") 263 1 font-lock-variable-name-face t) 264 ) 265 "Highlighting expressions for CMake mode.") 266 267;------------------------------------------------------------------------------ 268 269;; Syntax table for this mode. 270(defvar cmake-mode-syntax-table nil 271 "Syntax table for CMake mode.") 272(or cmake-mode-syntax-table 273 (setq cmake-mode-syntax-table 274 (let ((table (make-syntax-table))) 275 (modify-syntax-entry ?\( "()" table) 276 (modify-syntax-entry ?\) ")(" table) 277 (modify-syntax-entry ?# "<" table) 278 (modify-syntax-entry ?\n ">" table) 279 (modify-syntax-entry ?$ "'" table) 280 table))) 281 282;; 283;; User hook entry point. 284;; 285(defvar cmake-mode-hook nil) 286 287;;------------------------------------------------------------------------------ 288;; Mode definition. 289;; 290;;;###autoload 291(define-derived-mode cmake-mode prog-mode "CMake" 292 "Major mode for editing CMake source files." 293 294 ; Setup font-lock mode. 295 (set (make-local-variable 'font-lock-defaults) '(cmake-font-lock-keywords)) 296 ; Setup indentation function. 297 (set (make-local-variable 'indent-line-function) 'cmake-indent) 298 ; Setup comment syntax. 299 (set (make-local-variable 'comment-start) "#")) 300 301;; Default cmake-mode key bindings 302(define-key cmake-mode-map "\e\C-a" #'cmake-beginning-of-defun) 303(define-key cmake-mode-map "\e\C-e" #'cmake-end-of-defun) 304(define-key cmake-mode-map "\e\C-h" #'cmake-mark-defun) 305 306 307; Help mode starts here 308 309 310;;;###autoload 311(defun cmake-command-run (type &optional topic buffer) 312 "Runs the command cmake with the arguments specified. The 313optional argument topic will be appended to the argument list." 314 (interactive "s") 315 (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*"))) 316 (buffer (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname))) 317 (command (concat cmake-mode-cmake-executable " " type " " topic)) 318 ;; Turn of resizing of mini-windows for shell-command. 319 (resize-mini-windows nil) 320 ) 321 (shell-command command buffer) 322 (save-selected-window 323 (select-window (display-buffer buffer 'not-this-window)) 324 (cmake-mode) 325 (read-only-mode 1) 326 (view-mode 1)) 327 ) 328 ) 329 330;;;###autoload 331(defun cmake-command-run-help (type &optional topic buffer) 332 "`cmake-command-run' but rendered in `rst-mode'." 333 (interactive "s") 334 (let* ((bufname (if buffer buffer (concat "*CMake" type (if topic "-") topic "*"))) 335 (buffer (if (get-buffer bufname) (get-buffer bufname) (generate-new-buffer bufname))) 336 (command (concat cmake-mode-cmake-executable " " type " " topic)) 337 ;; Turn of resizing of mini-windows for shell-command. 338 (resize-mini-windows nil) 339 ) 340 (shell-command command buffer) 341 (save-selected-window 342 (select-window (display-buffer buffer 'not-this-window)) 343 (rst-mode) 344 (read-only-mode 1) 345 (view-mode 1)) 346 ) 347 ) 348 349;;;###autoload 350(defun cmake-help-list-commands () 351 "Prints out a list of the cmake commands." 352 (interactive) 353 (cmake-command-run-help "--help-command-list") 354 ) 355 356(defvar cmake-commands '() "List of available topics for --help-command.") 357(defvar cmake-help-command-history nil "Command read history.") 358(defvar cmake-modules '() "List of available topics for --help-module.") 359(defvar cmake-help-module-history nil "Module read history.") 360(defvar cmake-variables '() "List of available topics for --help-variable.") 361(defvar cmake-help-variable-history nil "Variable read history.") 362(defvar cmake-properties '() "List of available topics for --help-property.") 363(defvar cmake-help-property-history nil "Property read history.") 364(defvar cmake-help-complete-history nil "Complete help read history.") 365(defvar cmake-string-to-list-symbol 366 '(("command" cmake-commands cmake-help-command-history) 367 ("module" cmake-modules cmake-help-module-history) 368 ("variable" cmake-variables cmake-help-variable-history) 369 ("property" cmake-properties cmake-help-property-history) 370 )) 371 372(defun cmake-get-list (listname) 373 "If the value of LISTVAR is nil, run cmake --help-LISTNAME-list 374and store the result as a list in LISTVAR." 375 (let ((listvar (car (cdr (assoc listname cmake-string-to-list-symbol))))) 376 (if (not (symbol-value listvar)) 377 (let ((temp-buffer-name "*CMake Temporary*")) 378 (save-window-excursion 379 (cmake-command-run-help (concat "--help-" listname "-list") nil temp-buffer-name) 380 (with-current-buffer temp-buffer-name 381 ; FIXME: Ignore first line if it is "cmake version ..." from CMake < 3.0. 382 (set listvar (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n" t))))) 383 (symbol-value listvar) 384 )) 385 ) 386 387(require 'thingatpt) 388(defun cmake-symbol-at-point () 389 (let ((symbol (symbol-at-point))) 390 (and (not (null symbol)) 391 (symbol-name symbol)))) 392 393(defun cmake-help-type (type) 394 (let* ((default-entry (cmake-symbol-at-point)) 395 (history (car (cdr (cdr (assoc type cmake-string-to-list-symbol))))) 396 (input (completing-read 397 (format "CMake %s: " type) ; prompt 398 (cmake-get-list type) ; completions 399 nil ; predicate 400 t ; require-match 401 default-entry ; initial-input 402 history 403 ))) 404 (if (string= input "") 405 (error "No argument given") 406 input)) 407 ) 408 409;;;###autoload 410(defun cmake-help-command () 411 "Prints out the help message for the command the cursor is on." 412 (interactive) 413 (cmake-command-run-help "--help-command" (cmake-help-type "command") "*CMake Help*")) 414 415;;;###autoload 416(defun cmake-help-module () 417 "Prints out the help message for the module the cursor is on." 418 (interactive) 419 (cmake-command-run-help "--help-module" (cmake-help-type "module") "*CMake Help*")) 420 421;;;###autoload 422(defun cmake-help-variable () 423 "Prints out the help message for the variable the cursor is on." 424 (interactive) 425 (cmake-command-run-help "--help-variable" (cmake-help-type "variable") "*CMake Help*")) 426 427;;;###autoload 428(defun cmake-help-property () 429 "Prints out the help message for the property the cursor is on." 430 (interactive) 431 (cmake-command-run-help "--help-property" (cmake-help-type "property") "*CMake Help*")) 432 433;;;###autoload 434(defun cmake-help () 435 "Queries for any of the four available help topics and prints out the appropriate page." 436 (interactive) 437 (let* ((default-entry (cmake-symbol-at-point)) 438 (command-list (cmake-get-list "command")) 439 (variable-list (cmake-get-list "variable")) 440 (module-list (cmake-get-list "module")) 441 (property-list (cmake-get-list "property")) 442 (all-words (append command-list variable-list module-list property-list)) 443 (input (completing-read 444 "CMake command/module/variable/property: " ; prompt 445 all-words ; completions 446 nil ; predicate 447 t ; require-match 448 default-entry ; initial-input 449 'cmake-help-complete-history 450 ))) 451 (if (string= input "") 452 (error "No argument given") 453 (if (member input command-list) 454 (cmake-command-run-help "--help-command" input "*CMake Help*") 455 (if (member input variable-list) 456 (cmake-command-run-help "--help-variable" input "*CMake Help*") 457 (if (member input module-list) 458 (cmake-command-run-help "--help-module" input "*CMake Help*") 459 (if (member input property-list) 460 (cmake-command-run-help "--help-property" input "*CMake Help*") 461 (error "Not a know help topic.") ; this really should not happen 462 )))))) 463 ) 464 465;;;###autoload 466(progn 467 (add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-mode)) 468 (add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-mode))) 469 470; This file provides cmake-mode. 471(provide 'cmake-mode) 472 473;;; cmake-mode.el ends here 474