Move emacs mode into tree

This commit is contained in:
Marijn Haverbeke 2012-01-16 12:44:24 +01:00
parent 1375b31c1c
commit 936c933fd8
5 changed files with 512 additions and 2 deletions

View File

@ -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

14
src/etc/emacs/Makefile Normal file
View File

@ -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)

27
src/etc/emacs/README.md Normal file
View File

@ -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`.

186
src/etc/emacs/cm-mode.el Normal file
View File

@ -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)

283
src/etc/emacs/rust-mode.el Normal file
View File

@ -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)