From 581b3db3b3f6a932f3a1d99167b3b1910f76beb1 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Wed, 24 Jul 2013 17:11:04 -0700 Subject: [PATCH] rewrite rust-mode to use font-lock-mode and emacs builtin syntax analysis --- src/etc/emacs/Makefile | 14 - src/etc/emacs/README.md | 35 +-- src/etc/emacs/cm-mode.el | 194 -------------- src/etc/emacs/rust-mode.el | 523 +++++++++++++++---------------------- 4 files changed, 209 insertions(+), 557 deletions(-) delete mode 100644 src/etc/emacs/Makefile delete mode 100644 src/etc/emacs/cm-mode.el diff --git a/src/etc/emacs/Makefile b/src/etc/emacs/Makefile deleted file mode 100644 index c79e7a9719bdc..0000000000000 --- a/src/etc/emacs/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -E=@echo -TEMP=temp.el - -EMACS ?= emacs - -all: $(TEMP) - $(EMACS) -batch -q -no-site-file -l ./$(TEMP) -f rustmode-compile - rm -f $(TEMP) -$(TEMP): - $(E) '(setq load-path (cons "." load-path))' >> $(TEMP) - $(E) '(defun rustmode-compile () (mapcar (lambda (x) (byte-compile-file x))' >> $(TEMP) - $(E) ' (list "cm-mode.el" "rust-mode.el")))' >> $(TEMP) -clean: - rm -f *.elc $(TEMP) diff --git a/src/etc/emacs/README.md b/src/etc/emacs/README.md index 02c2c248d3625..508ac7f1af27f 100644 --- a/src/etc/emacs/README.md +++ b/src/etc/emacs/README.md @@ -13,21 +13,8 @@ file: (add-to-list 'load-path "/path/to/rust-mode/") (require 'rust-mode) -Make sure you byte-compile the .el files first, or the mode will be -painfully slow. There is an included `Makefile` which will do it for -you, so in the simplest case you can just run `make` and everything -should Just Work. - -If for some reason that doesn't work, you can byte compile manually, -by pasting this in your `*scratch*` buffer, moving the cursor below -it, and pressing `C-j`: - - (progn - (byte-compile-file "/path/to/rust-mode/cm-mode.el" t) - (byte-compile-file "/path/to/rust-mode/rust-mode.el" t)) - -Rust mode will automatically be associated with .rs and .rc files. To -enable it explicitly, do `M-x rust-mode`. +Rust mode will automatically be associated with .rs files. To enable it +explicitly, do `M-x rust-mode`. ### package.el installation via Marmalade or MELPA @@ -67,24 +54,6 @@ should upgrade in order to support installation from multiple sources. The ELPA archive is deprecated and no longer accepting new packages, so the version there (1.7.1) is very outdated. -#### Important - -In order to have cm-mode properly initialized after compilation prior -to rust-mode.el compilation you will need to add these `advices` to -your init file or if you are a melpa user install the `melpa` package. - -```lisp -(defadvice package-download-tar - (after package-download-tar-initialize activate compile) - "initialize the package after compilation" - (package-initialize)) - -(defadvice package-download-single - (after package-download-single-initialize activate compile) - "initialize the package after compilation" - (package-initialize)) -``` - #### Install rust-mode From there you can install rust-mode or any other modes by choosing diff --git a/src/etc/emacs/cm-mode.el b/src/etc/emacs/cm-mode.el deleted file mode 100644 index 0303f994172cb..0000000000000 --- a/src/etc/emacs/cm-mode.el +++ /dev/null @@ -1,194 +0,0 @@ -;;; cm-mode.el --- Wrapper for CodeMirror-style Emacs modes - -;; Version: 0.1.0 -;; Author: Mozilla -;; Url: https://github.com/mozilla/rust -;; Highlighting is done by running a stateful parser (with first-class -;; state object) over the buffer, line by line, using the output to -;; add 'face properties, and storing the parser state at the end of -;; each line. Indentation is done based on the parser state at the -;; start of the line. - -(eval-when-compile (require 'cl)) - -;; Mode data structure - -(defun make-cm-mode (token &optional start-state copy-state - compare-state indent) - (vector token - (or start-state (lambda () 'null)) - (or copy-state 'cm-default-copy-state) - (or compare-state 'eq) - indent)) -(defmacro cm-mode-token (x) `(aref ,x 0)) -(defmacro cm-mode-start-state (x) `(aref ,x 1)) -(defmacro cm-mode-copy-state (x) `(aref ,x 2)) -(defmacro cm-mode-compare-state (x) `(aref ,x 3)) -(defmacro cm-mode-indent (x) `(aref ,x 4)) - -(defvar cm-cur-mode nil) -(defvar cm-worklist nil) - -(defun cm-default-copy-state (state) - (if (consp state) (copy-sequence state) state)) - -(defun cm-clear-work-items (from to) - (let ((prev-cons nil) - (rem cm-worklist)) - (while rem - (let ((pos (marker-position (car rem)))) - (cond ((or (< pos from) (> pos to)) (setf prev-cons rem)) - (prev-cons (setf (cdr prev-cons) (cdr rem))) - (t (setf cm-worklist (cdr rem)))) - (setf rem (cdr rem)))))) - -(defun cm-min-worklist-item () - (let ((rest cm-worklist) (min most-positive-fixnum)) - (while rest - (let ((pos (marker-position (car rest)))) - (when (< pos min) (setf min pos))) - (setf rest (cdr rest))) - min)) - -;; Indentation - -(defun cm-indent () - (let (indent-pos) - (save-excursion - (beginning-of-line) - (let* ((buf (current-buffer)) - (state (cm-preserve-state buf 'cm-state-for-point)) - (old-indent (current-indentation))) - (back-to-indentation) - (setf indent-pos (point)) - (let ((new-indent (funcall (cm-mode-indent cm-cur-mode) state))) - (unless (= old-indent new-indent) - (indent-line-to new-indent) - (setf indent-pos (point)) - (beginning-of-line) - (cm-preserve-state buf - (lambda () - (cm-highlight-line state) - (when (< (point) (point-max)) - (put-text-property (point) (+ (point) 1) 'cm-parse-state state)))))))) - (when (< (point) indent-pos) - (goto-char indent-pos)))) - -(defun cm-backtrack-to-state () - (let ((backtracked 0) - (min-indent most-positive-fixnum) - min-indented) - (loop - (when (= (point) (point-min)) - (return (funcall (cm-mode-start-state cm-cur-mode)))) - (let ((st (get-text-property (- (point) 1) 'cm-parse-state))) - (when (and st (save-excursion - (backward-char) - (beginning-of-line) - (not (looking-at "[ ]*$")))) - (return (funcall (cm-mode-copy-state cm-cur-mode) st)))) - (let ((i (current-indentation))) - (when (< i min-indent) - (setf min-indent i min-indented (point)))) - (when (> (incf backtracked) 30) - (goto-char min-indented) - (return (funcall (cm-mode-start-state cm-cur-mode)))) - (forward-line -1)))) - -(defun cm-state-for-point () - (let ((pos (point)) - (state (cm-backtrack-to-state))) - (while (< (point) pos) - (cm-highlight-line state) - (put-text-property (point) (+ (point) 1) 'cm-parse-state - (funcall (cm-mode-copy-state cm-cur-mode) state)) - (forward-char)) - state)) - -;; Highlighting - -(defun cm-highlight-line (state) - (let ((eol (point-at-eol))) - (remove-text-properties (point) eol '(face)) - (loop - (let ((p (point))) - (when (= p eol) (return)) - (let ((style (funcall (cm-mode-token cm-cur-mode) state))) - (when (= p (point)) (print (point)) (error "Nothing consumed.")) - (when (> p eol) (error "Parser moved past EOL")) - (when style - (put-text-property p (point) 'face style))))))) - -(defun cm-find-state-before-point () - (loop - (beginning-of-line) - (when (= (point) 1) - (return (funcall (cm-mode-start-state cm-cur-mode)))) - (let ((cur (get-text-property (- (point) 1) 'cm-parse-state))) - (when cur (return (funcall (cm-mode-copy-state cm-cur-mode) cur)))) - (backward-char))) - -(defun cm-schedule-work (delay) - (run-with-idle-timer delay nil 'cm-preserve-state (current-buffer) 'cm-do-some-work)) - -(defun cm-preserve-state (buffer f &rest args) - (with-current-buffer buffer - (let ((modified (buffer-modified-p)) - (buffer-undo-list t) - (inhibit-read-only t) - (inhibit-point-motion-hooks t) - (inhibit-modification-hooks t)) - (unwind-protect (apply f args) - (unless modified - (restore-buffer-modified-p nil)))))) - -(defun cm-do-some-work-inner () - (let ((end-time (time-add (current-time) (list 0 0 500))) - (quitting nil)) - (while (and (not quitting) cm-worklist) - (goto-char (cm-min-worklist-item)) - (let ((state (cm-find-state-before-point)) - (startpos (point)) - (timer-idle-list nil)) - (loop - (cm-highlight-line state) - (when (= (point) (point-max)) (return)) - (let ((old (get-text-property (point) 'cm-parse-state))) - (when (and old (funcall (cm-mode-compare-state cm-cur-mode) state old)) - (return)) - (put-text-property (point) (+ (point) 1) 'cm-parse-state - (funcall (cm-mode-copy-state cm-cur-mode) state))) - (when (or (let ((timer-idle-list nil)) (input-pending-p)) - (time-less-p end-time (current-time))) - (setf quitting t) (return)) - (forward-char)) - (cm-clear-work-items startpos (point))) - (when quitting - (push (copy-marker (+ (point) 1)) cm-worklist) - (cm-schedule-work 0.05))))) - -(defun cm-do-some-work () - (save-excursion - (condition-case cnd (cm-do-some-work-inner) - (error (print cnd) (error cnd))))) - -(defun cm-after-change-function (from to oldlen) - (cm-preserve-state (current-buffer) 'remove-text-properties from to '(cm-parse-state)) - (push (copy-marker from) cm-worklist) - (cm-schedule-work 0.2)) - -;; Entry function - -;;;###autoload -(defun cm-mode (mode) - (set (make-local-variable 'cm-cur-mode) mode) - (set (make-local-variable 'cm-worklist) (list (copy-marker 1))) - (when (cm-mode-indent mode) - (set (make-local-variable 'indent-line-function) 'cm-indent)) - (add-hook 'after-change-functions 'cm-after-change-function t t) - (add-hook 'after-revert-hook (lambda () (cm-after-change-function 1 (point-max) nil)) t t) - (cm-schedule-work 0.05)) - -(provide 'cm-mode) - -;;; cm-mode.el ends here diff --git a/src/etc/emacs/rust-mode.el b/src/etc/emacs/rust-mode.el index a1b8423ae3b87..106cdbfd5f426 100644 --- a/src/etc/emacs/rust-mode.el +++ b/src/etc/emacs/rust-mode.el @@ -1,331 +1,222 @@ ;;; rust-mode.el --- A major emacs mode for editing Rust source code -;; Version: 0.1.0 +;; Version: 0.2.0 ;; Author: Mozilla -;; Package-Requires: ((cm-mode "0.1.0")) ;; Url: https://github.com/mozilla/rust -(require 'cm-mode) -(require 'cc-mode) (eval-when-compile (require 'cl)) -(defun rust-electric-brace (arg) - (interactive "*P") - (self-insert-command (prefix-numeric-value arg)) - (when (and c-electric-flag - (not (member (get-text-property (point) 'face) - '(font-lock-comment-face font-lock-string-face)))) - (cm-indent))) - -(defcustom rust-capitalized-idents-are-types t - "If non-nil, capitalized identifiers will be treated as types for the purposes of font-lock mode" - :type 'boolean - :require 'rust-mode - :group 'rust-mode) - -(defcustom rust-indent-unit 4 - "Amount of offset per level of indentation" - :type 'integer - :require 'rust-mode - :group 'rust-mode) - -(defvar rust-syntax-table (let ((table (make-syntax-table))) - (c-populate-syntax-table table) - table)) - -(defun make-rust-state () - (vector 'rust-token-base - (list (vector 'top (- rust-indent-unit) nil nil nil)) - 0 - nil)) -(defmacro rust-state-tokenize (x) `(aref ,x 0)) -(defmacro rust-state-context (x) `(aref ,x 1)) -(defmacro rust-state-indent (x) `(aref ,x 2)) -(defmacro rust-state-last-token (x) `(aref ,x 3)) - -(defmacro rust-context-type (x) `(aref ,x 0)) -(defmacro rust-context-indent (x) `(aref ,x 1)) -(defmacro rust-context-column (x) `(aref ,x 2)) -(defmacro rust-context-align (x) `(aref ,x 3)) -(defmacro rust-context-info (x) `(aref ,x 4)) - -(defun rust-push-context (st type &optional align-column auto-align) - (let ((ctx (vector type (rust-state-indent st) align-column - (if align-column (if auto-align t 'unset) nil) nil))) - (push ctx (rust-state-context st)) - ctx)) -(defun rust-pop-context (st) - (let ((old (pop (rust-state-context st)))) - (setf (rust-state-indent st) (rust-context-indent old)) - old)) -(defun rust-dup-context (st) - (let* ((list (rust-state-context st)) - (dup (copy-sequence (car list)))) - (setf (rust-state-context st) (cons dup (cdr list))) - dup)) - -(defvar rust-operator-chars "-+/%=<>!*&|@~^") -(defvar rust-punc-chars "()[].,{}:;") -(defvar rust-value-keywords - (let ((table (make-hash-table :test 'equal))) - (dolist (word '("mod" "const" "class" "type" - "trait" "struct" "fn" "enum" - "impl")) - (puthash word 'def table)) - (dolist (word '("as" "break" - "copy" "do" "drop" "else" - "extern" "for" "if" "let" "log" - "loop" "once" "priv" "pub" "pure" - "ref" "return" "static" "unsafe" "use" - "while" "while" - "assert" - "mut")) - (puthash word t table)) - (puthash "match" 'alt table) - (dolist (word '("self" "true" "false")) (puthash word 'atom table)) +;; Syntax definitions and helpers +(defvar rust-mode-syntax-table + (let ((table (make-syntax-table))) + + ;; Operators + (loop for i in '(?+ ?- ?* ?/ ?& ?| ?^ ?! ?< ?> ?~ ?@) + do (modify-syntax-entry i "." table)) + + ;; Strings + (modify-syntax-entry ?\" "\"" table) + (modify-syntax-entry ?\\ "\\" table) + + ;; _ is a word-char + (modify-syntax-entry ?_ "w" table) + + ;; Comments + (modify-syntax-entry ?/ ". 124b" table) + (modify-syntax-entry ?* ". 23" table) + (modify-syntax-entry ?\n "> b" table) + (modify-syntax-entry ?\^m "> b" table) + table)) -;; FIXME type-context keywords - -(defvar rust-tcat nil "Kludge for multiple returns without consing") - -(defmacro rust-eat-re (re) - `(when (looking-at ,re) (goto-char (match-end 0)) t)) - -(defvar rust-char-table - (let ((table (make-char-table 'syntax-table))) - (macrolet ((def (range &rest body) - `(let ((--b (lambda (st) ,@body))) - ,@(mapcar (lambda (elt) - (if (consp elt) - `(loop for ch from ,(car elt) to ,(cdr elt) collect - (set-char-table-range table ch --b)) - `(set-char-table-range table ',elt --b))) - (if (consp range) range (list range)))))) - (def t (forward-char) nil) - (def (32 ?\t) (skip-chars-forward " \t") nil) - (def ?\" (forward-char) - (rust-push-context st 'string (current-column) t) - (setf (rust-state-tokenize st) 'rust-token-string) - (rust-token-string st)) - (def ?\' (rust-single-quote)) - (def ?/ (forward-char) - (case (char-after) - (?/ (end-of-line) 'font-lock-comment-face) - (?* (forward-char) - (rust-push-context st 'comment) - (setf (rust-state-tokenize st) 'rust-token-comment) - (rust-token-comment st)) - (t (skip-chars-forward rust-operator-chars) (setf rust-tcat 'op) nil))) - (def ?# (forward-char) - (cond ((eq (char-after) ?\[) (forward-char) (setf rust-tcat 'open-attr)) - ((rust-eat-re "[a-z_]+") (setf rust-tcat 'macro))) - 'font-lock-preprocessor-face) - (def ((?a . ?z) (?A . ?Z) ?_) - (rust-token-identifier)) - (def ((?0 . ?9)) - (rust-eat-re "0x[0-9a-fA-F_]+\\|0b[01_]+\\|[0-9_]+\\(\\.[0-9_]+\\)?\\(e[+\\-]?[0-9_]+\\)?") - (setf rust-tcat 'atom) - (rust-eat-re "[iuf][0-9_]*") - 'font-lock-constant-face) - (def ?. (forward-char) - (cond ((rust-eat-re "[0-9]+\\(e[+\\-]?[0-9]+\\)?") - (setf rust-tcat 'atom) - (rust-eat-re "f[0-9]+") - 'font-lock-constant-face) - (t (setf rust-tcat (char-before)) nil))) - (def (?\( ?\) ?\[ ?\] ?\{ ?\} ?: ?\; ?,) - (forward-char) - (setf rust-tcat (char-before)) nil) - (def ?| - (skip-chars-forward rust-operator-chars) - (setf rust-tcat 'pipe) nil) - (def (?+ ?- ?% ?= ?< ?> ?! ?* ?& ?@ ?~) - (skip-chars-forward rust-operator-chars) - (setf rust-tcat 'op) nil) - table))) - -(defun rust-token-identifier () - (rust-eat-re "[a-zA-Z_][a-zA-Z0-9_]*") - (setf rust-tcat 'ident) - (if (and (eq (char-after) ?:) (eq (char-after (+ (point) 1)) ?:) - (not (eq (char-after (+ (point) 2)) ?:))) - (progn (forward-char 2) 'font-lock-builtin-face) - (match-string 0))) - -(defun rust-single-quote () - (forward-char) - (setf rust-tcat 'atom) - ; Is this a lifetime? - (if (or (looking-at "[a-zA-Z_]$") - (looking-at "[a-zA-Z_][^']")) - ; If what we see is 'abc, use font-lock-builtin-face: - (progn (rust-eat-re "[a-zA-Z_][a-zA-Z_0-9]*") - 'font-lock-builtin-face) - ; Otherwise, handle as a character constant: - (let ((is-escape (eq (char-after) ?\\)) - (start (point))) - (if (not (rust-eat-until-unescaped ?\')) - 'font-lock-warning-face - (if (or is-escape (= (point) (+ start 2))) - 'font-lock-string-face 'font-lock-warning-face))))) - -(defun rust-token-base (st) - (funcall (char-table-range rust-char-table (char-after)) st)) - -(defun rust-eat-until-unescaped (ch) - (let (escaped) - (loop - (let ((cur (char-after))) - (when (or (eq cur ?\n) (not cur)) (return nil)) - (forward-char) - (when (and (eq cur ch) (not escaped)) (return t)) - (setf escaped (and (not escaped) (eq cur ?\\))))))) - -(defun rust-token-string (st) - (setf rust-tcat 'atom) - (cond ((rust-eat-until-unescaped ?\") - (setf (rust-state-tokenize st) 'rust-token-base) - (rust-pop-context st)) - (t (let ((align (eq (char-before) ?\\))) - (unless (eq align (rust-context-align (car (rust-state-context st)))) - (setf (rust-context-align (rust-dup-context st)) align))))) - 'font-lock-string-face) - -(defun rust-token-comment (st) - (let ((eol (point-at-eol))) - (loop - (unless (re-search-forward "\\(/\\*\\)\\|\\(\\*/\\)" eol t) - (goto-char eol) - (return)) - (if (match-beginning 1) - (push (car (rust-state-context st)) (rust-state-context st)) - (rust-pop-context st) - (unless (eq (rust-context-type (car (rust-state-context st))) 'comment) - (setf (rust-state-tokenize st) 'rust-token-base) - (return)))) - 'font-lock-comment-face)) - -(defun rust-next-block-info (st) - (dolist (cx (rust-state-context st)) - (when (eq (rust-context-type cx) ?\}) (return (rust-context-info cx))))) - -(defun rust-is-capitalized (string) - (let ((case-fold-search nil)) - (string-match-p "[A-Z]" string))) - -(defun rust-token (st) - (let ((cx (car (rust-state-context st)))) - (when (bolp) - (setf (rust-state-indent st) (current-indentation)) - (when (eq (rust-context-align cx) 'unset) - (setf (rust-context-align cx) nil))) - (setf rust-tcat nil) - (let* ((tok (funcall (rust-state-tokenize st) st)) - (tok-id (or tok rust-tcat)) - (cur-cx (rust-context-type cx)) - (cx-info (rust-context-info cx))) - (when (stringp tok) - (setf tok-id (gethash tok rust-value-keywords nil)) - (setf tok (cond ((eq tok-id 'atom) 'font-lock-constant-face) - (tok-id 'font-lock-keyword-face) - ((equal (rust-state-last-token st) 'def) 'font-lock-function-name-face) - ((and rust-capitalized-idents-are-types - (rust-is-capitalized tok)) 'font-lock-type-face) - (t nil)))) - (when rust-tcat - (when (eq (rust-context-align cx) 'unset) - (setf (rust-context-align cx) t)) - (when (eq cx-info 'alt-1) - (setf cx (rust-dup-context st)) - (setf (rust-context-info cx) 'alt-2)) - (when (and (eq rust-tcat 'pipe) (eq (rust-state-last-token st) ?{)) - (setf cx (rust-dup-context st)) - (setf (rust-context-info cx) 'block)) - (case rust-tcat - ((?\; ?,) (when (eq cur-cx 'statement) (rust-pop-context st))) - (?\{ - (when (and (eq cur-cx 'statement) (not (member cx-info '(alt-1 alt-2)))) - (rust-pop-context st)) - (when (eq cx-info 'alt-2) - (setf cx (rust-dup-context st)) - (setf (rust-context-info cx) nil)) - (let ((next-info (rust-next-block-info st)) - (newcx (rust-push-context st ?\} (current-column)))) - (cond ((eq cx-info 'alt-2) (setf (rust-context-info newcx) 'alt-outer)) - ((eq next-info 'alt-outer) (setf (rust-context-info newcx) 'alt-inner))))) - ((?\[ open-attr) - (let ((newcx (rust-push-context st ?\] (current-column)))) - (when (eq rust-tcat 'open-attr) - (setf (rust-context-info newcx) 'attr)))) - (?\( (rust-push-context st ?\) (current-column)) - (when (eq (rust-context-info cx) 'attr) - (setf (rust-context-info (car (rust-state-context st))) 'attr))) - (?\} (when (eq cur-cx 'statement) (rust-pop-context st)) - (when (eq (rust-context-type (car (rust-state-context st))) ?}) - (rust-pop-context st)) - (setf cx (car (rust-state-context st))) - (when (and (eq (rust-context-type cx) 'statement) - (not (eq (rust-context-info cx) 'alt-2))) - (rust-pop-context st))) - (t (cond ((eq cur-cx rust-tcat) - (when (eq (rust-context-info (rust-pop-context st)) 'attr) - (setf tok 'font-lock-preprocessor-face) - (when (eq (rust-context-type (car (rust-state-context st))) 'statement) - (rust-pop-context st)))) - ((or (and (eq cur-cx ?\}) (not (eq (rust-context-info cx) 'alt-outer))) - (eq cur-cx 'top)) - (rust-push-context st 'statement))))) - (setf (rust-state-last-token st) tok-id)) - (setf cx (car (rust-state-context st))) - (when (and (eq tok-id 'alt) (eq (rust-context-type cx) 'statement)) - (setf (rust-context-info cx) 'alt-1)) - (when (and (eq (rust-state-last-token st) 'pipe) - (eq (rust-next-block-info st) 'block) (eolp)) - (when (eq (rust-context-type cx) 'statement) (rust-pop-context st)) - (setf cx (rust-dup-context st) - (rust-context-info cx) nil - (rust-context-align cx) nil)) - (if (eq (rust-context-info cx) 'attr) - 'font-lock-preprocessor-face - tok)))) - -(defun rust-indent (st) - (let ((cx (car (rust-state-context st))) - (parent (cadr (rust-state-context st)))) - (when (and (eq (rust-context-type cx) 'statement) - (or (eq (char-after) ?\}) (looking-at "with \\|{[ ]*$"))) - (setf cx parent parent (caddr (rust-state-context st)))) - (let* ((tp (rust-context-type cx)) - (closing (eq tp (char-after))) - (unit rust-indent-unit) - (base (if (and (eq tp 'statement) parent (rust-context-align parent)) - (rust-context-column parent) (rust-context-indent cx)))) - (cond ((eq tp 'comment) base) - ((eq tp 'string) (if (rust-context-align cx) (rust-context-column cx) 0)) - ((eq tp 'statement) (+ base (if (eq (char-after) ?\}) 0 unit))) - ((eq (rust-context-align cx) t) (+ (rust-context-column cx) (if closing -1 0))) - (t (+ base (if closing 0 unit))))))) + +(defun rust-paren-level () (nth 0 (syntax-ppss))) +(defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss))) +(defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss)))) +(defun rust-rewind-irrelevant () + (let ((starting (point))) + (skip-chars-backward "[:space:]\n") + (if (looking-back "\\*/") (backward-char)) + (if (rust-in-str-or-cmnt) + (rust-rewind-past-str-cmnt)) + (if (/= starting (point)) + (rust-rewind-irrelevant)))) + +(defun rust-mode-indent-line () + (interactive) + (let ((indent + (save-excursion + (back-to-indentation) + (let ((level (rust-paren-level))) + (cond + ;; A function return type is 1 level indented + ((looking-at "->") (* default-tab-width (+ level 1))) + + ;; A closing brace is 1 level unindended + ((looking-at "}") (* default-tab-width (- level 1))) + + ;; If we're in any other token-tree / sexp, then: + ;; - [ or ( means line up with the opening token + ;; - { means indent to either nesting-level * tab width, + ;; or one further indent from that if either current line + ;; begins with 'else', or previous line didn't end in + ;; semi, comma or brace, and wasn't an attribute. PHEW. + ((> level 0) + (let ((pt (point))) + (rust-rewind-irrelevant) + (backward-up-list) + (if (looking-at "[[(]") + (+ 1 (current-column)) + (progn + (goto-char pt) + (back-to-indentation) + (if (looking-at "\\") + (* default-tab-width (+ 1 level)) + (progn + (goto-char pt) + (beginning-of-line) + (rust-rewind-irrelevant) + (end-of-line) + (if (looking-back "[{};,]") + (* default-tab-width level) + (back-to-indentation) + (if (looking-at "#") + (* default-tab-width level) + (* default-tab-width (+ 1 level)))))))))) + + ;; Otherwise we're in a column-zero definition + (t 0)))))) + (cond + ;; If we're to the left of the indentation, reindent and jump to it. + ((<= (current-column) indent) + (indent-line-to indent)) + + ;; We're to the right; if it needs indent, do so but save excursion. + ((not (eq (current-indentation) indent)) + (save-excursion (indent-line-to indent)))))) + + +;; Font-locking definitions and helpers +(defconst rust-mode-keywords + '("as" + "break" + "do" + "else" "enum" "extern" + "false" "fn" "for" + "if" "impl" + "let" "loop" + "match" "mod" "mut" + "priv" "pub" + "ref" "return" + "self" "static" "struct" "super" + "true" "trait" "type" + "unsafe" "use" + "while")) + +(defconst rust-special-types + '("u8" "i8" + "u16" "i16" + "u32" "i32" + "u64" "i64" + + "f32" "f64" + "float" "int" "uint" + "bool" + "str" "char")) + +(defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") +(defconst rust-re-CamelCase "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*") +(defun rust-re-word (inner) (concat "\\<" inner "\\>")) +(defun rust-re-grab (inner) (concat "\\(" inner "\\)")) +(defun rust-re-grabword (inner) (rust-re-grab (rust-re-word inner))) +(defun rust-re-item-def (itype) + (concat (rust-re-word itype) "[[:space:]]+" (rust-re-grab rust-re-ident))) + +(defvar rust-mode-font-lock-keywords + (append + `( + ;; Keywords proper + (,(regexp-opt rust-mode-keywords 'words) . font-lock-keyword-face) + + ;; Special types + (,(regexp-opt rust-special-types 'words) . font-lock-type-face) + + ;; Attributes like `#[bar(baz)]` + (,(rust-re-grab (concat "#\\[" rust-re-ident "[^]]*\\]")) + 1 font-lock-preprocessor-face) + + ;; Syntax extension invocations like `foo!`, highlight including the ! + (,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:]]") + 1 font-lock-preprocessor-face) + + ;; Field names like `foo:`, highlight excluding the : + (,(concat (rust-re-grab rust-re-ident) ":[^:]") 1 font-lock-variable-name-face) + + ;; Module names like `foo::`, highlight including the :: + (,(rust-re-grab (concat rust-re-ident "::")) 1 font-lock-type-face) + + ;; Lifetimes like `'foo` + (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face) + + ;; Character constants, since they're not treated as strings + ;; in order to have sufficient leeway to parse 'lifetime above. + (,(rust-re-grab "'[^']'") 1 font-lock-string-face) + (,(rust-re-grab "'\\\\[nrt]'") 1 font-lock-string-face) + (,(rust-re-grab "'\\\\x[[:xdigit:]]\\{2\\}'") 1 font-lock-string-face) + (,(rust-re-grab "'\\\\u[[:xdigit:]]\\{4\\}'") 1 font-lock-string-face) + (,(rust-re-grab "'\\\\U[[:xdigit:]]\\{8\\}'") 1 font-lock-string-face) + + ;; CamelCase Means Type Or Constructor + (,(rust-re-grabword rust-re-CamelCase) 1 font-lock-type-face) + ) + + ;; Item definitions + (loop for (item . face) in + + '(("enum" . font-lock-type-face) + ("struct" . font-lock-type-face) + ("type" . font-lock-type-face) + ("mod" . font-lock-type-face) + ("use" . font-lock-type-face) + ("fn" . font-lock-function-name-face) + ("static" . font-lock-constant-face)) + + collect `(,(rust-re-item-def item) 1 ,face)))) + + +;; For compatibility with Emacs < 24, derive conditionally +(defalias 'rust-parent-mode + (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode)) + ;;;###autoload -(define-derived-mode rust-mode fundamental-mode "Rust" - "Major mode for editing Rust source files." - (set-syntax-table rust-syntax-table) - (setq major-mode 'rust-mode mode-name "Rust") - (run-hooks 'rust-mode-hook) - (set (make-local-variable 'indent-tabs-mode) nil) - (let ((par "[ ]*\\(//+\\|\\**\\)[ ]*$")) - (set (make-local-variable 'paragraph-start) par) - (set (make-local-variable 'paragraph-separate) par)) - (set (make-local-variable 'comment-start) "//") - (cm-mode (make-cm-mode 'rust-token 'make-rust-state 'copy-sequence 'equal 'rust-indent))) - -(define-key rust-mode-map "}" 'rust-electric-brace) -(define-key rust-mode-map "{" 'rust-electric-brace) +(define-derived-mode rust-mode rust-parent-mode "Rust" + "Major mode for Rust code." + + ;; Basic syntax + (set-syntax-table rust-mode-syntax-table) + + ;; Indentation + (set (make-local-variable 'indent-line-function) + 'rust-mode-indent-line) + + ;; Fonts + (set (make-local-variable 'font-lock-defaults) + '(rust-mode-font-lock-keywords nil nil nil nil)) + + ;; Misc + (set (make-local-variable 'comment-start) "// ") + (set (make-local-variable 'comment-end) "") + (set (make-local-variable 'indent-tabs-mode) nil)) + ;;;###autoload -(progn - (add-to-list 'auto-mode-alist '("\\.rs$" . rust-mode)) - (add-to-list 'auto-mode-alist '("\\.rc$" . rust-mode))) +(add-to-list 'auto-mode-alist '("\\.rs$" . rust-mode)) + +(defun rust-mode-reload () + (interactive) + (unload-feature 'rust-mode) + (require 'rust-mode) + (rust-mode)) (provide 'rust-mode)