From 936c933fd8b7bb71b8a7fd845f27cf50b3fa0402 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 16 Jan 2012 12:44:24 +0100 Subject: [PATCH] Move emacs mode into tree --- doc/tutorial/setup.md | 4 +- src/etc/emacs/Makefile | 14 ++ src/etc/emacs/README.md | 27 ++++ src/etc/emacs/cm-mode.el | 186 ++++++++++++++++++++++++ src/etc/emacs/rust-mode.el | 283 +++++++++++++++++++++++++++++++++++++ 5 files changed, 512 insertions(+), 2 deletions(-) create mode 100644 src/etc/emacs/Makefile create mode 100644 src/etc/emacs/README.md create mode 100644 src/etc/emacs/cm-mode.el create mode 100644 src/etc/emacs/rust-mode.el diff --git a/doc/tutorial/setup.md b/doc/tutorial/setup.md index a1a28d903a0..f85052961a0 100644 --- a/doc/tutorial/setup.md +++ b/doc/tutorial/setup.md @@ -47,8 +47,8 @@ detail [later on](mod.html). ## Editing Rust code There are Vim highlighting and indentation scrips in the Rust source -distribution under `src/etc/vim/`. An Emacs mode can be found at -[https://github.com/marijnh/rust-mode][rust-mode]. +distribution under `src/etc/vim/`, and an emacs mode under +`src/etc/emacs/`. [rust-mode]: https://github.com/marijnh/rust-mode diff --git a/src/etc/emacs/Makefile b/src/etc/emacs/Makefile new file mode 100644 index 00000000000..c79e7a9719b --- /dev/null +++ b/src/etc/emacs/Makefile @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000000..7bfeebeef6b --- /dev/null +++ b/src/etc/emacs/README.md @@ -0,0 +1,27 @@ +rust-mode: A major emacs mode for editing Rust source code +========================================================== + +`rust-mode` makes editing [Rust](http://rust-lang.org) code with emacs +enjoyable. + +To install, check out this repository and add this to your .emacs +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`. diff --git a/src/etc/emacs/cm-mode.el b/src/etc/emacs/cm-mode.el new file mode 100644 index 00000000000..469bded047a --- /dev/null +++ b/src/etc/emacs/cm-mode.el @@ -0,0 +1,186 @@ +;; Wrapper for CodeMirror-style emacs modes. 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 + +(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) diff --git a/src/etc/emacs/rust-mode.el b/src/etc/emacs/rust-mode.el new file mode 100644 index 00000000000..e4f0ee6c62f --- /dev/null +++ b/src/etc/emacs/rust-mode.el @@ -0,0 +1,283 @@ +(require 'cm-mode) +(require 'cc-mode) + +(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))) + +(defvar rust-indent-unit 4) +(defvar rust-syntax-table (let ((table (make-syntax-table))) + (c-populate-syntax-table table) + table)) + +(add-to-list 'auto-mode-alist '("\\.rs$" . rust-mode)) +(add-to-list 'auto-mode-alist '("\\.rc$" . rust-mode)) + +(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" "type" "resource" "fn" "tag" "iface" "impl")) + (puthash word 'def table)) + (dolist (word '("if" "else" "while" "do" "for" "break" "cont" "ret" "be" "fail" "const" + "check" "assert" "claim" "prove" "native" "import" "export" "let" "log" + "use" "pure" "unsafe")) + (puthash word t table)) + (puthash "alt" 'alt table) + (dolist (word '("true" "false")) (puthash word 'atom 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 ?\' (forward-char) + (setf rust-tcat 'atom) + (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)))) + (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-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))) + (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-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-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) + (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 (if (member (rust-context-info cx) '(alt-inner alt-outer)) + (/ rust-indent-unit 2) 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))))))) + +(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) + +(provide 'rust-mode)