branch: elpa/zig-mode commit b7731275e12ccb5cfea92153b9cc6ea8effc4bab Author: Matthew D. Steele <mdste...@alum.mit.edu> Commit: Matthew D. Steele <mdste...@alum.mit.edu>
Initial implementation of indent-line-function --- run_tests.sh | 9 ++++ tests.el | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ zig-mode.el | 74 ++++++++++++++++++++++++++++---- 3 files changed, 211 insertions(+), 9 deletions(-) diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..bbb57f0 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ -z "${EMACS}" ]; then + EMACS="emacs" +else + echo "Running with EMACS=${EMACS}" +fi + +${EMACS} --batch -l zig-mode.el -l tests.el -f ert-run-tests-batch-and-exit diff --git a/tests.el b/tests.el new file mode 100644 index 0000000..9086175 --- /dev/null +++ b/tests.el @@ -0,0 +1,137 @@ +;; Tests for zig-mode. + +(require 'ert) +(require 'zig-mode) + +;;===========================================================================;; +;; Indentation tests + +(defun zig-test-indent-region (original expected) + (with-temp-buffer + (zig-mode) + (insert original) + (indent-region 1 (+ 1 (buffer-size))) + (should (equal expected (buffer-string))))) + +(ert-deftest test-indent-top-level () + (zig-test-indent-region + " const four = 4;" + "const four = 4;")) + +(ert-deftest test-indent-fn-def-body () + (zig-test-indent-region + " +pub fn plus1(value: u32) u32 { +return value + 1; +}" + " +pub fn plus1(value: u32) u32 { + return value + 1; +}")) + +(ert-deftest test-indent-fn-def-args () + (zig-test-indent-region + " +pub fn add(value1: u32, +value2: u32) u32 { +return value1 + value2; +}" + " +pub fn add(value1: u32, + value2: u32) u32 { + return value1 + value2; +}")) + +(ert-deftest test-indent-fn-call-args () + (zig-test-indent-region + " +blarg(foo, +foo + bar + baz + +quux, +quux);" + " +blarg(foo, + foo + bar + baz + + quux, + quux);")) + +(ert-deftest test-indent-if-else () + (zig-test-indent-region + " +fn sign(value: i32) i32 { +if (value > 0) return 1; +else if (value < 0) { +return -1; +} else { +return 0; +} +}" + " +fn sign(value: i32) i32 { + if (value > 0) return 1; + else if (value < 0) { + return -1; + } else { + return 0; + } +}")) + +(ert-deftest test-indent-struct () + (zig-test-indent-region + " +const Point = struct { +x: f32, +y: f32, +}; +const origin = Point { +.x = 0.0, +.y = 0.0, +};" + " +const Point = struct { + x: f32, + y: f32, +}; +const origin = Point { + .x = 0.0, + .y = 0.0, +};")) + +(ert-deftest test-indent-multiline-str-literal () + (zig-test-indent-region + " +const code = +\\\\const foo = []u32{ +\\\\ 12345, +\\\\}; +;" + " +const code = + \\\\const foo = []u32{ + \\\\ 12345, + \\\\}; +;")) + +(ert-deftest test-indent-array-literal-1 () + (zig-test-indent-region + " +const msgs = [][]u8{ +\"hello\", +\"goodbye\", +};" + " +const msgs = [][]u8{ + \"hello\", + \"goodbye\", +};")) + +(ert-deftest test-indent-array-literal-2 () + (zig-test-indent-region + " +const msg = []u8{'h', 'e', 'l', 'l', 'o', +'w', 'o', 'r', 'l', 'd'};" + " +const msg = []u8{'h', 'e', 'l', 'l', 'o', + 'w', 'o', 'r', 'l', 'd'};")) + +;;===========================================================================;; diff --git a/zig-mode.el b/zig-mode.el index 221a3a4..36bd031 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.6 +;; Version: 0.0.7 ;; Author: Andrea Orru <andreaorru1...@gmail.com>, Andrew Kelley <superjo...@gmail.com> ;; Keywords: zig, languages ;; Package-Requires: ((emacs "24")) @@ -24,8 +24,6 @@ ;;; Code: -(require 'cc-mode) - (defun zig-re-word (inner) "Construct a regular expression for the word INNER." (concat "\\<" inner "\\>")) @@ -104,7 +102,6 @@ ;; Other types "bool" "void" "noreturn" "type" "error" "promise")) - (defconst zig-constants '( ;; Boolean @@ -113,6 +110,21 @@ ;; Other constants "null" "undefined" "this")) +(defgroup zig-mode nil + "Support for Zig code." + :link '(url-link "https://ziglang.org/") + :group 'languages) + +(defcustom zig-indent-offset 4 + "Indent Zig code by this number of spaces." + :type 'integer + :group 'zig-mode + :safe #'integerp) + +(defface zig-multiline-string-face + '((t :inherit font-lock-string-face)) + "Face for multiline string literals." + :group 'zig-mode) (defvar zig-font-lock-keywords (append @@ -138,9 +150,48 @@ ("var" . font-lock-variable-name-face) ("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))) +(defun zig-skip-backwards-past-whitespace-and-comments () + (while (or + ;; If inside a comment, jump to start of comment. + (let ((start (zig-start-of-current-str-or-comment))) + (and start + (not (zig-currently-in-str)) + (goto-char start))) + ;; Skip backwards past whitespace and comment end delimiters. + (/= 0 (skip-syntax-backward " >"))))) + +(defun zig-mode-indent-line () + (interactive) + (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 ";")) + (save-excursion + (zig-skip-backwards-past-whitespace-and-comments) + (when (> (point) 1) + (backward-char) + (not (looking-at "[,;([{}]"))))) + zig-indent-offset 0)))))) + (if (<= (current-column) (current-indentation)) + (indent-line-to indent-col) + (save-excursion (indent-line-to indent-col))))) + (defun zig-syntax-propertize-newline-if-in-multiline-str (end) (when (and (zig-currently-in-str) (save-excursion @@ -164,7 +215,12 @@ (point) end)) (defun zig-mode-syntactic-face-function (state) - (if (nth 3 state) 'font-lock-string-face + (if (nth 3 state) + (save-excursion + (goto-char (nth 8 state)) + (if (looking-at "\\\\\\\\") + 'zig-multiline-string-face + 'font-lock-string-face)) (save-excursion (goto-char (nth 8 state)) (if (looking-at "///[^/]") @@ -172,12 +228,12 @@ 'font-lock-comment-face)))) ;;;###autoload -(define-derived-mode zig-mode c-mode "Zig" - "A major mode for the zig programming language." - (set (make-local-variable 'c-basic-offset) 4) - (set (make-local-variable 'c-syntactic-indentation) nil) +(define-derived-mode zig-mode prog-mode "Zig" + "A major mode for the Zig programming language." (setq-local comment-start "// ") (setq-local comment-end "") + (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) (setq font-lock-defaults '(zig-font-lock-keywords nil nil nil nil