diff options
| author | Marijn Haverbeke <marijnh@gmail.com> | 2012-01-16 12:44:24 +0100 |
|---|---|---|
| committer | Marijn Haverbeke <marijnh@gmail.com> | 2012-01-16 12:44:24 +0100 |
| commit | f0e4edc220bda13693292b2c3d322eae572d65aa (patch) | |
| tree | a5f88dee6759db4f57cfb574a2e0d24c7d59e846 /cm-mode.el | |
| download | rust-mode-f0e4edc220bda13693292b2c3d322eae572d65aa.tar.gz | |
Move emacs mode into tree
Diffstat (limited to 'cm-mode.el')
| -rw-r--r-- | cm-mode.el | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/cm-mode.el b/cm-mode.el new file mode 100644 index 0000000..469bded --- /dev/null +++ b/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) |
