branch: elpa/zig-mode commit 3778fb55ca675f1eaa5cc85f941ef952a2daa5f4 Author: Matthew D. Steele <mdste...@alum.mit.edu> Commit: Matthew D. Steele <mdste...@alum.mit.edu>
Make indentation more nuanced (and add more tests for it) --- README.md | 18 ++++++++++----- tests.el | 56 ++++++++++++++++++++++++++++++++++++++++++++++ zig-mode.el | 74 +++++++++++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 124 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index be2ab54..28b851c 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,22 @@ # zig-mode -Syntax highlighting for the [Zig programming language](http://ziglang.org) in Emacs. + +Syntax highlighting and automatic indentation for the +[Zig programming language](http://ziglang.org) in Emacs. Requires Emacs 24 or +later. ## Installation -Simply install the `zig-mode` package via [MELPA](https://melpa.org/#/getting-started). + +[](https://melpa.org/#/zig-mode) + +Simply install the `zig-mode` package via +[MELPA](https://melpa.org/#/getting-started). Alternatively, you can `git clone` the `zig-mode` repository somewhere (e.g. under your `~/.emacs.d/`), then add the following to your `.emacs` file: ```elisp -(add-to-list 'load-path "~/path/to/your/zig-mode/") -(autoload 'zig-mode "zig-mode" nil t) -(add-to-list 'auto-mode-alist '("\\.zig\\'" . zig-mode)) +(unless (version< emacs-version "24") + (add-to-list 'load-path "~/path/to/your/zig-mode/") + (autoload 'zig-mode "zig-mode" nil t) + (add-to-list 'auto-mode-alist '("\\.zig\\'" . zig-mode))) ``` diff --git a/tests.el b/tests.el index f5f7fb2..304103e 100644 --- a/tests.el +++ b/tests.el @@ -103,6 +103,29 @@ const python = ;;===========================================================================;; ;; Indentation tests +(defun zig-test-indent-line (line-number original expected-line) + (with-temp-buffer + (zig-mode) + (insert original) + (goto-line line-number) + (indent-for-tab-command) + (let* ((current-line (thing-at-point 'line t)) + (stripped-line (replace-regexp-in-string "\n\\'" "" current-line))) + (should (equal expected-line stripped-line))))) + +(ert-deftest test-indent-from-current-block () + (zig-test-indent-line + 6 + " +{ + // Normally, zig-mode indents to 4, but suppose + // someone indented this part to 2 for some reason. + { + // This line should get indented to 6, not 8. + } +}" + " // This line should get indented to 6, not 8.")) + (defun zig-test-indent-region (original expected) (with-temp-buffer (zig-mode) @@ -231,4 +254,37 @@ const msg = []u8{'h', 'e', 'l', 'l', 'o', const msg = []u8{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'};")) +(ert-deftest test-indent-paren-block () + (zig-test-indent-region + " +const foo = ( +some_very_long + expression_that_is * set_off_in_parens +);" + " +const foo = ( + some_very_long + expression_that_is * set_off_in_parens +);")) + +(ert-deftest test-indent-double-paren-block () + (zig-test-indent-region + " +const foo = (( +this_expression_is + set_off_in_double_parens * for_some_reason +));" + " +const foo = (( + this_expression_is + set_off_in_double_parens * for_some_reason +));")) + +(ert-deftest test-indent-with-comment-after-open-brace () + (zig-test-indent-region + " +if (false) { // This comment shouldn't mess anything up. +launchTheMissiles(); +}" + " +if (false) { // This comment shouldn't mess anything up. + launchTheMissiles(); +}")) + ;;===========================================================================;; diff --git a/zig-mode.el b/zig-mode.el index 2a3a737..f78080a 100644 --- a/zig-mode.el +++ b/zig-mode.el @@ -1,6 +1,6 @@ ;;; zig-mode.el --- A major mode for the Zig programming language -*- lexical-binding: t -*- -;; Version: 0.0.7 +;; Version: 0.0.8 ;; Author: Andrea Orru <andreaorru1...@gmail.com>, Andrew Kelley <superjo...@gmail.com> ;; Keywords: zig, languages ;; Package-Requires: ((emacs "24")) @@ -110,6 +110,9 @@ ;; Other constants "null" "undefined" "this")) +(defconst zig-electric-indent-chars + '( ?\; ?, ?) ?] ?} )) + (defgroup zig-mode nil "Support for Zig code." :link '(url-link "https://ziglang.org/") @@ -151,7 +154,6 @@ ("fn" . font-lock-function-name-face))))) (defun zig-paren-nesting-level () (nth 0 (syntax-ppss))) -(defun zig-prev-open-paren-pos () (car (last (nth 9 (syntax-ppss))))) (defun zig-currently-in-str () (nth 3 (syntax-ppss))) (defun zig-start-of-current-str-or-comment () (nth 8 (syntax-ppss))) @@ -167,27 +169,57 @@ (defun zig-mode-indent-line () (interactive) + ;; First, calculate the column that this line should be indented to. (let ((indent-col (save-excursion (back-to-indentation) - (let ((paren-level - (let ((level (zig-paren-nesting-level))) - (if (looking-at "[]})]") (1- level) level)))) - (+ (if (<= paren-level 0) - 0 - (or (save-excursion - (goto-char (1+ (zig-prev-open-paren-pos))) - (and (not (looking-at "\n")) - (current-column))) - (* zig-indent-offset paren-level))) - (if (and - (not (looking-at ";")) + (let* (;; paren-level: How many sets of parens (or other delimiters) + ;; we're within, except that if this line closes the + ;; innermost set(s) (e.g. the line is just "}"), then we + ;; don't count those set(s). + (paren-level + (save-excursion + (while (looking-at "[]})]") (forward-char)) + (zig-paren-nesting-level))) + ;; prev-block-indent-col: If we're within delimiters, this is + ;; the column to which the start of that block is indented + ;; (if we're not, this is just zero). + (prev-block-indent-col + (if (<= paren-level 0) 0 (save-excursion - (zig-skip-backwards-past-whitespace-and-comments) - (when (> (point) 1) - (backward-char) - (not (looking-at "[,;([{}]"))))) - zig-indent-offset 0)))))) + (while (>= (zig-paren-nesting-level) paren-level) + (backward-up-list) + (back-to-indentation)) + (current-column)))) + ;; base-indent-col: The column to which a complete expression + ;; on this line should be indented. + (base-indent-col + (if (<= paren-level 0) + prev-block-indent-col + (or (save-excursion + (backward-up-list) + (forward-char) + (and (not (looking-at " *\\(//[^\n]*\\)?\n")) + (current-column))) + (+ prev-block-indent-col zig-indent-offset)))) + ;; is-expr-continutation: True if this line continues an + ;; expression from the previous line, false otherwise. + (is-expr-continutation + (and + (not (looking-at "[]});]")) + (save-excursion + (zig-skip-backwards-past-whitespace-and-comments) + (when (> (point) 1) + (backward-char) + (not (looking-at "[,;([{}]"))))))) + ;; Now we can calculate indent-col: + (if is-expr-continutation + (+ base-indent-col zig-indent-offset) + base-indent-col))))) + ;; If point is within the indentation whitespace, move it to the end of the + ;; new indentation whitespace (which is what the indent-line-to function + ;; always does). Otherwise, we don't want point to move, so we use a + ;; save-excursion. (if (<= (current-column) (current-indentation)) (indent-line-to indent-col) (save-excursion (indent-line-to indent-col))))) @@ -256,6 +288,10 @@ "A major mode for the Zig programming language." (setq-local comment-start "// ") (setq-local comment-end "") + (setq-local electric-indent-chars + (append zig-electric-indent-chars + (and (boundp 'electric-indent-chars) + electric-indent-chars))) (setq-local indent-line-function 'zig-mode-indent-line) (setq-local indent-tabs-mode nil) ; Zig forbids tab characters. (setq-local syntax-propertize-function 'zig-syntax-propertize)