| 1 | ;;; zig-mode.el --- A major mode for the Zig programming language -*- lexical-binding: t -*- |
| 2 | |
| 3 | ;; Author: Andrea Orru <andreaorru1991@gmail.com> |
| 4 | ;; Andrew Kelley <superjoe30@gmail.com> |
| 5 | ;; Maintainer: Shen, Jen-Chieh <jcs090218@gmail.com> |
| 6 | ;; URL: https://github.com/zig-lang/zig-mode |
| 7 | ;; Version: 0.0.8 |
| 8 | ;; Package-Requires: ((emacs "26.1") (reformatter "0.6")) |
| 9 | ;; Keywords: zig, languages |
| 10 | |
| 11 | ;; This file is free software; you can redistribute it and/or modify |
| 12 | ;; it under the terms of the GNU General Public License as published by |
| 13 | ;; the Free Software Foundation; either version 3, or (at your option) |
| 14 | ;; any later version. |
| 15 | |
| 16 | ;; This file is distributed in the hope that it will be useful, |
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | ;; GNU General Public License for more details. |
| 20 | |
| 21 | ;; You should have received a copy of the GNU General Public License |
| 22 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 23 | |
| 24 | ;;; Commentary: |
| 25 | |
| 26 | ;; A major mode for the Zig programming languages. |
| 27 | |
| 28 | ;; See documentation on https://github.com/zig-lang/zig-mode |
| 29 | |
| 30 | ;;; Code: |
| 31 | |
| 32 | (require 'reformatter) |
| 33 | |
| 34 | (defgroup zig-mode nil |
| 35 | "Support for Zig code." |
| 36 | :link '(url-link "https://ziglang.org/") |
| 37 | :group 'languages) |
| 38 | |
| 39 | (defcustom zig-indent-offset 4 |
| 40 | "Indent Zig code by this number of spaces." |
| 41 | :type 'integer |
| 42 | :group 'zig-mode |
| 43 | :safe #'integerp) |
| 44 | |
| 45 | (defcustom zig-format-on-save t |
| 46 | "Format buffers before saving using zig fmt." |
| 47 | :type 'boolean |
| 48 | :safe #'booleanp |
| 49 | :group 'zig-mode) |
| 50 | |
| 51 | (defcustom zig-format-show-buffer t |
| 52 | "Show a *zig-fmt* buffer after zig fmt completes with errors" |
| 53 | :type 'boolean |
| 54 | :safe #'booleanp |
| 55 | :group 'zig-mode) |
| 56 | |
| 57 | (defcustom zig-zig-bin "zig" |
| 58 | "Path to zig executable." |
| 59 | :type 'file |
| 60 | :safe #'stringp |
| 61 | :group 'zig-mode) |
| 62 | |
| 63 | (defcustom zig-run-optimization-mode "Debug" |
| 64 | "Optimization mode to run code with." |
| 65 | :type 'string |
| 66 | :safe #'stringp |
| 67 | :group 'zig-mode) |
| 68 | |
| 69 | (defcustom zig-test-optimization-mode "Debug" |
| 70 | "Optimization mode to run tests with." |
| 71 | :type 'string |
| 72 | :safe #'stringp |
| 73 | :group 'zig-mode) |
| 74 | |
| 75 | ;; zig CLI commands |
| 76 | |
| 77 | (defun zig--run-cmd (cmd &optional source &rest args) |
| 78 | "Use compile command to execute a zig CMD with ARGS if given. |
| 79 | If given a SOURCE, execute the CMD on it." |
| 80 | (let ((cmd-args |
| 81 | (if source |
| 82 | (mapconcat 'shell-quote-argument (cons source args) " ") |
| 83 | args))) |
| 84 | (compilation-start (concat zig-zig-bin " " cmd " " cmd-args)))) |
| 85 | |
| 86 | ;;;###autoload |
| 87 | (defun zig-compile () |
| 88 | "Compile using `zig build`." |
| 89 | (interactive) |
| 90 | (zig--run-cmd "build")) |
| 91 | |
| 92 | ;;;###autoload |
| 93 | (defun zig-build-exe () |
| 94 | "Create executable from source or object file." |
| 95 | (interactive) |
| 96 | (zig--run-cmd "build-exe" (file-local-name (buffer-file-name)))) |
| 97 | |
| 98 | ;;;###autoload |
| 99 | (defun zig-build-lib () |
| 100 | "Create library from source or assembly." |
| 101 | (interactive) |
| 102 | (zig--run-cmd "build-lib" (file-local-name (buffer-file-name)))) |
| 103 | |
| 104 | ;;;###autoload |
| 105 | (defun zig-build-obj () |
| 106 | "Create object from source or assembly." |
| 107 | (interactive) |
| 108 | (zig--run-cmd "build-obj" (file-local-name (buffer-file-name)))) |
| 109 | |
| 110 | ;;;###autoload |
| 111 | (defun zig-test-buffer () |
| 112 | "Test buffer using `zig test`." |
| 113 | (interactive) |
| 114 | (zig--run-cmd "test" (file-local-name (buffer-file-name)) "-O" zig-test-optimization-mode)) |
| 115 | |
| 116 | ;;;###autoload |
| 117 | (defun zig-run () |
| 118 | "Create an executable from the current buffer and run it immediately." |
| 119 | (interactive) |
| 120 | (zig--run-cmd "run" (file-local-name (buffer-file-name)) "-O" zig-run-optimization-mode)) |
| 121 | |
| 122 | ;; zig fmt |
| 123 | |
| 124 | (reformatter-define zig-format |
| 125 | :program zig-zig-bin |
| 126 | :args '("fmt" "--stdin") |
| 127 | :group 'zig-mode |
| 128 | :lighter " ZigFmt") |
| 129 | |
| 130 | ;;;###autoload (autoload 'zig-format-buffer "current-file" nil t) |
| 131 | ;;;###autoload (autoload 'zig-format-region "current-file" nil t) |
| 132 | ;;;###autoload (autoload 'zig-format-on-save-mode "current-file" nil t) |
| 133 | |
| 134 | (defun zig-re-word (inner) |
| 135 | "Construct a regular expression for the word INNER." |
| 136 | (concat "\\<" inner "\\>")) |
| 137 | |
| 138 | (defun zig-re-grab (inner) |
| 139 | "Construct a group regular expression for INNER." |
| 140 | (concat "\\(" inner "\\)")) |
| 141 | |
| 142 | (defconst zig-re-optional "\\(?:[[:space:]]*\\?[[:space:]]*\\)") |
| 143 | (defconst zig-re-pointer "\\(?:[[:space:]]*\\*\\(?:const[[:space:]]*\\)?[[:space:]]*\\)") |
| 144 | (defconst zig-re-array "\\(?:[[:space:]]*\\[[^]]*\\]\\(?:const[[:space:]]*\\)?[[:space:]]*\\)") |
| 145 | |
| 146 | (defconst zig-re-optionals-pointers-arrays |
| 147 | (concat "\\(?:" zig-re-optional "\\|" zig-re-pointer "\\|" zig-re-array "\\)*")) |
| 148 | |
| 149 | (defconst zig-re-identifier "[[:word:]_][[:word:]_[:digit:]]*") |
| 150 | (defconst zig-re-type-annotation |
| 151 | (concat (zig-re-grab zig-re-identifier) |
| 152 | "[[:space:]]*:[[:space:]]*" |
| 153 | zig-re-optionals-pointers-arrays |
| 154 | (zig-re-grab zig-re-identifier))) |
| 155 | |
| 156 | (defun zig-re-definition (dtype) |
| 157 | "Construct a regular expression for definitions of type DTYPE." |
| 158 | (concat (zig-re-word dtype) "[[:space:]]+" (zig-re-grab zig-re-identifier))) |
| 159 | |
| 160 | (defconst zig-mode-syntax-table |
| 161 | (let ((table (make-syntax-table))) |
| 162 | |
| 163 | ;; Operators |
| 164 | (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?= ?! ?< ?>)) |
| 165 | (modify-syntax-entry i "." table)) |
| 166 | |
| 167 | ;; Strings |
| 168 | (modify-syntax-entry ?\' "\"" table) |
| 169 | (modify-syntax-entry ?\" "\"" table) |
| 170 | (modify-syntax-entry ?\\ "\\" table) |
| 171 | |
| 172 | ;; Comments |
| 173 | (modify-syntax-entry ?/ ". 12" table) |
| 174 | (modify-syntax-entry ?\n ">" table) |
| 175 | |
| 176 | table)) |
| 177 | |
| 178 | (defconst zig-keywords |
| 179 | '( |
| 180 | ;; Storage |
| 181 | "const" "var" "extern" "packed" "export" "pub" "noalias" "inline" |
| 182 | "noinline" "comptime" "callconv" "volatile" "allowzero" |
| 183 | "align" "linksection" "threadlocal" "addrspace" |
| 184 | |
| 185 | ;; Structure |
| 186 | "struct" "enum" "union" "error" "opaque" |
| 187 | |
| 188 | ;; Statement |
| 189 | "break" "return" "continue" "asm" "defer" "errdefer" "unreachable" |
| 190 | "try" "catch" "async" "nosuspend" "await" "suspend" "resume" |
| 191 | |
| 192 | ;; Conditional |
| 193 | "if" "else" "switch" "and" "or" "orelse" |
| 194 | |
| 195 | ;; Repeat |
| 196 | "while" "for" |
| 197 | |
| 198 | ;; Other keywords |
| 199 | "fn" "usingnamespace" "test")) |
| 200 | |
| 201 | (defconst zig-types |
| 202 | '( |
| 203 | ;; Integer types |
| 204 | "i2" "u2" "i3" "u3" "i4" "u4" "i5" "u5" "i6" "u6" "i7" "u7" "i8" "u8" |
| 205 | "i16" "u16" "i29" "u29" "i32" "u32" "i64" "u64" "i128" "u128" |
| 206 | "isize" "usize" |
| 207 | |
| 208 | ;; Floating types |
| 209 | "f16" "f32" "f64" "f80" "f128" |
| 210 | |
| 211 | ;; C types |
| 212 | "c_char" "c_short" "c_ushort" "c_int" "c_uint" "c_long" "c_ulong" |
| 213 | "c_longlong" "c_ulonglong" "c_longdouble" |
| 214 | |
| 215 | ;; Comptime types |
| 216 | "comptime_int" "comptime_float" |
| 217 | |
| 218 | ;; Other types |
| 219 | "bool" "void" "noreturn" "type" "error" "anyerror" "anyframe" "anytype" |
| 220 | "anyopaque")) |
| 221 | |
| 222 | (defconst zig-constants |
| 223 | '( |
| 224 | ;; Boolean |
| 225 | "true" "false" |
| 226 | |
| 227 | ;; Other constants |
| 228 | "null" "undefined")) |
| 229 | |
| 230 | (defconst zig-electric-indent-chars |
| 231 | '(?\; ?, ?\) ?\] ?})) |
| 232 | |
| 233 | |
| 234 | (defface zig-multiline-string-face |
| 235 | '((t :inherit font-lock-string-face)) |
| 236 | "Face for multiline string literals." |
| 237 | :group 'zig-mode) |
| 238 | |
| 239 | (defvar zig-font-lock-keywords |
| 240 | (append |
| 241 | `( |
| 242 | ;; Builtins (prefixed with @) |
| 243 | (,(concat "@" zig-re-identifier) . font-lock-builtin-face) |
| 244 | |
| 245 | ;; Keywords, constants and types |
| 246 | (,(regexp-opt zig-keywords 'symbols) . font-lock-keyword-face) |
| 247 | (,(regexp-opt zig-constants 'symbols) . font-lock-constant-face) |
| 248 | (,(regexp-opt zig-types 'symbols) . font-lock-type-face) |
| 249 | |
| 250 | ;; Type annotations (both variable and type) |
| 251 | (,zig-re-type-annotation 1 font-lock-variable-name-face) |
| 252 | (,zig-re-type-annotation 2 font-lock-type-face) |
| 253 | ) |
| 254 | |
| 255 | ;; Definitions |
| 256 | (mapcar (lambda (x) |
| 257 | (list (zig-re-definition (car x)) |
| 258 | 1 (cdr x))) |
| 259 | '(("const" . font-lock-variable-name-face) |
| 260 | ("var" . font-lock-variable-name-face) |
| 261 | ("fn" . font-lock-function-name-face))))) |
| 262 | |
| 263 | (defun zig-paren-nesting-level () (nth 0 (syntax-ppss))) |
| 264 | (defun zig-currently-in-str () (nth 3 (syntax-ppss))) |
| 265 | (defun zig-start-of-current-str-or-comment () (nth 8 (syntax-ppss))) |
| 266 | |
| 267 | (defun zig-skip-backwards-past-whitespace-and-comments () |
| 268 | (while (or |
| 269 | ;; If inside a comment, jump to start of comment. |
| 270 | (let ((start (zig-start-of-current-str-or-comment))) |
| 271 | (and start |
| 272 | (not (zig-currently-in-str)) |
| 273 | (goto-char start))) |
| 274 | ;; Skip backwards past whitespace and comment end delimiters. |
| 275 | (/= 0 (skip-syntax-backward " >"))))) |
| 276 | |
| 277 | (defun zig-in-str-or-cmnt () (nth 8 (syntax-ppss))) |
| 278 | (defconst zig-top-item-beg-re |
| 279 | (concat "^ *" |
| 280 | (regexp-opt |
| 281 | '("pub" "extern" "export" "")) |
| 282 | "[[:space:]]*" |
| 283 | (regexp-opt |
| 284 | '("fn" "test")) |
| 285 | "[[:space:]]+") |
| 286 | "Start of a Zig item.") |
| 287 | |
| 288 | (defun zig-beginning-of-defun (&optional arg) |
| 289 | "Move backward to the beginning of the current defun. |
| 290 | |
| 291 | With ARG, move backward multiple defuns. Negative ARG means |
| 292 | move forward. |
| 293 | |
| 294 | This is written mainly to be used as `beginning-of-defun-function' for Zig." |
| 295 | (interactive "p") |
| 296 | (let* ((arg (or arg 1)) |
| 297 | (magnitude (abs arg)) |
| 298 | (sign (if (< arg 0) -1 1))) |
| 299 | ;; If moving forward, don't find the defun we might currently be |
| 300 | ;; on. |
| 301 | (when (< sign 0) |
| 302 | (end-of-line)) |
| 303 | (catch 'done |
| 304 | (dotimes (_ magnitude) |
| 305 | ;; Search until we find a match that is not in a string or comment. |
| 306 | (while (if (re-search-backward (concat "^[[:space:]]*\\(" zig-top-item-beg-re "\\)") |
| 307 | nil 'move sign) |
| 308 | (zig-in-str-or-cmnt) |
| 309 | ;; Did not find it. |
| 310 | (throw 'done nil))))) |
| 311 | t)) |
| 312 | |
| 313 | (defun zig-end-of-defun () |
| 314 | "Move forward to the next end of defun. |
| 315 | |
| 316 | With argument, do it that many times. |
| 317 | Negative argument -N means move back to Nth preceding end of defun. |
| 318 | |
| 319 | Assume that this is called after `beginning-of-defun'. So point is |
| 320 | at the beginning of the defun body. |
| 321 | |
| 322 | This is written mainly to be used as `end-of-defun-function' for Zig." |
| 323 | (interactive) |
| 324 | |
| 325 | ;; Jump over the function parameters and paren-wrapped return, if they exist. |
| 326 | (while (re-search-forward "(" (line-end-position) t) |
| 327 | (progn |
| 328 | (backward-char) |
| 329 | (forward-sexp))) |
| 330 | |
| 331 | ;; Find the opening brace |
| 332 | (if (re-search-forward "[{]" nil t) |
| 333 | (progn |
| 334 | (goto-char (match-beginning 0)) |
| 335 | ;; Go to the closing brace |
| 336 | (condition-case nil |
| 337 | (forward-sexp) |
| 338 | (scan-error |
| 339 | (goto-char (point-max)))) |
| 340 | (end-of-line)) |
| 341 | ;; There is no opening brace, so consider the whole buffer to be one "defun" |
| 342 | (goto-char (point-max)))) |
| 343 | |
| 344 | (defun zig-mode-indent-line () |
| 345 | (interactive) |
| 346 | ;; First, calculate the column that this line should be indented to. |
| 347 | (let ((indent-col |
| 348 | (save-excursion |
| 349 | (back-to-indentation) |
| 350 | (let* (;; paren-level: How many sets of parens (or other delimiters) |
| 351 | ;; we're within, except that if this line closes the |
| 352 | ;; innermost set(s) (e.g. the line is just "}"), then we |
| 353 | ;; don't count those set(s). |
| 354 | (paren-level |
| 355 | (save-excursion |
| 356 | (while (looking-at "[]})]") (forward-char)) |
| 357 | (zig-paren-nesting-level))) |
| 358 | ;; prev-block-indent-col: If we're within delimiters, this is |
| 359 | ;; the column to which the start of that block is indented |
| 360 | ;; (if we're not, this is just zero). |
| 361 | (prev-block-indent-col |
| 362 | (if (<= paren-level 0) 0 |
| 363 | (save-excursion |
| 364 | (while (>= (zig-paren-nesting-level) paren-level) |
| 365 | (backward-up-list) |
| 366 | (back-to-indentation)) |
| 367 | (current-column)))) |
| 368 | ;; base-indent-col: The column to which a complete expression |
| 369 | ;; on this line should be indented. |
| 370 | (base-indent-col |
| 371 | (if (<= paren-level 0) |
| 372 | prev-block-indent-col |
| 373 | (or (save-excursion |
| 374 | (backward-up-list) |
| 375 | (forward-char) |
| 376 | (and (not (looking-at " *\\(//[^\n]*\\)?\n")) |
| 377 | (current-column))) |
| 378 | (+ prev-block-indent-col zig-indent-offset)))) |
| 379 | ;; is-expr-continutation: True if this line continues an |
| 380 | ;; expression from the previous line, false otherwise. |
| 381 | (is-expr-continutation |
| 382 | (and |
| 383 | (not (looking-at "[]});]\\|else")) |
| 384 | (save-excursion |
| 385 | (zig-skip-backwards-past-whitespace-and-comments) |
| 386 | (when (> (point) 1) |
| 387 | (backward-char) |
| 388 | (not (looking-at "[,;([{}]"))))))) |
| 389 | ;; Now we can calculate indent-col: |
| 390 | (if is-expr-continutation |
| 391 | (+ base-indent-col zig-indent-offset) |
| 392 | base-indent-col))))) |
| 393 | ;; If point is within the indentation whitespace, move it to the end of the |
| 394 | ;; new indentation whitespace (which is what the indent-line-to function |
| 395 | ;; always does). Otherwise, we don't want point to move, so we use a |
| 396 | ;; save-excursion. |
| 397 | (if (<= (current-column) (current-indentation)) |
| 398 | (indent-line-to indent-col) |
| 399 | (save-excursion (indent-line-to indent-col))))) |
| 400 | |
| 401 | (defun zig-syntax-propertize-to-newline-if-in-multiline-str (end) |
| 402 | ;; First, we need to check if we're in a multiline string literal; if we're |
| 403 | ;; not, do nothing. |
| 404 | (when (zig-currently-in-str) |
| 405 | (let ((start (zig-start-of-current-str-or-comment))) |
| 406 | (when (save-excursion |
| 407 | (goto-char start) |
| 408 | (looking-at "\\\\\\\\")) |
| 409 | ;; At this point, we've determined that we're within a multiline string |
| 410 | ;; literal. Let `stop' be the position of the closing newline, or |
| 411 | ;; `end', whichever comes first. |
| 412 | (let ((stop (if (save-excursion |
| 413 | (goto-char start) |
| 414 | (re-search-forward "\n" end t)) |
| 415 | (prog1 (match-end 0) |
| 416 | ;; We found the closing newline, so mark it as the |
| 417 | ;; end of this string literal. |
| 418 | (put-text-property (match-beginning 0) |
| 419 | (match-end 0) |
| 420 | 'syntax-table |
| 421 | (string-to-syntax "|"))) |
| 422 | end))) |
| 423 | ;; Zig multiline string literals don't support escapes, so mark all |
| 424 | ;; backslashes (up to `stop') as punctation instead of escapes. |
| 425 | (save-excursion |
| 426 | (goto-char (1+ start)) |
| 427 | (while (re-search-forward "\\\\" stop t) |
| 428 | (put-text-property (match-beginning 0) (match-end 0) |
| 429 | 'syntax-table (string-to-syntax ".")) |
| 430 | (goto-char (match-end 0)))) |
| 431 | ;; Move to the end of the string (or `end'), so that |
| 432 | ;; zig-syntax-propertize can pick up from there. |
| 433 | (goto-char stop)))))) |
| 434 | |
| 435 | (defun zig-syntax-propertize (start end) |
| 436 | (goto-char start) |
| 437 | (zig-syntax-propertize-to-newline-if-in-multiline-str end) |
| 438 | (funcall |
| 439 | (syntax-propertize-rules |
| 440 | ;; Multiline strings |
| 441 | ;; Do not match backslashes that are preceded by single or |
| 442 | ;; double-quotes. |
| 443 | ("[^\\'\"]c?\\(\\\\\\)\\\\" |
| 444 | (1 (prog1 "|" |
| 445 | (goto-char (match-end 0)) |
| 446 | (zig-syntax-propertize-to-newline-if-in-multiline-str end))))) |
| 447 | (point) end)) |
| 448 | |
| 449 | (defun zig-mode-syntactic-face-function (state) |
| 450 | (if (nth 3 state) |
| 451 | (save-excursion |
| 452 | (goto-char (nth 8 state)) |
| 453 | (if (looking-at "\\\\\\\\") |
| 454 | 'zig-multiline-string-face |
| 455 | 'font-lock-string-face)) |
| 456 | (save-excursion |
| 457 | (goto-char (nth 8 state)) |
| 458 | (if (looking-at "//[/|!][^/]") |
| 459 | 'font-lock-doc-face |
| 460 | 'font-lock-comment-face)))) |
| 461 | |
| 462 | ;;; Imenu support |
| 463 | (defun zig-re-structure-def-imenu (stype) |
| 464 | "Construct a regular expression for strucutres definitions of type STYPE." |
| 465 | (concat (zig-re-word "const") "[[:space:]]+" |
| 466 | (zig-re-grab zig-re-identifier) |
| 467 | ".*" |
| 468 | (zig-re-word stype))) |
| 469 | |
| 470 | (defvar zig-imenu-generic-expression |
| 471 | (append (mapcar (lambda (x) |
| 472 | (list (capitalize x) (zig-re-structure-def-imenu x) 1)) |
| 473 | '("enum" "struct" "union")) |
| 474 | `(("Fn" ,(zig-re-definition "fn") 1)))) |
| 475 | |
| 476 | ;;; Guarantee filesystem unix line endings |
| 477 | (defun zig-file-coding-system () |
| 478 | (with-current-buffer (current-buffer) |
| 479 | (if (buffer-file-name) |
| 480 | (if (string-match "\\.d?zig\\'" buffer-file-name) |
| 481 | (setq buffer-file-coding-system 'utf-8-unix) |
| 482 | nil)) |
| 483 | )) |
| 484 | |
| 485 | (add-hook 'zig-mode-hook 'zig-file-coding-system) |
| 486 | |
| 487 | (defvar zig-mode-map |
| 488 | (let ((map (make-sparse-keymap))) |
| 489 | (define-key map (kbd "C-c C-b") 'zig-compile) |
| 490 | (define-key map (kbd "C-c C-f") 'zig-format-buffer) |
| 491 | (define-key map (kbd "C-c C-r") 'zig-run) |
| 492 | (define-key map (kbd "C-c C-t") 'zig-test-buffer) |
| 493 | map) |
| 494 | "Keymap for Zig major mode.") |
| 495 | |
| 496 | ;;;###autoload |
| 497 | (define-derived-mode zig-mode prog-mode "Zig" |
| 498 | "A major mode for the Zig programming language. |
| 499 | |
| 500 | \\{zig-mode-map}" |
| 501 | :group 'zig-mode |
| 502 | (setq-local comment-start "// ") |
| 503 | (setq-local comment-start-skip "//+ *") |
| 504 | (setq-local comment-end "") |
| 505 | (setq-local electric-indent-chars |
| 506 | (append zig-electric-indent-chars |
| 507 | (and (boundp 'electric-indent-chars) |
| 508 | electric-indent-chars))) |
| 509 | (setq-local beginning-of-defun-function 'zig-beginning-of-defun) |
| 510 | (setq-local end-of-defun-function 'zig-end-of-defun) |
| 511 | (setq-local indent-line-function 'zig-mode-indent-line) |
| 512 | (setq-local indent-tabs-mode nil) ; Zig forbids tab characters. |
| 513 | (setq-local syntax-propertize-function 'zig-syntax-propertize) |
| 514 | (setq-local imenu-generic-expression zig-imenu-generic-expression) |
| 515 | (setq font-lock-defaults '(zig-font-lock-keywords |
| 516 | nil nil nil nil |
| 517 | (font-lock-syntactic-face-function . zig-mode-syntactic-face-function))) |
| 518 | |
| 519 | (when zig-format-on-save |
| 520 | (zig-format-on-save-mode 1))) |
| 521 | |
| 522 | ;;;###autoload |
| 523 | (add-to-list 'auto-mode-alist '("\\.\\(zig\\|zon\\)\\'" . zig-mode)) |
| 524 | |
| 525 | (provide 'zig-mode) |
| 526 | ;;; zig-mode.el ends here |