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

Reply via email to