From 3ba23d3758e06d95b14ed4e89607e258d15ddbf7 Mon Sep 17 00:00:00 2001 From: Simeon Simeonov Date: Wed, 12 Oct 2022 23:21:07 +0200 Subject: Add Rust mode and update lua-mode, markdown-mode and yaml-mode --- .emacs | 6 +- .emacs.d/lisp/lua-mode.el | 1001 +++++++---- .emacs.d/lisp/markdown-mode.el | 149 +- .emacs.d/lisp/rust-cargo.el | 124 ++ .emacs.d/lisp/rust-compile.el | 83 + .emacs.d/lisp/rust-mode-tests.el | 3637 ++++++++++++++++++++++++++++++++++++++ .emacs.d/lisp/rust-mode.el | 1632 +++++++++++++++++ .emacs.d/lisp/rust-playpen.el | 59 + .emacs.d/lisp/rust-rustfmt.el | 372 ++++ .emacs.d/lisp/rust-utils.el | 91 + .emacs.d/lisp/yaml-mode.el | 6 +- 11 files changed, 6721 insertions(+), 439 deletions(-) create mode 100644 .emacs.d/lisp/rust-cargo.el create mode 100644 .emacs.d/lisp/rust-compile.el create mode 100644 .emacs.d/lisp/rust-mode-tests.el create mode 100644 .emacs.d/lisp/rust-mode.el create mode 100644 .emacs.d/lisp/rust-playpen.el create mode 100644 .emacs.d/lisp/rust-rustfmt.el create mode 100644 .emacs.d/lisp/rust-utils.el diff --git a/.emacs b/.emacs index fa2617e..3946dcd 100644 --- a/.emacs +++ b/.emacs @@ -108,12 +108,14 @@ ;;(autoload 'php-mode "php-mode" "Mode for editing PHP source files") ;;(add-to-list 'auto-mode-alist '("\\.\\(inc\\|php[s34]?\\)" . php-mode)) - ;; Lua (autoload 'lua-mode "lua-mode" "Lua editing mode." t) (add-to-list 'auto-mode-alist '("\\.lua$" . lua-mode)) ;;(add-to-list 'interpreter-mode-alist '("lua" . lua-mode)) +;; Rust +;; (autoload 'rust-mode "rust-mode" nil t) +;; (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)) ;; Markdown (autoload 'markdown-mode "markdown-mode" @@ -125,12 +127,10 @@ "Major mode for editing GitHub Flavored Markdown files" t) (add-to-list 'auto-mode-alist '("README\\.md\\'" . gfm-mode)) - ;; YAML (require 'yaml-mode) (add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode)) - ;; yasnippet (require 'yasnippet) (yas-global-mode 1) diff --git a/.emacs.d/lisp/lua-mode.el b/.emacs.d/lisp/lua-mode.el index 640e490..2d6a2d5 100644 --- a/.emacs.d/lisp/lua-mode.el +++ b/.emacs.d/lisp/lua-mode.el @@ -11,16 +11,16 @@ ;; Paul Du Bois and ;; Aaron Smith . ;; -;; URL: http://immerrr.github.com/lua-mode -;; Version: 20151025 +;; URL: https://immerrr.github.io/lua-mode +;; Version: 20210802 ;; Package-Requires: ((emacs "24.3")) ;; ;; This file is NOT part of Emacs. ;; -;; This program is free software; you can redistribute it and/or -;; modify it under the terms of the GNU General Public License -;; as published by the Free Software Foundation; either version 2 -;; of the License, or (at your option) any later version. +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -28,15 +28,13 @@ ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License -;; along with this program; if not, write to the Free Software -;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -;; MA 02110-1301, USA. +;; along with this program. If not, see . ;; Keywords: languages, processes, tools -;; This field is expanded to commit SHA, date & associated heads/tags during +;; This field is expanded to commit SHA and commit date during the ;; archive creation. -;; Revision: $Format:%h (%cD %d)$ +;; Revision: $Format:%h (%cD)$ ;; ;;; Commentary: @@ -53,6 +51,12 @@ ;; - Var `lua-indent-string-contents': ;; set to `t` if you like to have contents of multiline strings to be ;; indented like comments +;; - Var `lua-indent-nested-block-content-align': +;; set to `nil' to stop aligning the content of nested blocks with the +;; open parenthesis +;; - Var `lua-indent-close-paren-align': +;; set to `t' to align close parenthesis with the open parenthesis, +;; rather than with the beginning of the line ;; - Var `lua-mode-hook': ;; list of functions to execute when lua-mode is initialized ;; - Var `lua-documentation-url': @@ -115,7 +119,7 @@ (lua-name (symbol (seq (+ (any alpha "_")) (* (any alnum "_"))))) (lua-funcname (seq lua-name (* ws "." ws lua-name) - (opt ws ":" ws lua-name))) + (opt ws ":" ws lua-name))) (lua-funcheader ;; Outer (seq ...) is here to shy-group the definition (seq (or (seq (symbol "function") ws (group-n 1 lua-funcname)) @@ -123,15 +127,26 @@ (symbol "function"))))) (lua-number (seq (or (seq (+ digit) (opt ".") (* digit)) - (seq (* digit) (opt ".") (+ digit))) - (opt (regexp "[eE][+-]?[0-9]+")))) + (seq (* digit) (opt ".") (+ digit))) + (opt (regexp "[eE][+-]?[0-9]+")))) (lua-assignment-op (seq "=" (or buffer-end (not (any "="))))) - (lua-token (or "+" "-" "*" "/" "%" "^" "#" "==" "~=" "<=" ">=" "<" - ">" "=" ";" ":" "," "." ".." "...")) + (lua-operator (or "+" "-" "*" "/" "%" "^" "#" "==" "~=" "<=" ">=" "<" + ">" "=" ";" ":" "," "." ".." "...")) + (lua-keyword-operator (symbol "and" "not" "or")) (lua-keyword - (symbol "and" "break" "do" "else" "elseif" "end" "for" "function" - "goto" "if" "in" "local" "not" "or" "repeat" "return" - "then" "until" "while")))) + (symbol "break" "do" "else" "elseif" "end" "for" "function" + "goto" "if" "in" "local" "repeat" "return" + "then" "until" "while")) + (lua-up-to-9-variables + (seq (group-n 1 lua-name) ws + (? "," ws (group-n 2 lua-name) ws + (? "," ws (group-n 3 lua-name) ws + (? "," ws (group-n 4 lua-name) ws + (? "," ws (group-n 5 lua-name) ws + (? "," ws (group-n 6 lua-name) ws + (? "," ws (group-n 7 lua-name) ws + (? "," ws (group-n 8 lua-name) ws + (? "," ws (group-n 9 lua-name) ws)))))))))))) (defmacro lua-rx (&rest regexps) (eval `(rx-let ,lua--rx-bindings @@ -185,7 +200,7 @@ element is itself expanded with `lua-rx-to-string'. " (setq form (if (eq 1 (length form)) (car form) (append '(or) form))) - (and (fboundp 'rx-form) ; Silence Emacs 27's byte-compiler. + (and (fboundp 'rx-form) ; Silence Emacs 27's byte-compiler. (rx-form `(seq symbol-start ,form symbol-end) rx-parent))) (setq lua-rx-constituents (copy-sequence rx-constituents)) @@ -208,14 +223,25 @@ element is itself expanded with `lua-rx-to-string'. " (opt (regexp "[eE][+-]?[0-9]+")))) (lua-assignment-op :rx (seq "=" (or buffer-end (not (any "="))))) - (lua-token + (lua-operator :rx (or "+" "-" "*" "/" "%" "^" "#" "==" "~=" "<=" ">=" "<" ">" "=" ";" ":" "," "." ".." "...")) + (lua-keyword-operator + :rx (symbol "and" "not" "or")) (lua-keyword - :rx (symbol "and" "break" "do" "else" "elseif" "end" "for" "function" - "goto" "if" "in" "local" "not" "or" "repeat" "return" - "then" "until" "while"))) - )))) + :rx (symbol "break" "do" "else" "elseif" "end" "for" "function" + "goto" "if" "in" "local" "repeat" "return" + "then" "until" "while")) + (lua-up-to-9-variables + :rx (seq (group-n 1 lua-name) ws + (? "," ws (group-n 2 lua-name) ws + (? "," ws (group-n 3 lua-name) ws + (? "," ws (group-n 4 lua-name) ws + (? "," ws (group-n 5 lua-name) ws + (? "," ws (group-n 6 lua-name) ws + (? "," ws (group-n 7 lua-name) ws + (? "," ws (group-n 8 lua-name) ws + (? "," ws (group-n 9 lua-name) ws))))))))))))))) ;; Local variables @@ -241,7 +267,11 @@ element is itself expanded with `lua-rx-to-string'. " :group 'lua) (defcustom lua-default-application "lua" - "Default application to run in Lua process." + "Default application to run in Lua process. + +Can be a string, where it denotes a command to be executed to +start Lua process, or a (HOST . PORT) cons, that can be used to +connect to Lua process running remotely." :type '(choice (string) (cons string integer)) :group 'lua) @@ -279,7 +309,7 @@ Should be a list of strings." "The active Lua process") (defvar lua-process-buffer nil - "Buffer used for communication with the Lua process") + "Buffer used for communication with the Lua process.") (defun lua--customize-set-prefix-key (prefix-key-sym prefix-key-val) (cl-assert (eq prefix-key-sym 'lua-prefix-key)) @@ -327,13 +357,13 @@ If the latter is nil, the keymap translates into `lua-mode-map' verbatim.") #'lua-electric-match)) lua--electric-indent-chars)) (define-key result-map [menu-bar lua-mode] (cons "Lua" lua-mode-menu)) + (define-key result-map [remap backward-up-list] 'lua-backward-up-list) - ;; FIXME: see if the declared logic actually works ;; handle prefix-keyed bindings: ;; * if no prefix, set prefix-map as parent, i.e. ;; if key is not defined look it up in prefix-map ;; * if prefix is set, bind the prefix-map to that key - (if (boundp 'lua-prefix-key) + (if lua-prefix-key (define-key result-map (vector lua-prefix-key) lua-prefix-mode-map) (set-keymap-parent result-map lua-prefix-mode-map)) result-map) @@ -372,8 +402,8 @@ If the latter is nil, the keymap translates into `lua-mode-map' verbatim.") ;; NOTE: this doesn't traverse `compilation-search-path' when ;; looking for filename. (not (file-exists-p (expand-file-name - filename - (when directory (expand-file-name directory)))))) + filename + (when directory (expand-file-name directory)))))) (setq ad-return-value (current-buffer)) ad-do-it)) @@ -397,7 +427,24 @@ Usually, stdin:XX line number points to nowhere." "If non-nil, contents of multiline string will be indented. Otherwise leading amount of whitespace on each line is preserved." :group 'lua - :type 'boolean) + :type 'boolean + :safe #'booleanp) + +(defcustom lua-indent-nested-block-content-align t + "If non-nil, the contents of nested blocks are indented to +align with the column of the opening parenthesis, rather than +just forward by `lua-indent-level'." + :group 'lua + :type 'boolean + :safe #'booleanp) + +(defcustom lua-indent-close-paren-align t + "If non-nil, close parenthesis are aligned with their open +parenthesis. If nil, close parenthesis are aligned to the +beginning of the line." + :group 'lua + :type 'boolean + :safe #'booleanp) (defcustom lua-jump-on-traceback t "*Jump to innermost traceback location in *lua* buffer. When this @@ -476,118 +523,38 @@ traceback location." "offset"))))) (cl-labels - ((module-name-re (x) - (concat "\\(?1:\\_<" - (if (listp x) (car x) x) - "\\_>\\)")) - (module-members-re (x) (if (listp x) - (concat "\\(?:[ \t]*\\.[ \t]*" - "\\_<\\(?2:" - (regexp-opt (cdr x)) - "\\)\\_>\\)?") - ""))) - - (concat - ;; common prefix: - ;; - beginning-of-line - ;; - or neither of [ '.', ':' ] to exclude "foo.string.rep" - ;; - or concatenation operator ".." - "\\(?:^\\|[^:. \t]\\|[.][.]\\)" - ;; optional whitespace - "[ \t]*" - "\\(?:" - ;; any of modules/functions - (mapconcat (lambda (x) (concat (module-name-re x) - (module-members-re x))) - modules - "\\|") - "\\)")))) + ((module-name-re (x) + (concat "\\(?1:\\_<" + (if (listp x) (car x) x) + "\\_>\\)")) + (module-members-re (x) (if (listp x) + (concat "\\(?:[ \t]*\\.[ \t]*" + "\\_<\\(?2:" + (regexp-opt (cdr x)) + "\\)\\_>\\)?") + ""))) + + (concat + ;; common prefix: + ;; - beginning-of-line + ;; - or neither of [ '.', ':' ] to exclude "foo.string.rep" + ;; - or concatenation operator ".." + "\\(?:^\\|[^:. \t]\\|[.][.]\\)" + ;; optional whitespace + "[ \t]*" + "\\(?:" + ;; any of modules/functions + (mapconcat (lambda (x) (concat (module-name-re x) + (module-members-re x))) + modules + "\\|") + "\\)")))) "A regexp that matches Lua builtin functions & variables. This is a compilation of 5.1, 5.2 and 5.3 builtins taken from the index of respective Lua reference manuals.") -(eval-and-compile - (defun lua-make-delimited-matcher (elt-regexp sep-regexp end-regexp) - "Construct matcher function for `font-lock-keywords' to match a sequence. - -It's supposed to match sequences with following EBNF: - -ELT-REGEXP { SEP-REGEXP ELT-REGEXP } END-REGEXP - -The sequence is parsed one token at a time. If non-nil is -returned, `match-data' will have one or more of the following -groups set according to next matched token: - -1. matched element token -2. unmatched garbage characters -3. misplaced token (i.e. SEP-REGEXP when ELT-REGEXP is expected) -4. matched separator token -5. matched end token - -Blanks & comments between tokens are silently skipped. -Groups 6-9 can be used in any of argument regexps." - (let* - ((delimited-matcher-re-template - "\\=\\(?2:.*?\\)\\(?:\\(?%s:\\(?4:%s\\)\\|\\(?5:%s\\)\\)\\|\\(?%s:\\(?1:%s\\)\\)\\)") - ;; There's some magic to this regexp. It works as follows: - ;; - ;; A. start at (point) - ;; B. non-greedy match of garbage-characters (?2:) - ;; C. try matching separator (?4:) or end-token (?5:) - ;; D. try matching element (?1:) - ;; - ;; Simple, but there's a trick: pt.C and pt.D are embraced by one more - ;; group whose purpose is determined only after the template is - ;; formatted (?%s:): - ;; - ;; - if element is expected, then D's parent group becomes "shy" and C's - ;; parent becomes group 3 (aka misplaced token), so if D matches when - ;; an element is expected, it'll be marked with warning face. - ;; - ;; - if separator-or-end-token is expected, then it's the opposite: - ;; C's parent becomes shy and D's will be matched as misplaced token. - (elt-expected-re (format delimited-matcher-re-template - 3 sep-regexp end-regexp "" elt-regexp)) - (sep-or-end-expected-re (format delimited-matcher-re-template - "" sep-regexp end-regexp 3 elt-regexp))) - - (lambda (end) - (let* ((prev-elt-p (match-beginning 1)) - (prev-end-p (match-beginning 5)) - - (regexp (if prev-elt-p sep-or-end-expected-re elt-expected-re)) - (comment-start (lua-comment-start-pos (syntax-ppss))) - (parse-stop end)) - - ;; If token starts inside comment, or end-token was encountered, stop. - (when (and (not comment-start) - (not prev-end-p)) - ;; Skip all comments & whitespace. forward-comment doesn't have boundary - ;; argument, so make sure point isn't beyond parse-stop afterwards. - (while (and (< (point) end) - (forward-comment 1))) - (goto-char (min (point) parse-stop)) - - ;; Reuse comment-start variable to store beginning of comment that is - ;; placed before line-end-position so as to make sure token search doesn't - ;; enter that comment. - (setq comment-start - (lua-comment-start-pos - (save-excursion - (parse-partial-sexp (point) parse-stop - nil nil nil 'stop-inside-comment))) - parse-stop (or comment-start parse-stop)) - - ;; Now, let's match stuff. If regular matcher fails, declare a span of - ;; non-blanks 'garbage', and the next iteration will start from where the - ;; garbage ends. If couldn't match any garbage, move point to the end - ;; and return nil. - (or (re-search-forward regexp parse-stop t) - (re-search-forward "\\(?1:\\(?2:[^ \t]+\\)\\)" parse-stop 'skip) - (prog1 nil (goto-char end))))))))) - (defvar lua-font-lock-keywords `(;; highlight the hash-bang line "#!/foo/bar/lua" as comment @@ -598,59 +565,67 @@ Groups 6-9 can be used in any of argument regexps." . font-lock-constant-face) ;; Keywords - (,(lua-rx lua-keyword) + (,(lua-rx (or lua-keyword lua-keyword-operator)) . font-lock-keyword-face) ;; Labels used by the "goto" statement ;; Highlights the following syntax: ::label:: (,(lua-rx "::" ws lua-name ws "::") - . font-lock-constant-face) + . font-lock-constant-face) ;; Highlights the name of the label in the "goto" statement like ;; "goto label" (,(lua-rx (symbol (seq "goto" ws+ (group-n 1 lua-name)))) - (1 font-lock-constant-face)) + (1 font-lock-constant-face)) ;; Highlight Lua builtin functions and variables (,lua--builtins (1 font-lock-builtin-face) (2 font-lock-builtin-face nil noerror)) - ("^[ \t]*\\_" - (,(lua-make-delimited-matcher (lua-rx lua-name) "," - (lua-rx (or (symbol "in") lua-assignment-op))) - nil nil - (1 font-lock-variable-name-face nil noerror) - (2 font-lock-warning-face t noerror) - (3 font-lock-warning-face t noerror))) - - ;; Handle local variable/function names - ;; local blalba, xyzzy = - ;; ^^^^^^ ^^^^^ - ;; - ;; local function foobar(x,y,z) - ;; ^^^^^^ - ;; local foobar = function(x,y,z) - ;; ^^^^^^ - ("^[ \t]*\\_" - (0 font-lock-keyword-face) - - ;; (* nonl) at the end is to consume trailing characters or otherwise they - ;; delimited matcher would attempt to parse them afterwards and wrongly - ;; highlight parentheses as incorrect variable name characters. - (,(lua-rx point ws lua-funcheader (* nonl)) - nil nil - (1 font-lock-function-name-face nil noerror)) - - (,(lua-make-delimited-matcher (lua-rx lua-name) "," - (lua-rx lua-assignment-op)) - nil nil - (1 font-lock-variable-name-face nil noerror) - (2 font-lock-warning-face t noerror) - (3 font-lock-warning-face t noerror))) - - (,(lua-rx (or bol ";") ws lua-funcheader) + (,(lua-rx (symbol "for") ws+ lua-up-to-9-variables) + (1 font-lock-variable-name-face) + (2 font-lock-variable-name-face nil noerror) + (3 font-lock-variable-name-face nil noerror) + (4 font-lock-variable-name-face nil noerror) + (5 font-lock-variable-name-face nil noerror) + (6 font-lock-variable-name-face nil noerror) + (7 font-lock-variable-name-face nil noerror) + (8 font-lock-variable-name-face nil noerror) + (9 font-lock-variable-name-face nil noerror)) + + (,(lua-rx (symbol "function") (? ws+ lua-funcname) ws "(" ws lua-up-to-9-variables) + (1 font-lock-variable-name-face) + (2 font-lock-variable-name-face nil noerror) + (3 font-lock-variable-name-face nil noerror) + (4 font-lock-variable-name-face nil noerror) + (5 font-lock-variable-name-face nil noerror) + (6 font-lock-variable-name-face nil noerror) + (7 font-lock-variable-name-face nil noerror) + (8 font-lock-variable-name-face nil noerror) + (9 font-lock-variable-name-face nil noerror)) + + (,(lua-rx lua-funcheader) (1 font-lock-function-name-face)) + ;; local x, y, z + ;; local x = ..... + ;; + ;; NOTE: this is intentionally below funcheader matcher, so that in + ;; + ;; local foo = function() ... + ;; + ;; "foo" is fontified as function-name-face, and variable-name-face is not applied. + (,(lua-rx (symbol "local") ws+ lua-up-to-9-variables) + (1 font-lock-variable-name-face) + (2 font-lock-variable-name-face nil noerror) + (3 font-lock-variable-name-face nil noerror) + (4 font-lock-variable-name-face nil noerror) + (5 font-lock-variable-name-face nil noerror) + (6 font-lock-variable-name-face nil noerror) + (7 font-lock-variable-name-face nil noerror) + (8 font-lock-variable-name-face nil noerror) + (9 font-lock-variable-name-face nil noerror)) + (,(lua-rx (or (group-n 1 "@" (symbol "author" "copyright" "field" "release" "return" "see" "usage" "description")) @@ -662,13 +637,14 @@ Groups 6-9 can be used in any of argument regexps." "Default expressions to highlight in Lua mode.") (defvar lua-imenu-generic-expression - `((nil ,(lua-rx (or bol ";") ws (opt (seq (symbol "local") ws)) lua-funcheader) 1)) + `(("Requires" ,(lua-rx (or bol ";") ws (opt (seq (symbol "local") ws)) (group-n 1 lua-name) ws "=" ws (symbol "require")) 1) + (nil ,(lua-rx (or bol ";") ws (opt (seq (symbol "local") ws)) lua-funcheader) 1)) "Imenu generic expression for lua-mode. See `imenu-generic-expression'.") (defvar lua-sexp-alist '(("then" . "end") - ("function" . "end") - ("do" . "end") - ("repeat" . "until"))) + ("function" . "end") + ("do" . "end") + ("repeat" . "until"))) (defvar lua-mode-abbrev-table nil "Abbreviation table used in lua-mode buffers.") @@ -709,11 +685,11 @@ Groups 6-9 can be used in any of argument regexps." :syntax-table lua-mode-syntax-table :group 'lua (setq-local font-lock-defaults '(lua-font-lock-keywords ;; keywords - nil ;; keywords-only - nil ;; case-fold - nil ;; syntax-alist - nil ;; syntax-begin - )) + nil ;; keywords-only + nil ;; case-fold + nil ;; syntax-alist + nil ;; syntax-begin + )) (setq-local syntax-propertize-function 'lua--propertize-multiline-bounds) @@ -733,7 +709,7 @@ Groups 6-9 can be used in any of argument regexps." ;; If electric-indent-chars is not defined, electric indentation is done ;; via `lua-mode-map'. (setq-local electric-indent-chars - (append electric-indent-chars lua--electric-indent-chars))) + (append electric-indent-chars lua--electric-indent-chars))) ;; setup menu bar entry (XEmacs style) @@ -770,7 +746,7 @@ Groups 6-9 can be used in any of argument regexps." "Insert character and adjust indentation." (interactive "P") (let (blink-paren-function) - (self-insert-command (prefix-numeric-value arg))) + (self-insert-command (prefix-numeric-value arg))) (if lua-electric-flag (lua-indent-line)) (blink-matching-open)) @@ -826,22 +802,41 @@ This function replaces previous prefix-key binding with a new one." "Returns true if the point is in a string." (save-excursion (elt (syntax-ppss pos) 3))) -(defun lua-comment-start-pos (parsing-state) +(defun lua--containing-double-hyphen-start-pos () + "Return position of the beginning comment delimiter (--). + +Emacs syntax framework does not consider comment delimiters as +part of the comment itself, but for this package it is useful to +consider point as inside comment when it is between the two hyphens" + (and (eql (char-before) ?-) + (eql (char-after) ?-) + (1- (point)))) + +(defun lua-comment-start-pos (&optional parsing-state) "Return position of comment containing current point. If point is not inside a comment, return nil." - (and parsing-state (nth 4 parsing-state) (nth 8 parsing-state))) + (unless parsing-state (setq parsing-state (syntax-ppss))) + (and + ;; Not a string + (not (nth 3 parsing-state)) + ;; Syntax-based comment + (or (and (nth 4 parsing-state) (nth 8 parsing-state)) + (lua--containing-double-hyphen-start-pos)))) (defun lua-comment-or-string-p (&optional pos) "Returns true if the point is in a comment or string." (save-excursion (let ((parse-result (syntax-ppss pos))) - (or (elt parse-result 3) (elt parse-result 4))))) + (or (elt parse-result 3) (lua-comment-start-pos parse-result))))) (defun lua-comment-or-string-start-pos (&optional pos) "Returns start position of string or comment which contains point. If point is not inside string or comment, return nil." - (save-excursion (elt (syntax-ppss pos) 8))) + (save-excursion + (when pos (goto-char pos)) + (or (elt (syntax-ppss pos) 8) + (lua--containing-double-hyphen-start-pos)))) ;; They're propertized as follows: ;; 1. generic-comment @@ -885,23 +880,15 @@ If none can be found before reaching LIMIT, return nil." ;; inside strings or comments ending either at EOL or at valid token. (and (setq last-search-matched (re-search-forward lua-ml-begin-regexp limit 'noerror)) - - ;; Handle triple-hyphen '---[[' situation in which the multiline - ;; opener should be skipped. + ;; Ensure --[[ is not inside a comment or string. ;; - ;; In HYPHEN1-HYPHEN2-BRACKET1-BRACKET2 situation (match-beginning - ;; 0) points to HYPHEN1, but if there's another hyphen before - ;; HYPHEN1, standard syntax table will only detect comment-start - ;; at HYPHEN2. + ;; This includes "---[[" sequence, in which "--" at the beginning + ;; creates a single-line comment, and thus "-[[" is no longer a + ;; multi-line opener. ;; - ;; We could check for comment-start at HYPHEN2, but then we'd have - ;; to flush syntax-ppss cache to remove the result saying that at - ;; HYPHEN2 there's no comment or string, because under some - ;; circumstances that would hide the fact that we put a - ;; comment-start property at HYPHEN1. - (or (lua-comment-or-string-start-pos (match-beginning 0)) - (and (eq ?- (char-after (match-beginning 0))) - (eq ?- (char-before (match-beginning 0))))))) + ;; XXX: need to ensure syntax-ppss beyond (match-beginning 0) is + ;; not calculated, or otherwise we'll need to flush the cache. + (lua-comment-or-string-start-pos (match-beginning 0)))) last-search-matched)) @@ -968,23 +955,70 @@ Return the amount the indentation changed by." 0 lua-indent-level)))))) -(defun lua-find-regexp (direction regexp &optional limit ignore-p) + +(defun lua--signum (x) + "Return 1 if X is positive, -1 if negative, 0 if zero." + ;; XXX: backport from cl-extras for Emacs24 + (cond ((> x 0) 1) ((< x 0) -1) (t 0))) + +(defun lua--ensure-point-within-limit (limit backward) + "Return non-nil if point is within LIMIT going forward. + +With BACKWARD non-nil, return non-nil if point is within LIMIT +going backward. + +If point is beyond limit, move it onto limit." + (if (= (lua--signum (- (point) limit)) + (if backward 1 -1)) + t + (goto-char limit) + nil)) + + +(defun lua--escape-from-string (&optional backward) + "Move point outside of string if it is inside one. + +By default, point is placed after the string, with BACKWARD it is +placed before the string." + (interactive) + (let ((parse-state (syntax-ppss))) + (when (nth 3 parse-state) + (if backward + (goto-char (nth 8 parse-state)) + (parse-partial-sexp (point) (line-end-position) nil nil (syntax-ppss) 'syntax-table)) + t))) + + +(defun lua-find-regexp (direction regexp &optional limit) "Searches for a regular expression in the direction specified. + Direction is one of 'forward and 'backward. -By default, matches in comments and strings are ignored, but what to ignore is -configurable by specifying ignore-p. If the regexp is found, returns point -position, nil otherwise. -ignore-p returns true if the match at the current point position should be -ignored, nil otherwise." - (let ((ignore-func (or ignore-p 'lua-comment-or-string-p)) - (search-func (if (eq direction 'forward) + +Matches in comments and strings are ignored. If the regexp is +found, returns point position, nil otherwise." + (let ((search-func (if (eq direction 'forward) 're-search-forward 're-search-backward)) (case-fold-search nil)) - (catch 'found - (while (funcall search-func regexp limit t) - (if (and (not (funcall ignore-func (match-beginning 0))) - (not (funcall ignore-func (match-end 0)))) - (throw 'found (point))))))) + (cl-loop + always (or (null limit) + (lua--ensure-point-within-limit limit (not (eq direction 'forward)))) + always (funcall search-func regexp limit 'noerror) + for match-beg = (match-beginning 0) + for match-end = (match-end 0) + while (or (lua-comment-or-string-p match-beg) + (lua-comment-or-string-p match-end)) + do (let ((parse-state (syntax-ppss))) + (cond + ;; Inside a string + ((nth 3 parse-state) + (lua--escape-from-string (not (eq direction 'forward)))) + ;; Inside a comment + ((nth 4 parse-state) + (goto-char (nth 8 parse-state)) + (when (eq direction 'forward) + (forward-comment 1))))) + finally return (point)))) + (defconst lua-block-regexp (eval-when-compile @@ -1050,7 +1084,7 @@ TOKEN-TYPE determines where the token occurs on a statement. open indicates that (defun lua-get-token-type (token-info) "Returns the relevant match regexp from token info" - (nth 3 token-info)) + (nth 3 token-info)) (defun lua-backwards-to-block-begin-or-end () "Move backwards to nearest block begin or end. Returns nil if not successful." @@ -1159,29 +1193,74 @@ If optional NOREPORT is non-nil, it won't flag an error if there is no block open/close open." (interactive) ;; search backward to the beginning of the keyword if necessary - (if (eq (char-syntax (following-char)) ?w) - (re-search-backward "\\_<" nil t)) + (when (and (eq (char-syntax (following-char)) ?w) + (not (looking-at "\\_<"))) + (re-search-backward "\\_<" nil t)) (let ((position (lua-goto-matching-block-token))) (if (and (not position) (not noreport)) (error "Not on a block control keyword or brace") position))) +(defun lua-skip-ws-and-comments-backward (&optional limit) + "Move point back skipping all whitespace and comments. + +If LIMIT is given, stop at it or before. + +Return non-nil if moved point." + (interactive) + (unless (lua-string-p) + (let ((start-pos (point)) + (comment-start-pos (lua-comment-start-pos))) + (setq limit (min (point) (or limit (point-min)))) + (when comment-start-pos + (goto-char (max limit comment-start-pos))) + (when (< limit (point)) (forward-comment (- limit (point)))) + (when (< (point) limit) (goto-char limit)) + (when (/= start-pos (point)) + (point))))) + +(defun lua-skip-ws-and-comments-forward (&optional limit) + "Move point forward skipping all whitespace and comments. + +If LIMIT is given, stop at it or before. + +Return non-nil if moved point." + (interactive) + (unless (lua-string-p) + (let ((start-pos (point)) + (comment-start-pos (lua-comment-start-pos))) + (setq limit (max (point) (or limit (point-max)))) + ;; Escape from current comment. It is necessary to use "while" because + ;; luadoc parameters have non-comment face, and parse-partial-sexp with + ;; 'syntax-table flag will stop on them. + (when comment-start-pos + (goto-char comment-start-pos) + (forward-comment 1)) + (when (< (point) limit) (forward-comment (- limit (point)))) + (when (< limit (point)) (goto-char limit)) + (when (/= start-pos (point)) + (point))))) + + (defun lua-forward-line-skip-blanks (&optional back) - "Move 1 line forward (back if BACK is non-nil) skipping blank lines. + "Move 1 line forward/backward and skip all insignificant ws/comment lines. Moves point 1 line forward (or backward) skipping lines that contain no Lua code besides comments. The point is put to the beginning of the line. Returns final value of point as integer or nil if operation failed." - (catch 'found - (while t - (unless (eql (forward-line (if back -1 1)) 0) ;; 0 means success - (throw 'found nil)) - (unless (or (looking-at "\\s *\\(--.*\\)?$") - (lua-comment-or-string-p)) - (throw 'found (point)))))) + (let ((start-pos (point))) + (if back + (progn + (beginning-of-line) + (lua-skip-ws-and-comments-backward)) + (forward-line) + (lua-skip-ws-and-comments-forward)) + (beginning-of-line) + (when (> (count-lines start-pos (point)) 0) + (point)))) (eval-when-compile (defconst lua-operator-class @@ -1190,17 +1269,17 @@ Returns final value of point as integer or nil if operation failed." (defconst lua-cont-eol-regexp (eval-when-compile (concat - "\\(\\_<" + "\\(?:\\(?1:\\_<" (regexp-opt '("and" "or" "not" "in" "for" "while" "local" "function" "if" "until" "elseif" "return") t) - "\\_>\\|" - "\\(^\\|[^" lua-operator-class "]\\)" + "\\_>\\)\\|" + "\\(?:^\\|[^" lua-operator-class "]\\)\\(?2:" (regexp-opt '("+" "-" "*" "/" "%" "^" ".." "==" "=" "<" ">" "<=" ">=" "~=" "." ":" - "&" "|" "~" ">>" "<<" "~") + "&" "|" "~" ">>" "<<" "~" ",") t) - "\\)" + "\\)\\)" "\\s *\\=")) "Regexp that matches the ending of a line that needs continuation. @@ -1212,14 +1291,14 @@ an optional whitespace till the end of the line.") (eval-when-compile (concat "\\=\\s *" - "\\(\\_<" - (regexp-opt '("and" "or" "not") t) - "\\_>\\|" - (regexp-opt '("+" "-" "*" "/" "%" "^" ".." "==" + "\\(?:\\(?1:\\_<" + (regexp-opt '("and" "or" "not" "in") t) + "\\_>\\)\\|\\(?2:" + (regexp-opt '("," "+" "-" "*" "/" "%" "^" ".." "==" "=" "<" ">" "<=" ">=" "~=" "." ":" "&" "|" "~" ">>" "<<" "~") t) - "\\($\\|[^" lua-operator-class "]\\)" + "\\)\\(?:$\\|[^" lua-operator-class "]\\)" "\\)")) "Regexp that matches a line that continues previous one. @@ -1231,44 +1310,101 @@ previous one even though it looked like an end-of-statement.") (defun lua-last-token-continues-p () "Return non-nil if the last token on this line is a continuation token." (let ((line-begin (line-beginning-position)) - (line-end (line-end-position))) + return-value) (save-excursion (end-of-line) - ;; we need to check whether the line ends in a comment and - ;; skip that one. - (while (lua-find-regexp 'backward "-" line-begin 'lua-string-p) - (if (looking-at "--") - (setq line-end (point)))) - (goto-char line-end) - (re-search-backward lua-cont-eol-regexp line-begin t)))) + (lua-skip-ws-and-comments-backward line-begin) + (setq return-value (and (re-search-backward lua-cont-eol-regexp line-begin t) + (or (match-beginning 1) + (match-beginning 2)))) + (if (and return-value + (string-equal (match-string-no-properties 0) "return")) + ;; "return" keyword is ambiguous and depends on next token + (unless (save-excursion + (goto-char (match-end 0)) + (forward-comment (point-max)) + (and + ;; Not continuing: at end of file + (not (eobp)) + (or + ;; "function" keyword: it is a continuation, e.g. + ;; + ;; return + ;; function() return 123 end + ;; + (looking-at (lua-rx (symbol "function"))) + ;; Looking at semicolon or any other keyword: not continuation + (not (looking-at (lua-rx (or ";" lua-keyword))))))) + (setq return-value nil))) + return-value))) + (defun lua-first-token-continues-p () "Return non-nil if the first token on this line is a continuation token." (let ((line-end (line-end-position))) (save-excursion (beginning-of-line) + (lua-skip-ws-and-comments-forward line-end) ;; if first character of the line is inside string, it's a continuation ;; if strings aren't supposed to be indented, `lua-calculate-indentation' won't even let ;; the control inside this function - (re-search-forward lua-cont-bol-regexp line-end t)))) + (and + (re-search-forward lua-cont-bol-regexp line-end t) + (or (match-beginning 1) + (match-beginning 2)))))) -(defconst lua-block-starter-regexp - (eval-when-compile - (concat - "\\(\\_<" - (regexp-opt '("do" "while" "repeat" "until" "if" "then" - "else" "elseif" "end" "for" "local") t) - "\\_>\\)"))) -(defun lua-first-token-starts-block-p () - "Return non-nil if the first token on this line is a block starter token." - (let ((line-end (line-end-position))) - (save-excursion - (beginning-of-line) - (re-search-forward (concat "\\s *" lua-block-starter-regexp) line-end t)))) +(defun lua--backward-up-list-noerror () + "Safe version of lua-backward-up-list that does not signal an error." + (condition-case nil + (lua-backward-up-list) + (scan-error nil))) -(defun lua-is-continuing-statement-p (&optional parse-start) - "Return non-nil if the line at PARSE-START continues a statement. + +(defun lua-backward-up-list () + "Goto starter/opener of the block that contains point." + (interactive) + (let ((start-pos (point)) + end-pos) + (or + ;; Return parent block opener token if it exists. + (cl-loop + ;; Search indentation modifier backward, return nil on failure. + always (lua-find-regexp 'backward lua-indentation-modifier-regexp) + ;; Fetch info about the found token + for token = (match-string-no-properties 0) + for token-info = (lua-get-block-token-info token) + for token-type = (lua-get-token-type token-info) + ;; If the token is a close token, continue to skip its opener. If not + ;; close, stop and return found token. + while (eq token-type 'close) + ;; Find matching opener to skip it and continue from beginning. + ;; + ;; Return nil on failure. + always (let ((position (lua-find-matching-token-word token 'backward))) + (and position (goto-char position))) + finally return token-info) + (progn + (setq end-pos (point)) + (goto-char start-pos) + (signal 'scan-error + (list "Block open token not found" + ;; If start-pos == end-pos, the "obstacle" is current + (if (eql start-pos end-pos) start-pos (match-beginning 0)) + (if (eql start-pos end-pos) start-pos (match-end 0)))))))) + +(defun lua--continuation-breaking-line-p () + "Return non-nil if looking at token(-s) that forbid continued line." + (save-excursion + (lua-skip-ws-and-comments-forward (line-end-position)) + (looking-at (lua-rx (or (symbol "do" "while" "repeat" "until" + "if" "then" "elseif" "else" + "for" "local") + lua-funcheader))))) + + +(defun lua-is-continuing-statement-p-1 () + "Return non-nil if current lined continues a statement. More specifically, return the point in the line that is continued. The criteria for a continuing statement are: @@ -1276,17 +1412,71 @@ The criteria for a continuing statement are: * the last token of the previous line is a continuing op, OR the first token of the current line is a continuing op +* the expression is not enclosed by a parentheses/braces/brackets" + (let (prev-line continuation-pos parent-block-opener) + (save-excursion (setq prev-line (lua-forward-line-skip-blanks 'back))) + (and prev-line + (not (lua--continuation-breaking-line-p)) + (save-excursion + (or + ;; Binary operator or keyword that implies continuation. + (and (setq continuation-pos + (or (lua-first-token-continues-p) + (save-excursion (and (goto-char prev-line) + ;; check last token of previous nonblank line + (lua-last-token-continues-p))))) + (not + ;; Operators/keywords does not create continuation inside some blocks: + (and + (setq parent-block-opener (car-safe (lua--backward-up-list-noerror))) + (or + ;; - inside parens/brackets + (member parent-block-opener '("(" "[")) + ;; - inside braces if it is a comma + (and (eq (char-after continuation-pos) ?,) + (equal parent-block-opener "{"))))) + continuation-pos)))))) + + +(defun lua-is-continuing-statement-p (&optional parse-start) + "Returns non-nil if the line at PARSE-START should be indented as continuation line. + +This true is when the line : + +* is continuing a statement itself + +* starts with a 1+ block-closer tokens, an top-most block opener is on a continuation line " - (let ((prev-line nil)) - (save-excursion - (if parse-start (goto-char parse-start)) - (save-excursion (setq prev-line (lua-forward-line-skip-blanks 'back))) - (and prev-line - (not (lua-first-token-starts-block-p)) - (or (lua-first-token-continues-p) - (and (goto-char prev-line) - ;; check last token of previous nonblank line - (lua-last-token-continues-p))))))) + (save-excursion + (if parse-start (goto-char parse-start)) + + ;; If line starts with a series of closer tokens, whether or not the line + ;; is a continuation line is decided by the opener line, e.g. + ;; + ;; x = foo + + ;; long_function_name( + ;; long_parameter_1, + ;; long_parameter_2, + ;; long_parameter_3, + ;; ) + long_function_name2({ + ;; long_parameter_1, + ;; long_parameter_2, + ;; long_parameter_3, + ;; }) + ;; + ;; Final line, "})" is a continuation line, but it is decided by the + ;; opener line, ") + long_function_name2({", which in its turn is decided + ;; by the "long_function_name(" line, which is a continuation line + ;; because the line before it ends with a binary operator. + (cl-loop + ;; Go to opener line + while (and (lua--goto-line-beginning-rightmost-closer) + (lua--backward-up-list-noerror)) + ;; If opener line is continuing, repeat. If opener line is not + ;; continuing, return nil. + always (lua-is-continuing-statement-p-1) + ;; We get here if there was no opener to go to: check current line. + finally return (lua-is-continuing-statement-p-1)))) (defun lua-make-indentation-info-pair (found-token found-pos) "Create a pair from FOUND-TOKEN and FOUND-POS for indentation calculation. @@ -1305,7 +1495,8 @@ Don't use standalone." (cons 'relative lua-indent-level)) ;; block openers - ((member found-token (list "{" "(" "[")) + ((and lua-indent-nested-block-content-align + (member found-token (list "{" "(" "["))) (save-excursion (let ((found-bol (line-beginning-position))) (forward-comment (point-max)) @@ -1326,9 +1517,10 @@ Don't use standalone." ;; nullify a previous then if on the same line. ((member found-token (list "until" "elseif")) (save-excursion - (let ((line (line-number-at-pos))) - (if (and (lua-goto-matching-block-token found-pos 'backward) - (= line (line-number-at-pos))) + (let* ((line-beginning (line-beginning-position)) + (same-line (and (lua-goto-matching-block-token found-pos 'backward) + (<= line-beginning (point))))) + (if same-line (cons 'remove-matching 0) (cons 'relative 0))))) @@ -1337,23 +1529,41 @@ Don't use standalone." ;; either the next line will be indented correctly, or the end on the same ;; line will remove the effect of the else. ((string-equal found-token "else") - (save-excursion - (let ((line (line-number-at-pos))) - (if (and (lua-goto-matching-block-token found-pos 'backward) - (= line (line-number-at-pos))) - (cons 'replace-matching (cons 'relative lua-indent-level)) - (cons 'relative lua-indent-level))))) + (save-excursion + (let* ((line-beginning (line-beginning-position)) + (same-line (and (lua-goto-matching-block-token found-pos 'backward) + (<= line-beginning (point))))) + (if same-line + (cons 'replace-matching (cons 'relative lua-indent-level)) + (cons 'relative lua-indent-level))))) ;; Block closers. If they are on the same line as their openers, they simply ;; eat up the matching indentation modifier. Otherwise, they pull ;; indentation back to the matching block opener. ((member found-token (list ")" "}" "]" "end")) (save-excursion - (let ((line (line-number-at-pos))) - (lua-goto-matching-block-token found-pos 'backward) - (if (/= line (line-number-at-pos)) - (lua-calculate-indentation-info (point)) - (cons 'remove-matching 0))))) + (let* ((line-beginning (line-beginning-position)) + (same-line (and (lua-goto-matching-block-token found-pos 'backward) + (<= line-beginning (point)))) + (opener-pos (point)) + opener-continuation-offset) + (if same-line + (cons 'remove-matching 0) + (back-to-indentation) + (setq opener-continuation-offset + (if (lua-is-continuing-statement-p-1) lua-indent-level 0)) + + ;; Accumulate indentation up to opener, including indentation. If + ;; there were no other indentation modifiers until said opener, + ;; ensure there is no continuation after the closer. + `(multiple . ((absolute . ,(- (current-indentation) opener-continuation-offset)) + ,@(when (/= opener-continuation-offset 0) + (list (cons 'continued-line opener-continuation-offset))) + ,@(delete nil (list (lua-calculate-indentation-info-1 nil opener-pos))) + (cancel-continued-line . nil))))))) + + ((member found-token '("do" "then")) + `(multiple . ((cancel-continued-line . nil) (relative . ,lua-indent-level)))) ;; Everything else. This is from the original code: If opening a block ;; (match-data 1 exists), then push indentation one level up, if it is @@ -1365,8 +1575,8 @@ Don't use standalone." ;; end of a block matched (- lua-indent-level)))))) -(defun lua-add-indentation-info-pair (pair info) - "Add the given indentation info PAIR to the list of indentation INFO. +(defun lua-add-indentation-info-pair (pair info-list) + "Add the given indentation info PAIR to the list of indentation INFO-LIST. This function has special case handling for two tokens: remove-matching, and replace-matching. These two tokens are cleanup tokens that remove or alter the effect of a previously recorded indentation info. @@ -1380,17 +1590,27 @@ and the cdr of the replace-matching info is added in its place. This is used when a middle-of the block (the only case is 'else') is seen on the same line the block is opened." (cond + ( (eq 'multiple (car pair)) + (let ((info-pair-elts (cdr pair))) + (while info-pair-elts + (setq info-list (lua-add-indentation-info-pair (car info-pair-elts) info-list) + info-pair-elts (cdr info-pair-elts))) + info-list)) + ( (eq 'cancel-continued-line (car pair)) + (if (eq (caar info-list) 'continued-line) + (cdr info-list) + info-list)) ( (eq 'remove-matching (car pair)) - ; Remove head of list - (cdr info)) + ;; Remove head of list + (cdr info-list)) ( (eq 'replace-matching (car pair)) - ; remove head of list, and add the cdr of pair instead - (cons (cdr pair) (cdr info))) + ;; remove head of list, and add the cdr of pair instead + (cons (cdr pair) (cdr info-list))) ( (listp (cdr-safe pair)) - (nconc pair info)) + (nconc pair info-list)) ( t - ; Just add the pair - (cons pair info)))) + ;; Just add the pair + (cons pair info-list)))) (defun lua-calculate-indentation-info-1 (indentation-info bound) "Helper function for `lua-calculate-indentation-info'. @@ -1413,10 +1633,11 @@ The effect of each token can be either a shift relative to the current indentation level, or indentation to some absolute column. This information is collected in a list of indentation info pairs, which denote absolute and relative each, and the shift/column to indent to." - (let (indentation-info) - - (while (lua-is-continuing-statement-p) - (lua-forward-line-skip-blanks 'back)) + (let (indentation-info cont-stmt-pos) + (while (setq cont-stmt-pos (lua-is-continuing-statement-p)) + (lua-forward-line-skip-blanks 'back) + (when (< cont-stmt-pos (point)) + (goto-char cont-stmt-pos))) ;; calculate indentation modifiers for the line itself (setq indentation-info (list (cons 'absolute (current-indentation)))) @@ -1438,7 +1659,7 @@ and relative each, and the shift/column to indent to." ;; if it's the first non-continued line, subtract one level (when (eq (car (car indentation-info)) 'continued-line) - (pop indentation-info))) + (push (cons 'stop-continued-line (- lua-indent-level)) indentation-info))) ;; add modifiers found in this continuation line (setq indentation-info @@ -1448,19 +1669,29 @@ and relative each, and the shift/column to indent to." indentation-info)) -(defun lua-accumulate-indentation-info (info) +(defun lua-accumulate-indentation-info (reversed-indentation-info) "Accumulates the indentation information previously calculated by lua-calculate-indentation-info. Returns either the relative indentation shift, or the absolute column to indent to." - (let ((info-list (reverse info)) + (let (indentation-info (type 'relative) (accu 0)) + ;; Aggregate all neighbouring relative offsets, reversing the INFO list. + (cl-dolist (elt reversed-indentation-info) + (if (and (eq (car elt) 'relative) + (eq (caar indentation-info) 'relative)) + (setcdr (car indentation-info) (+ (cdar indentation-info) (cdr elt))) + (push elt indentation-info))) + + ;; Aggregate indentation info, taking 'absolute modifiers into account. (mapc (lambda (x) - (setq accu (if (eq 'absolute (car x)) - (progn (setq type 'absolute) - (cdr x)) - (+ accu (cdr x))))) - info-list) + (let ((new-val (cdr x))) + (if (eq 'absolute (car x)) + (progn (setq type 'absolute + accu new-val)) + (setq accu (+ accu new-val))))) + indentation-info) + (cons type accu))) (defun lua-calculate-indentation-block-modifier (&optional parse-end) @@ -1510,6 +1741,7 @@ one." ;; right hand side (or "{" "function" + "(" (seq (group-n 1 (eval lua--function-name-rx) (* blank)) (any "({"))))))) @@ -1551,6 +1783,27 @@ left-shifter expression. " (looking-at lua--left-shifter-regexp) (= old-point (match-end 1)))))) +(defun lua--goto-line-beginning-rightmost-closer (&optional parse-start) + (let (case-fold-search pos line-end-pos return-val) + (save-excursion + (if parse-start (goto-char parse-start)) + (setq line-end-pos (line-end-position)) + (back-to-indentation) + (unless (lua-comment-or-string-p) + (cl-loop while (and (<= (point) line-end-pos) + (looking-at lua-indentation-modifier-regexp)) + for token-info = (lua-get-block-token-info (match-string 0)) + for token-type = (lua-get-token-type token-info) + while (not (eq token-type 'open)) + do (progn + (setq pos (match-beginning 0) + return-val token-info) + (goto-char (match-end 0)) + (forward-comment (line-end-position)))))) + (when pos + (progn + (goto-char pos) + return-val)))) (defun lua-calculate-indentation-override (&optional parse-start) @@ -1562,26 +1815,44 @@ line containing block-open token for the last block-close token in the sequence. If not, return nil." - (let (case-fold-search token-info block-token-pos) + (let (case-fold-search rightmost-closer-info opener-info opener-pos) (save-excursion - (if parse-start (goto-char parse-start)) + (when (and (setq rightmost-closer-info (lua--goto-line-beginning-rightmost-closer parse-start)) + (setq opener-info (lua--backward-up-list-noerror)) + ;; Ensure opener matches closer. + (string-match (lua-get-token-match-re rightmost-closer-info 'backward) + (car opener-info))) + + ;; Special case: "middle" tokens like for/do, while/do, if/then, + ;; elseif/then: corresponding "end" or corresponding "else" must be + ;; unindented to the beginning of the statement, which is not + ;; necessarily the same as beginning of string that contains "do", e.g. + ;; + ;; while ( + ;; foo and + ;; bar) do + ;; hello_world() + ;; end + (setq opener-pos (point)) + (when (/= (- opener-pos (line-beginning-position)) (current-indentation)) + (unless (or + (and (string-equal (car opener-info) "do") + (member (car (lua--backward-up-list-noerror)) '("while" "for"))) + (and (string-equal (car opener-info) "then") + (member (car (lua--backward-up-list-noerror)) '("if" "elseif")))) + (goto-char opener-pos))) + + ;; (let (cont-stmt-pos) + ;; (while (setq cont-stmt-pos (lua-is-continuing-statement-p)) + ;; (goto-char cont-stmt-pos))) + ;; Exception cases: when the start of the line is an assignment, + ;; go to the start of the assignment instead of the matching item + (if (and lua-indent-close-paren-align + (member (car opener-info) '("{" "(" "[")) + (not (lua-point-is-after-left-shifter-p))) + (current-column) + (current-indentation)))))) - (back-to-indentation) - (unless (lua-comment-or-string-p) - (while - (and (looking-at lua-indentation-modifier-regexp) - (setq token-info (lua-get-block-token-info (match-string 0))) - (not (eq 'open (lua-get-token-type token-info)))) - (setq block-token-pos (match-beginning 0)) - (goto-char (match-end 0)) - (skip-syntax-forward " " (line-end-position))) - - (when (lua-goto-matching-block-token block-token-pos 'backward) - ;; Exception cases: when the start of the line is an assignment, - ;; go to the start of the assignment instead of the matching item - (if (lua-point-is-after-left-shifter-p) - (current-indentation) - (current-column))))))) (defun lua-calculate-indentation () "Return appropriate indentation for current line as Lua code." @@ -1689,11 +1960,11 @@ This function just searches for a `end' at the beginning of a line." (goto-char (point-min)) (while (re-search-forward "[\"'\\\t\\\n]" nil t) (cond - ((string= (match-string 0) "\n") - (replace-match "\\\\n")) - ((string= (match-string 0) "\t") - (replace-match "\\\\t")) - (t + ((string= (match-string 0) "\n") + (replace-match "\\\\n")) + ((string= (match-string 0) "\t") + (replace-match "\\\\t")) + (t (replace-match "\\\\\\&" t)))) (concat "'" (buffer-string) "'")))) @@ -1706,32 +1977,33 @@ This function just searches for a `end' at the beginning of a line." PROGRAM defaults to NAME, which defaults to `lua-default-application'. When called interactively, switch to the process buffer." (interactive) - (or switches - (setq switches lua-default-command-switches)) (setq name (or name (if (consp lua-default-application) (car lua-default-application) lua-default-application))) (setq program (or program lua-default-application)) - (setq lua-process-buffer (apply 'make-comint name program startfile switches)) - (setq lua-process (get-buffer-process lua-process-buffer)) - (set-process-query-on-exit-flag lua-process nil) - (with-current-buffer lua-process-buffer - ;; wait for prompt - (while (not (lua-prompt-line)) - (accept-process-output (get-buffer-process (current-buffer))) - (goto-char (point-max))) - ;; send initialization code - (lua-send-string lua-process-init-code) - - ;; enable error highlighting in stack traces - (require 'compile) - (setq lua--repl-buffer-p t) - (make-local-variable 'compilation-error-regexp-alist) - (setq compilation-error-regexp-alist - (cons (list lua-traceback-line-re 1 2) - compilation-error-regexp-alist)) - (compilation-shell-minor-mode 1) - (setq-local comint-prompt-regexp lua-prompt-regexp)) + ;; don't re-initialize if there already is a lua process + (unless (comint-check-proc (format "*%s*" name)) + (setq lua-process-buffer (apply #'make-comint name program startfile + (or switches lua-default-command-switches))) + (setq lua-process (get-buffer-process lua-process-buffer)) + (set-process-query-on-exit-flag lua-process nil) + (with-current-buffer lua-process-buffer + ;; enable error highlighting in stack traces + (require 'compile) + (setq lua--repl-buffer-p t) + (make-local-variable 'compilation-error-regexp-alist) + (setq compilation-error-regexp-alist + (cons (list lua-traceback-line-re 1 2) + compilation-error-regexp-alist)) + (compilation-shell-minor-mode 1) + (setq-local comint-prompt-regexp lua-prompt-regexp) + + ;; Don't send initialization code until seeing the prompt to ensure that + ;; the interpreter is ready. + (while (not (lua-prompt-line)) + (accept-process-output (get-buffer-process (current-buffer))) + (goto-char (point-max))) + (lua-send-string lua-process-init-code))) ;; when called interactively, switch to process buffer (if (called-interactively-p 'any) @@ -1739,8 +2011,7 @@ When called interactively, switch to the process buffer." (defun lua-get-create-process () "Return active Lua process creating one if necessary." - (unless (comint-check-proc lua-process-buffer) - (lua-start-process)) + (lua-start-process) lua-process) (defun lua-kill-process () @@ -1868,12 +2139,22 @@ Create a Lua process if one doesn't already exist." (when (buffer-live-p lua-process-buffer) (delete-windows-on lua-process-buffer))) +(defun lua--funcname-char-p (c) + "Check if character C is part of a function name. +Return nil if C is nil. See `lua-funcname-at-point'." + (and c (string-match-p "\\`[A-Za-z_.]\\'" (string c)))) + (defun lua-funcname-at-point () "Get current Name { '.' Name } sequence." - ;; FIXME: copying/modifying syntax table for each call may incur a penalty - (with-syntax-table (copy-syntax-table) - (modify-syntax-entry ?. "_") - (current-word t))) + (when (or (lua--funcname-char-p (char-before)) + (lua--funcname-char-p (char-after))) + (save-excursion + (save-match-data + (re-search-backward "\\`\\|[^A-Za-z_.]") + ;; NOTE: `point' will be either at the start of the buffer or on a + ;; non-symbol character. + (re-search-forward "\\([A-Za-z_]+\\(?:\\.[A-Za-z_]+\\)*\\)") + (match-string-no-properties 1))))) (defun lua-search-documentation () "Search Lua documentation for the word at the point." diff --git a/.emacs.d/lisp/markdown-mode.el b/.emacs.d/lisp/markdown-mode.el index 2162e4f..aa27e4e 100644 --- a/.emacs.d/lisp/markdown-mode.el +++ b/.emacs.d/lisp/markdown-mode.el @@ -188,7 +188,7 @@ line around the header title." (defcustom markdown-indent-on-enter t "Determines indentation behavior when pressing \\[newline]. -Possible settings are nil, t, and `indent-and-new-item'. +Possible settings are nil, t, and \\='indent-and-new-item. When non-nil, pressing \\[newline] will call `newline-and-indent' to indent the following line according to the context using @@ -196,7 +196,7 @@ to indent the following line according to the context using \\[electric-newline-and-maybe-indent] can still be used to insert a newline without indentation. -When set to `indent-and-new-item' and the point is in a list item +When set to \\='indent-and-new-item and the point is in a list item when \\[newline] is pressed, the list will be continued on the next line, where a new item will be inserted. @@ -490,15 +490,15 @@ completion." (defcustom markdown-split-window-direction 'any "Preference for splitting windows for static and live preview. -The default value is `any', which instructs Emacs to use +The default value is \\='any, which instructs Emacs to use `split-window-sensibly' to automatically choose how to split windows based on the values of `split-width-threshold' and `split-height-threshold' and the available windows. To force -vertically split (left and right) windows, set this to `vertical' -or `right'. To force horizontally split (top and bottom) windows, -set this to `horizontal' or `below'. +vertically split (left and right) windows, set this to \\='vertical +or \\='right. To force horizontally split (top and bottom) windows, +set this to \\='horizontal or \\='below. -If this value is `any' and `display-buffer-alist' is set then +If this value is \\='any and `display-buffer-alist' is set then `display-buffer' is used for open buffer function" :group 'markdown :type '(choice (const :tag "Automatic" any) @@ -516,8 +516,8 @@ the buffer." (defcustom markdown-live-preview-delete-export 'delete-on-destroy "Delete exported HTML file when using `markdown-live-preview-export'. -If set to `delete-on-export', delete on every export. When set to -`delete-on-destroy' delete when quitting from command +If set to \\='delete-on-export, delete on every export. When set to +\\='delete-on-destroy delete when quitting from command `markdown-live-preview-mode'. Never delete if set to nil." :group 'markdown :type '(choice @@ -864,7 +864,7 @@ Group 3 matches the text.") (defconst markdown-regex-wiki-link "\\(?:^\\|[^\\]\\)\\(?1:\\(?2:\\[\\[\\)\\(?3:[^]|]+\\)\\(?:\\(?4:|\\)\\(?5:[^]]+\\)\\)?\\(?6:\\]\\]\\)\\)" "Regular expression for matching wiki links. -This matches typical bracketed [[WikiLinks]] as well as `aliased' +This matches typical bracketed [[WikiLinks]] as well as \\='aliased wiki links of the form [[PageName|link text]]. The meanings of the first and second components depend on the value of `markdown-wiki-link-alias-first'. @@ -1344,7 +1344,7 @@ block construct when it is matched using to the text matching START-REGEX-OR-FUN, END-PROPERTY to END-REGEX-OR-FUN, and MIDDLE-PROPERTY to the text in between the two. The value of *-PROPERTY is the `match-data' when the regexp was matched to the text. In the case of -MIDDLE-PROPERTY, the value is a false match data of the form `(begin end)', with +MIDDLE-PROPERTY, the value is a false match data of the form \\='(begin end), with begin and end set to the edges of the \"middle\" text. This makes fontification easier.") @@ -1614,7 +1614,7 @@ start which was previously propertized." (markdown-maybe-funcall-regexp (caar correct-entry)) (buffer-substring - (point-at-bol) (point-at-eol))) + (line-beginning-position) (line-end-position))) (- (match-end 1) (match-beginning 1)))) (end-reg (markdown-maybe-funcall-regexp (cl-caadr correct-entry) start-length))) @@ -1625,7 +1625,7 @@ start which was previously propertized." ;; we assume the opening constructs take up (only) an entire line, ;; so we re-check the current line (let* ((block-start (match-beginning 0)) - (cur-line (buffer-substring (point-at-bol) (point-at-eol))) + (cur-line (buffer-substring (line-beginning-position) (line-end-position))) ;; find entry in `markdown-fenced-block-pairs' corresponding ;; to regex which was matched (correct-entry @@ -1636,7 +1636,7 @@ start which was previously propertized." cur-line)) markdown-fenced-block-pairs)) (enclosed-text-start - (save-excursion (1+ (point-at-eol)))) + (save-excursion (1+ (line-end-position)))) (end-reg (markdown-maybe-funcall-regexp (cl-caadr correct-entry) @@ -1651,7 +1651,7 @@ start which was previously propertized." (beginning-of-line) (re-search-forward (markdown-maybe-funcall-regexp (caar correct-entry)) - (point-at-eol))) + (line-end-position))) ;; mark starting, even if ending is outside of region (put-text-property (match-beginning 0) (match-end 0) prop (match-data t)) (markdown-propertize-end-match @@ -2563,7 +2563,7 @@ the returned list would be (1 14 3 5 \"- \" nil (1 6 1 4 4 5 5 6)) If the point is not inside a list item, return nil." - (car (get-text-property (point-at-bol) 'markdown-list-item))) + (car (get-text-property (line-beginning-position) 'markdown-list-item))) (defun markdown-list-item-at-point-p () "Return t if there is a list item at the point and nil otherwise." @@ -2647,7 +2647,7 @@ The return value has the same form as that of (defun markdown-bounds-of-thing-at-point (thing) "Call `bounds-of-thing-at-point' for THING with slight modifications. -Does not include trailing newlines when THING is `line'. Handles the +Does not include trailing newlines when THING is \\='line. Handles the end of buffer case by setting both endpoints equal to the value of `point-max', since an empty region will trigger empty markup insertion. Return bounds of form (beg . end) if THING is found, or nil otherwise." @@ -2764,7 +2764,7 @@ data. See `markdown-code-block-at-point-p' for code blocks." Uses text properties at the beginning of the line position. This includes pre blocks, tilde-fenced code blocks, and GFM quoted code blocks. Return nil otherwise." - (let ((bol (save-excursion (goto-char pos) (point-at-bol)))) + (let ((bol (save-excursion (goto-char pos) (line-beginning-position)))) (or (get-text-property bol 'markdown-pre) (let* ((bounds (markdown-get-enclosing-fenced-block-construct pos)) (second (cl-second bounds))) @@ -2795,7 +2795,7 @@ Set match data for `markdown-regex-header'." (defun markdown-pipe-at-bol-p () "Return non-nil if the line begins with a pipe symbol. This may be useful for tables and Pandoc's line_blocks extension." - (char-equal (char-after (point-at-bol)) ?|)) + (char-equal (char-after (line-beginning-position)) ?|)) ;;; Markdown Font Lock Matching Functions ===================================== @@ -2978,7 +2978,7 @@ $..$ or `markdown-regex-math-inline-double' for matching $$..$$." (set-match-data (cl-seventh bounds)) ;; Step at least one character beyond point. Otherwise ;; `font-lock-fontify-keywords-region' infloops. - (goto-char (min (1+ (max (point-at-eol) first)) + (goto-char (min (1+ (max (line-end-position) first)) (point-max))) t))) @@ -3028,13 +3028,13 @@ Restore match data previously stored in PROPERTY." (defun markdown-match-pre-blocks (last) "Match preformatted blocks from point to LAST. -Use data stored in `markdown-pre' text property during syntax +Use data stored in \\='markdown-pre text property during syntax analysis." (markdown-match-propertized-text 'markdown-pre last)) (defun markdown-match-gfm-code-blocks (last) "Match GFM quoted code blocks from point to LAST. -Use data stored in `markdown-gfm-code' text property during syntax +Use data stored in \\='markdown-gfm-code text property during syntax analysis." (markdown-match-propertized-text 'markdown-gfm-code last)) @@ -3056,7 +3056,7 @@ analysis." (defun markdown-match-blockquotes (last) "Match blockquotes from point to LAST. -Use data stored in `markdown-blockquote' text property during syntax +Use data stored in \\='markdown-blockquote text property during syntax analysis." (markdown-match-propertized-text 'markdown-blockquote last)) @@ -3108,8 +3108,8 @@ processed elements." (match-beginning 0) (match-end 2) 'face prohibited-faces) (markdown-range-property-any (match-beginning 4) (match-end 0) 'face prohibited-faces) - (and (char-equal (char-after (point-at-bol)) ?<) - (char-equal (char-after (1+ (point-at-bol))) ?<))) + (and (char-equal (char-after (line-beginning-position)) ?<) + (char-equal (char-after (1+ (line-beginning-position))) ?<))) (set-match-data nil) (setq found t)))) ;; Match opening exclamation point (optional) and left bracket. @@ -4478,13 +4478,13 @@ code block in an indirect buffer after insertion." (markdown-ensure-blank-line-before) (indent-to indent) (insert "```" gfm-open-brace lang gfm-close-brace) - (markdown-syntax-propertize-fenced-block-constructs (point-at-bol) end)) + (markdown-syntax-propertize-fenced-block-constructs (line-beginning-position) end)) (let ((indent (current-indentation)) start-bol) (delete-horizontal-space :backward-only) (markdown-ensure-blank-line-before) (indent-to indent) - (setq start-bol (point-at-bol)) + (setq start-bol (line-beginning-position)) (insert "```" gfm-open-brace lang gfm-close-brace "\n") (indent-to indent) (unless edit (insert ?\n)) @@ -4982,7 +4982,7 @@ If the point is at a table, move to the next row. Otherwise, indent according to value of `markdown-indent-on-enter'. When it is nil, simply call `newline'. Otherwise, indent the next line following RET using `markdown-indent-line'. Furthermore, when it -is set to `indent-and-new-item' and the point is in a list item, +is set to \\='indent-and-new-item and the point is in a list item, start a new item with the same indentation. If the point is in an empty list item, remove it (so that pressing RET twice when in a list simply adds a blank line)." @@ -5786,7 +5786,7 @@ See `imenu-create-index-function' and `imenu--index-alist' for details." ;; Headings (goto-char (point-min)) (while (re-search-forward markdown-regex-header (point-max) t) - (when (and (not (markdown-code-block-at-point-p (point-at-bol))) + (when (and (not (markdown-code-block-at-point-p (line-beginning-position))) (not (markdown-text-property-at-point 'markdown-yaml-metadata-begin))) (cond ((setq heading (match-string-no-properties 1)) @@ -6236,7 +6236,7 @@ increase the indentation by one level." ((string-match-p "[\\*\\+-]\\|#\\." marker) (insert new-indent marker)))) ;; Propertize the newly inserted list item now - (markdown-syntax-propertize-list-items (point-at-bol) (point-at-eol))))) + (markdown-syntax-propertize-list-items (line-beginning-position) (line-end-position))))) (defun markdown-move-list-item-up () "Move the current list item up in the list when possible. @@ -6486,7 +6486,7 @@ means move forward N blocks." (beginning-of-line) ;; Skip over code block endings. (when (markdown-range-properties-exist - (point-at-bol) (point-at-eol) + (line-beginning-position) (line-end-position) '(markdown-gfm-block-end markdown-tilde-fence-end)) (forward-line -1)) @@ -6515,11 +6515,11 @@ means move forward N blocks." (not (markdown-cur-line-blank-p)) (not (looking-at markdown-regex-blockquote)) (not (markdown-range-properties-exist - (point-at-bol) (point-at-eol) + (line-beginning-position) (line-end-position) '(markdown-gfm-block-end markdown-tilde-fence-end)))) (setq skip (markdown-range-properties-exist - (point-at-bol) (point-at-eol) + (line-beginning-position) (line-end-position) '(markdown-gfm-block-begin markdown-tilde-fence-begin))) (forward-line -1)) @@ -6566,11 +6566,11 @@ means move backward N blocks." (not (markdown-cur-line-blank-p)) (not (looking-at markdown-regex-blockquote)) (not (markdown-range-properties-exist - (point-at-bol) (point-at-eol) + (line-beginning-position) (line-end-position) '(markdown-gfm-block-begin markdown-tilde-fence-begin)))) (setq skip (markdown-range-properties-exist - (point-at-bol) (point-at-eol) + (line-beginning-position) (line-end-position) '(markdown-gfm-block-end markdown-tilde-fence-end))) (forward-line)))))))) @@ -6594,7 +6594,7 @@ ARG = -N means move forward N blocks." (cond ;; Code blocks ((and (markdown-code-block-at-pos (point)) ;; this line - (markdown-code-block-at-pos (point-at-bol 0))) ;; previous line + (markdown-code-block-at-pos (line-beginning-position 0))) ;; previous line (forward-line -1) (while (and (markdown-code-block-at-point-p) (not (bobp))) (forward-line -1)) @@ -6849,7 +6849,7 @@ Only visible heading lines are considered, unless INVISIBLE-OK is non-nil." (defun markdown-on-heading-p () "Return non-nil if point is on a heading line." - (get-text-property (point-at-bol) 'markdown-heading)) + (get-text-property (line-beginning-position) 'markdown-heading)) (defun markdown-end-of-subtree (&optional invisible-OK) "Move to the end of the current subtree. @@ -6895,7 +6895,7 @@ setext header, but should not be folded." (outline-next-visible-heading 1)) (while (< (point) (point-max)) (when (markdown-code-block-at-point-p) - (outline-flag-region (1- (point-at-bol)) (point-at-eol) t)) + (outline-flag-region (1- (line-beginning-position)) (line-end-position) t)) (outline-next-visible-heading 1)))) (defvar markdown-cycle-global-status 1) @@ -7847,8 +7847,7 @@ Translate filenames using `markdown-filename-translate-function'." (title-end (match-end 7)) (title (match-string-no-properties 7)) ;; Markup part - (mp (list 'face 'markdown-markup-face - 'invisible 'markdown-markup + (mp (list 'invisible 'markdown-markup 'rear-nonsticky t 'font-lock-multiline t)) ;; Link part (without face) @@ -7858,26 +7857,28 @@ Translate filenames using `markdown-filename-translate-function'." 'help-echo (if title (concat title "\n" url) url))) ;; URL part (up (list 'keymap markdown-mode-mouse-map - 'face 'markdown-url-face 'invisible 'markdown-markup 'mouse-face 'markdown-highlight-face 'font-lock-multiline t)) ;; URL composition character (url-char (markdown--first-displayable markdown-url-compose-char)) ;; Title part - (tp (list 'face 'markdown-link-title-face - 'invisible 'markdown-markup + (tp (list 'invisible 'markdown-markup 'font-lock-multiline t))) (dolist (g '(1 2 4 5 8)) (when (match-end g) - (add-text-properties (match-beginning g) (match-end g) mp))) + (add-text-properties (match-beginning g) (match-end g) mp) + (add-face-text-property (match-beginning g) (match-end g) 'markdown-markup-face))) ;; Preserve existing faces applied to link part (e.g., inline code) (when link-start (add-text-properties link-start link-end lp) - (add-face-text-property link-start link-end - 'markdown-link-face 'append)) - (when url-start (add-text-properties url-start url-end up)) - (when title-start (add-text-properties url-end title-end tp)) + (add-face-text-property link-start link-end 'markdown-link-face)) + (when url-start + (add-text-properties url-start url-end up) + (add-face-text-property url-start url-end 'markdown-url-face)) + (when title-start + (add-text-properties url-end title-end tp) + (add-face-text-property url-end title-end 'markdown-link-title-face)) (when (and markdown-hide-urls url-start) (compose-region url-start (or title-end url-end) url-char)) t))) @@ -7890,13 +7891,11 @@ Translate filenames using `markdown-filename-translate-function'." (ref-start (match-beginning 6)) (ref-end (match-end 6)) ;; Markup part - (mp (list 'face 'markdown-markup-face - 'invisible 'markdown-markup + (mp (list 'invisible 'markdown-markup 'rear-nonsticky t 'font-lock-multiline t)) ;; Link part (lp (list 'keymap markdown-mode-mouse-map - 'face 'markdown-link-face 'mouse-face 'markdown-highlight-face 'font-lock-multiline t 'help-echo (lambda (_ __ pos) @@ -7908,16 +7907,20 @@ Translate filenames using `markdown-filename-translate-function'." ;; URL composition character (url-char (markdown--first-displayable markdown-url-compose-char)) ;; Reference part - (rp (list 'face 'markdown-reference-face - 'invisible 'markdown-markup + (rp (list 'invisible 'markdown-markup 'font-lock-multiline t))) (dolist (g '(1 2 4 5 8)) (when (match-end g) - (add-text-properties (match-beginning g) (match-end g) mp))) - (when link-start (add-text-properties link-start link-end lp)) - (when ref-start (add-text-properties ref-start ref-end rp) - (when (and markdown-hide-urls (> (- ref-end ref-start) 2)) - (compose-region ref-start ref-end url-char))) + (add-text-properties (match-beginning g) (match-end g) mp) + (add-face-text-property (match-beginning g) (match-end g) 'markdown-markup-face))) + (when link-start + (add-text-properties link-start link-end lp) + (add-face-text-property link-start link-end 'markdown-link-face)) + (when ref-start + (add-text-properties ref-start ref-end rp) + (add-face-text-property ref-start ref-end 'markdown-reference-face) + (when (and markdown-hide-urls (> (- ref-end ref-start) 2)) + (compose-region ref-start ref-end url-char))) t))) (defun markdown-fontify-angle-uris (last) @@ -8376,7 +8379,7 @@ return the number of paragraphs left to move." (while (and (not (eobp)) (> arg 0) (= (forward-paragraph 1) 0) - (or (markdown-code-block-at-pos (point-at-bol 0)) + (or (markdown-code-block-at-pos (line-beginning-position 0)) (setq arg (1- arg))))) ;; Move backward by one paragraph with negative ARG (always -1). (let ((start (point))) @@ -8394,7 +8397,7 @@ return the number of paragraphs left to move." ((looking-at "^|\\s-*") (goto-char (match-end 0))) ;; Return point if the paragraph passed was a code block. - ((markdown-code-block-at-pos (point-at-bol 2)) + ((markdown-code-block-at-pos (line-beginning-position 2)) (goto-char start))))) arg) @@ -8520,7 +8523,7 @@ Returns nil if non-applicable." (insert markup)) (goto-char pos) (insert markup)) - (syntax-propertize (point-at-eol)) + (syntax-propertize (line-end-position)) t))))) (defun markdown-toggle-gfm-checkbox () @@ -8755,9 +8758,9 @@ Use matching function MATCHER." (end (match-end 0)) ;; Find positions outside opening and closing backquotes. (bol-prev (progn (goto-char start) - (if (bolp) (point-at-bol 0) (point-at-bol)))) + (if (bolp) (line-beginning-position 0) (line-beginning-position)))) (eol-next (progn (goto-char end) - (if (bolp) (point-at-bol 2) (point-at-bol 3)))) + (if (bolp) (line-beginning-position 2) (line-beginning-position 3)))) lang) (if (and markdown-fontify-code-blocks-natively (or (setq lang (markdown-code-block-lang)) @@ -8843,8 +8846,8 @@ at the END of code blocks." (save-excursion (if (fboundp 'edit-indirect-region) (let* ((bounds (markdown-get-enclosing-fenced-block-construct)) - (begin (and bounds (not (null (nth 0 bounds))) (goto-char (nth 0 bounds)) (point-at-bol 2))) - (end (and bounds(not (null (nth 1 bounds))) (goto-char (nth 1 bounds)) (point-at-bol 1)))) + (begin (and bounds (not (null (nth 0 bounds))) (goto-char (nth 0 bounds)) (line-beginning-position 2))) + (end (and bounds(not (null (nth 1 bounds))) (goto-char (nth 1 bounds)) (line-beginning-position 1)))) (if (and begin end) (let* ((indentation (and (goto-char (nth 0 bounds)) (current-indentation))) (lang (markdown-code-block-lang)) @@ -9049,7 +9052,7 @@ This function assumes point is on a table." (while (and (re-search-forward markdown-table-dline-regexp end t) (setq cnt (1+ cnt)) - (< (point-at-eol) pos)))) + (< (line-end-position) pos)))) cnt)) (defun markdown--thing-at-wiki-link (pos) @@ -9080,7 +9083,7 @@ a table." (if (looking-at "|[^|\r\n]*") (let* ((pos (match-beginning 0)) (val (buffer-substring (1+ pos) (match-end 0)))) - (goto-char (min (point-at-eol) (+ 2 pos))) + (goto-char (min (line-end-position) (+ 2 pos))) ;; Trim whitespaces (setq val (replace-regexp-in-string "\\`[ \t]+" "" val) val (replace-regexp-in-string "[ \t]+\\'" "" val))) @@ -9105,7 +9108,7 @@ beyond the last delimiter. This function assumes point is on a table." (beginning-of-line 1) (when (> n 0) - (while (and (> n 0) (search-forward "|" (point-at-eol) t)) + (while (and (> n 0) (search-forward "|" (line-end-position) t)) (when (and (not (looking-back "\\\\|" (line-beginning-position))) (not (markdown--thing-at-wiki-link (match-beginning 0)))) (cl-decf n))) @@ -9260,8 +9263,8 @@ With optional argument ARG, insert below the current row." (unless (markdown-table-at-point-p) (user-error "Not at a table")) (let ((col (current-column))) - (kill-region (point-at-bol) - (min (1+ (point-at-eol)) (point-max))) + (kill-region (line-beginning-position) + (min (1+ (line-end-position)) (point-max))) (unless (markdown-table-at-point-p) (beginning-of-line 0)) (move-to-column col))) @@ -9277,8 +9280,8 @@ With optional argument UP, move it up." (unless (markdown-table-at-point-p) (goto-char pos) (user-error "Cannot move row further")) (goto-char pos) (beginning-of-line 1) (setq pos (point)) - (setq txt (buffer-substring (point) (1+ (point-at-eol)))) - (delete-region (point) (1+ (point-at-eol))) + (setq txt (buffer-substring (point) (1+ (line-end-position)))) + (delete-region (point) (1+ (line-end-position))) (beginning-of-line tonew) (insert txt) (beginning-of-line 0) (move-to-column col))) @@ -9655,7 +9658,7 @@ rows and columns and the column alignment." (make-list columns (concat align "|"))))) (if (string-match "^[ \t]*$" (buffer-substring-no-properties - (point-at-bol) (point))) + (line-beginning-position) (point))) (beginning-of-line 1) (newline)) (dotimes (_ rows) (insert line)) diff --git a/.emacs.d/lisp/rust-cargo.el b/.emacs.d/lisp/rust-cargo.el new file mode 100644 index 0000000..cfbcd81 --- /dev/null +++ b/.emacs.d/lisp/rust-cargo.el @@ -0,0 +1,124 @@ +;;; rust-cargo.el --- Support for cargo -*-lexical-binding: t-*- +;;; Commentary: + +;; This library implements support for running `cargo'. + +;;; Code: + +(require 'json) + +;;; Options + +(defcustom rust-cargo-bin "cargo" + "Path to cargo executable." + :type 'string + :group 'rust-mode) + +(defcustom rust-always-locate-project-on-open nil + "Whether to run `cargo locate-project' every time `rust-mode' is activated." + :type 'boolean + :group 'rust-mode) + +(defcustom rust-cargo-default-arguments "" + "Default arguments when running common cargo commands." + :type 'string + :group 'rust-mode) + +;;; Buffer Project + +(defvar-local rust-buffer-project nil) + +(defun rust-buffer-project () + "Get project root if possible." + ;; Copy environment variables into the new buffer, since + ;; with-temp-buffer will re-use the variables' defaults, even if + ;; they have been changed in this variable using e.g. envrc-mode. + ;; See https://github.com/purcell/envrc/issues/12. + (let ((env process-environment) + (path exec-path)) + (with-temp-buffer + ;; Copy the entire environment just in case there's something we + ;; don't know we need. + (setq-local process-environment env) + ;; Set PATH so we can find cargo. + (setq-local exec-path path) + (let ((ret (process-file rust-cargo-bin nil (list (current-buffer) nil) nil "locate-project" "--workspace"))) + (when (/= ret 0) + (error "`cargo locate-project' returned %s status: %s" ret (buffer-string))) + (goto-char 0) + (let ((output (json-read))) + (cdr (assoc-string "root" output))))))) + +(defun rust-buffer-crate () + "Try to locate Cargo.toml using `locate-dominating-file'." + (let ((dir (locate-dominating-file default-directory "Cargo.toml"))) + (if dir dir default-directory))) + +(defun rust-update-buffer-project () + (setq-local rust-buffer-project (rust-buffer-project))) + +(defun rust-maybe-initialize-buffer-project () + (setq-local rust-buffer-project nil) + (when rust-always-locate-project-on-open + (rust-update-buffer-project))) + +(add-hook 'rust-mode-hook #'rust-maybe-initialize-buffer-project) + +;;; Internal + +(defun rust--compile (format-string &rest args) + (when (null rust-buffer-project) + (rust-update-buffer-project)) + (let ((default-directory + (or (and rust-buffer-project + (file-name-directory rust-buffer-project)) + default-directory))) + (compile (apply #'format format-string args)))) + +;;; Commands + +(defun rust-check () + "Compile using `cargo check`" + (interactive) + (rust--compile "%s check %s" rust-cargo-bin rust-cargo-default-arguments)) + +(defun rust-compile () + "Compile using `cargo build`" + (interactive) + (rust--compile "%s build %s" rust-cargo-bin rust-cargo-default-arguments)) + +(defun rust-compile-release () + "Compile using `cargo build --release`" + (interactive) + (rust--compile "%s build --release" rust-cargo-bin)) + +(defun rust-run () + "Run using `cargo run`" + (interactive) + (rust--compile "%s run %s" rust-cargo-bin rust-cargo-default-arguments)) + +(defun rust-run-release () + "Run using `cargo run --release`" + (interactive) + (rust--compile "%s run --release" rust-cargo-bin)) + +(defun rust-test () + "Test using `cargo test`" + (interactive) + (rust--compile "%s test %s" rust-cargo-bin rust-cargo-default-arguments)) + +(defun rust-run-clippy () + "Run `cargo clippy'." + (interactive) + (when (null rust-buffer-project) + (rust-update-buffer-project)) + (let* ((args (list rust-cargo-bin "clippy" + (concat "--manifest-path=" rust-buffer-project))) + ;; set `compile-command' temporarily so `compile' doesn't + ;; clobber the existing value + (compile-command (mapconcat #'shell-quote-argument args " "))) + (rust--compile compile-command))) + +;;; _ +(provide 'rust-cargo) +;;; rust-cargo.el ends here diff --git a/.emacs.d/lisp/rust-compile.el b/.emacs.d/lisp/rust-compile.el new file mode 100644 index 0000000..1bb3103 --- /dev/null +++ b/.emacs.d/lisp/rust-compile.el @@ -0,0 +1,83 @@ +;;; rust-compile.el --- Compile facilities -*-lexical-binding: t-*- +;;; Commentary: + +;; This library teaches `compilation-mode' about "rustc" output. + +;;; Code: + +(require 'compile) + +;;; _ + +(defvar rustc-compilation-location + (let ((file "\\([^\n]+\\)") + (start-line "\\([0-9]+\\)") + (start-col "\\([0-9]+\\)")) + (concat "\\(" file ":" start-line ":" start-col "\\)"))) + +(defvar rustc-compilation-regexps + (let ((re (concat "^\\(?:error\\|\\(warning\\)\\|\\(note\\)\\)[^\0]+?--> " + rustc-compilation-location))) + (cons re '(4 5 6 (1 . 2) 3))) + "Specifications for matching errors in rustc invocations. +See `compilation-error-regexp-alist' for help on their format.") + +(defvar rustc-colon-compilation-regexps + (let ((re (concat "^ *::: " rustc-compilation-location))) + (cons re '(2 3 4 0 1))) + "Specifications for matching `:::` hints in rustc invocations. +See `compilation-error-regexp-alist' for help on their format.") + +(defvar rustc-refs-compilation-regexps + (let ((re "^\\([0-9]+\\)[[:space:]]*|")) + (cons re '(nil 1 nil 0 1))) + "Specifications for matching code references in rustc invocations. +See `compilation-error-regexp-alist' for help on their format.") + +;; Match test run failures and panics during compilation as +;; compilation warnings +(defvar cargo-compilation-regexps + '("', \\(\\([^:]+\\):\\([0-9]+\\)\\)" + 2 3 nil nil 1) + "Specifications for matching panics in cargo test invocations. +See `compilation-error-regexp-alist' for help on their format.") + +(defun rustc-scroll-down-after-next-error () + "In the new style error messages, the regular expression +matches on the file name (which appears after `-->`), but the +start of the error appears a few lines earlier. This hook runs +after `next-error' (\\[next-error]); it simply scrolls down a few lines in +the compilation window until the top of the error is visible." + (save-selected-window + (when (eq major-mode 'rust-mode) + (select-window (get-buffer-window next-error-last-buffer 'visible)) + (when (save-excursion + (beginning-of-line) + (looking-at " *-->")) + (let ((start-of-error + (save-excursion + (beginning-of-line) + (while (not (looking-at "^[a-z]+:\\|^[a-z]+\\[E[0-9]+\\]:")) + (forward-line -1)) + (point)))) + (set-window-start (selected-window) start-of-error)))))) + +(eval-after-load 'compile + '(progn + (add-to-list 'compilation-error-regexp-alist-alist + (cons 'rustc-refs rustc-refs-compilation-regexps)) + (add-to-list 'compilation-error-regexp-alist 'rustc-refs) + (add-to-list 'compilation-error-regexp-alist-alist + (cons 'rustc rustc-compilation-regexps)) + (add-to-list 'compilation-error-regexp-alist 'rustc) + (add-to-list 'compilation-error-regexp-alist-alist + (cons 'rustc-colon rustc-colon-compilation-regexps)) + (add-to-list 'compilation-error-regexp-alist 'rustc-colon) + (add-to-list 'compilation-error-regexp-alist-alist + (cons 'cargo cargo-compilation-regexps)) + (add-to-list 'compilation-error-regexp-alist 'cargo) + (add-hook 'next-error-hook #'rustc-scroll-down-after-next-error))) + +;;; _ +(provide 'rust-compile) +;;; rust-compile.el ends here diff --git a/.emacs.d/lisp/rust-mode-tests.el b/.emacs.d/lisp/rust-mode-tests.el new file mode 100644 index 0000000..e4949b2 --- /dev/null +++ b/.emacs.d/lisp/rust-mode-tests.el @@ -0,0 +1,3637 @@ +;;; rust-mode-tests.el --- ERT tests for rust-mode.el -*- lexical-binding: t; -*- + +(require 'rust-mode) +(require 'ert) +(require 'cl-lib) +(require 'compile) +(require 'imenu) + +(defconst rust-test-fill-column 32) +(setq-default indent-tabs-mode nil) + +(defmacro rust-test-silence (messages &rest body) + `(let ((f (lambda (orig-fun format-string &rest args) + (unless (member format-string ,messages) + (apply orig-fun format-string args))))) + (unwind-protect + (progn + (advice-add 'message :around f) + ,@body) + (advice-remove 'message f)))) + +(defun rust-compare-code-after-manip (_original _point-pos _manip-func expected got) + (equal expected got)) + +(defun rust-test-explain-bad-manip (original point-pos _manip-func expected got) + (if (equal expected got) + nil + (list + ;; The (goto-char) and (insert) business here is just for + ;; convenience--after an error, you can copy-paste that into emacs eval to + ;; insert the bare strings into a buffer + "Rust code was manipulated wrong after:" + `(insert ,original) + `(goto-char ,point-pos) + 'expected `(insert ,expected) + 'got `(insert ,got) + (cl-loop for i from 0 to (max (length original) (length expected)) + for oi = (if (< i (length got)) (elt got i)) + for ei = (if (< i (length expected)) (elt expected i)) + while (equal oi ei) + finally return `(first-difference-at + (goto-char ,(+ 1 i)) + expected ,(char-to-string ei) + got ,(char-to-string oi)))))) +(put 'rust-compare-code-after-manip 'ert-explainer + 'rust-test-explain-bad-manip) + +(defun rust-test-manip-code (original point-pos manip-func expected) + (with-temp-buffer + (rust-mode) + (insert original) + (goto-char point-pos) + (funcall manip-func) + (should (rust-compare-code-after-manip + original point-pos manip-func expected (buffer-string))))) + +(defun rust-test-fill-paragraph (unfilled expected &optional start-pos end-pos) + "We're going to run through many scenarios here--the point should be able to be anywhere from the start-pos (defaults to 1) through end-pos (defaults to the length of what was passed in) and (fill-paragraph) should return the same result. It should also work with fill-region from start-pos to end-pos. + +Also, the result should be the same regardless of whether the code is at the beginning or end of the file. (If you're not careful, that can make a difference.) So we test each position given above with the passed code at the beginning, the end, neither and both. So we do this a total of 1 + (end-pos - start-pos)*4 times. Oy." + (let* ((start-pos (or start-pos 1)) + (end-pos (or end-pos (length unfilled))) + (padding "\n \n") + (padding-len (length padding))) + (cl-loop + for pad-at-beginning from 0 to 1 + do (cl-loop for pad-at-end from 0 to 1 + with padding-beginning = (if (= 0 pad-at-beginning) "" padding) + with padding-end = (if (= 0 pad-at-end) "" padding) + with padding-adjust = (* padding-len pad-at-beginning) + ;; If we're adding space to the beginning, and our start position + ;; is at the very beginning, we want to test within the added space. + ;; Otherwise adjust the start and end for the beginning padding. + with start-pos = (if (= 1 start-pos) 1 (+ padding-adjust start-pos)) + with end-pos = (+ end-pos padding-adjust) + do (cl-loop for pos from start-pos to end-pos + do (rust-test-manip-code + (concat padding-beginning unfilled padding-end) + pos + (lambda () + (let ((fill-column rust-test-fill-column)) + (fill-paragraph))) + (concat padding-beginning expected padding-end))))) + ;; In addition to all the fill-paragraph tests, check that it works using fill-region + (rust-test-manip-code + unfilled + start-pos + (lambda () + (let ((fill-column rust-test-fill-column)) + (fill-region start-pos end-pos))) + expected) + )) + +(ert-deftest fill-paragraph-top-level-multi-line-style-doc-comment-second-line () + (rust-test-fill-paragraph + "/** + * This is a very very very very very very very long string + */" + "/** + * This is a very very very very + * very very very long string + */")) + + +(ert-deftest fill-paragraph-top-level-multi-line-style-doc-comment-first-line () + (rust-test-fill-paragraph + "/** This is a very very very very very very very long string + */" + "/** This is a very very very + * very very very very long + * string + */")) + +(ert-deftest fill-paragraph-multi-paragraph-multi-line-style-doc-comment () + (let + ((multi-paragraph-unfilled + "/** + * This is the first really really really really really really really long paragraph + * + * This is the second really really really really really really long paragraph + */")) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/** + * This is the first really + * really really really really + * really really long paragraph + * + * This is the second really really really really really really long paragraph + */" + 1 89) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/** + * This is the first really really really really really really really long paragraph + * + * This is the second really + * really really really really + * really long paragraph + */" + 90))) + +(ert-deftest fill-paragraph-multi-paragraph-single-line-style-doc-comment () + (let + ((multi-paragraph-unfilled + "/// This is the first really really really really really really really long paragraph +/// +/// This is the second really really really really really really long paragraph")) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/// This is the first really +/// really really really really +/// really really long paragraph +/// +/// This is the second really really really really really really long paragraph" + 1 86) + (rust-test-fill-paragraph + multi-paragraph-unfilled + "/// This is the first really really really really really really really long paragraph +/// +/// This is the second really +/// really really really really +/// really long paragraph" + 87))) + +(ert-deftest fill-paragraph-multi-paragraph-single-line-style-indented () + (rust-test-fill-paragraph + " // This is the first really really really really really really really long paragraph + // + // This is the second really really really really really really long paragraph" + " // This is the first really + // really really really + // really really really + // long paragraph + // + // This is the second really really really really really really long paragraph" 1 89)) + +(ert-deftest fill-paragraph-multi-line-style-comment () + (rust-test-fill-paragraph + "/* This is a very very very very very very very very long string + */" + "/* This is a very very very very + * very very very very long + * string + */")) + +(ert-deftest fill-paragraph-multi-line-style-inner-doc-comment () + (rust-test-fill-paragraph + "/*! This is a very very very very very very very long string + */" + "/*! This is a very very very + * very very very very long + * string + */")) + +(ert-deftest fill-paragraph-single-line-style-inner-doc-comment () + (rust-test-fill-paragraph + "//! This is a very very very very very very very long string" + "//! This is a very very very +//! very very very very long +//! string")) + +(ert-deftest fill-paragraph-prefixless-multi-line-doc-comment () + (rust-test-fill-paragraph + "/** +This is my summary. Blah blah blah blah blah. Dilly dally dilly dally dilly dally doo. + +This is some more text. Fee fie fo fum. Humpty dumpty sat on a wall. +*/" + "/** +This is my summary. Blah blah +blah blah blah. Dilly dally +dilly dally dilly dally doo. + +This is some more text. Fee fie fo fum. Humpty dumpty sat on a wall. +*/" 4 90)) + +(ert-deftest fill-paragraph-with-no-space-after-star-prefix () + (rust-test-fill-paragraph + "/** + *This is a very very very very very very very long string + */" + "/** + *This is a very very very very + *very very very long string + */")) + +(ert-deftest fill-paragraph-single-line-style-with-code-before () + (rust-test-fill-paragraph + "fn foo() { } +/// This is my comment. This is more of my comment. This is even more." + "fn foo() { } +/// This is my comment. This is +/// more of my comment. This is +/// even more." 14)) + +(ert-deftest fill-paragraph-single-line-style-with-code-after () + (rust-test-fill-paragraph + "/// This is my comment. This is more of my comment. This is even more. +fn foo() { }" + "/// This is my comment. This is +/// more of my comment. This is +/// even more. +fn foo() { }" 1 73)) + +(ert-deftest fill-paragraph-single-line-style-code-before-and-after () + (rust-test-fill-paragraph + "fn foo() { } +/// This is my comment. This is more of my comment. This is even more. +fn bar() { }" + "fn foo() { } +/// This is my comment. This is +/// more of my comment. This is +/// even more. +fn bar() { }" 14 85)) + +(defun test-auto-fill (initial position inserted expected) + (rust-test-manip-code + initial + position + (lambda () + (unwind-protect + (progn + (let ((fill-column rust-test-fill-column)) + (auto-fill-mode) + (goto-char position) + (insert inserted) + (syntax-ppss-flush-cache 1) + (funcall auto-fill-function))) + (auto-fill-mode t))) + expected)) + +(ert-deftest auto-fill-multi-line-doc-comment () + (test-auto-fill + "/** + * + */" + 7 + " This is a very very very very very very very long string" + "/** + * This is a very very very very + * very very very long string + */")) + +(ert-deftest auto-fill-single-line-doc-comment () + (test-auto-fill + "/// This is the first really +/// really really really really +/// really really long paragraph +/// +/// " + 103 + "This is the second really really really really really really long paragraph" + "/// This is the first really +/// really really really really +/// really really long paragraph +/// +/// This is the second really +/// really really really really +/// really long paragraph" + )) + +(ert-deftest auto-fill-multi-line-prefixless () + (test-auto-fill + "/* + + */" + 4 + "This is a very very very very very very very long string" + "/* +This is a very very very very +very very very long string + */" + )) + +(defun test-indent (indented &optional deindented) + (let ((deindented (or deindented (replace-regexp-in-string "^[[:blank:]]*" " " indented)))) + (rust-test-manip-code + deindented + 1 + (lambda () + (rust-test-silence + '("%s %s" ; "Indenting..." progress-reporter-do-update + "%sdone") ; "Indenting...done" progress-reporter-done + (indent-region 1 (+ 1 (buffer-size))))) + indented))) + + +(ert-deftest indent-struct-fields-aligned () + (test-indent + " +struct Foo { bar: i32, + baz: i32 } + +struct Blah {x:i32, + y:i32, + z:String")) + +(ert-deftest indent-doc-comments () + (test-indent + " +/** + * This is a doc comment + * + */ + +/// So is this + +fn foo() { + /*! + * this is a nested doc comment + */ + \n //! And so is this +}")) + +(ert-deftest indent-inside-braces () + (test-indent + " +// struct fields out one level: +struct foo { + a:i32, + // comments too + b:char +} + +fn bar(x:Box) { // comment here should not affect the next indent + bla(); + bla(); +}")) + +(ert-deftest indent-top-level () + (test-indent + " +// Everything here is at the top level and should not be indented +#[attrib] +mod foo; + +pub static bar = Quux{a: b()} + +use foo::bar::baz; + +fn foo() { } +")) + +(ert-deftest font-lock-multi-raw-strings-in-a-row () + (rust-test-font-lock + " +r\"foo\\\", \"bar\", r\"bar\"; +r\"foo\\.\", \"bar\", r\"bar\"; +r\"foo\\..\", \"bar\", r\"foo\\..\\bar\"; +r\"\\\", \"foo\", r\"\\foo\"; +not_a_string(); + +" + + (apply #'append (mapcar (lambda (s) (list s 'font-lock-string-face)) + '("r\"foo\\\"" "\"bar\"" "r\"bar\"" + "r\"foo\\.\"" "\"bar\"" "r\"bar\"" + "r\"foo\\..\"" "\"bar\"" "r\"foo\\..\\bar\"" + "r\"\\\"" "\"foo\"" "r\"\\foo\""))) + )) + +(ert-deftest font-lock-raw-string-after-normal-string-ending-in-r () + (rust-test-font-lock + "\"bar\" r\"foo\"" + '("\"bar\"" font-lock-string-face "r\"foo\"" font-lock-string-face))) + +(ert-deftest indent-params-no-align () + (test-indent + " +// Indent out one level because no params appear on the first line +fn xyzzy( + a:i32, + b:char) { } + +fn abcdef( + a:i32, + b:char) + -> char +{ }")) + +(ert-deftest indent-params-align () + (test-indent + " +// Align the second line of params to the first +fn foo(a:i32, + b:char) { } + +fn bar( a:i32, + b:char) + -> i32 +{ } + +fn baz( a:i32, // should work with a comment here + b:char) + -> i32 +{ } +")) + +(ert-deftest indent-open-after-arrow () + (test-indent + " +// Indent function body only one level after `-> {` +fn foo1(a:i32, b:char) -> i32 { + let body; +} + +fn foo2(a:i32, + b:char) -> i32 { + let body; +} + +fn foo3(a:i32, + b:char) + -> i32 { + let body; +} + +fn foo4(a:i32, + b:char) + -> i32 where i32:A { + let body; +} +")) + +(ert-deftest indent-return-type-non-visual () + (let ((rust-indent-return-type-to-arguments nil)) +(test-indent + " +fn imagine_long_enough_to_wrap_at_arrow(a:i32, b:char) + -> i32 +{ + let body; +} +"))) + +(ert-deftest indent-body-after-where () + (let ((rust-indent-where-clause t)) + (test-indent + " +fn foo1(a: A, b: B) -> A + where A: Clone + Default, B: Eq { + let body; + Foo { + bar: 3 + } +} + +fn foo2(a: A, b: B) -> A + where A: Clone + Default, B: Eq +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style1a () + (let ((rust-indent-where-clause t)) + (test-indent + " +fn foo1a(a: A, b: B, c: C) -> D + where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style1b () + (let ((rust-indent-where-clause t)) + (test-indent + " +fn foo1b(a: A, b: B, c: C) -> D + where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style2a () + (test-indent + " +fn foo2a(a: A, b: B, c: C) -> D where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style2b () + (test-indent + " +fn foo2b(a: A, b: B, c: C) -> D where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style3a () + (test-indent + " +fn foo3a(a: A, b: B, c: C) -> D where + A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style3b () + (test-indent + " +fn foo3b(a: A, b: B, c: C) -> D where + A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +")) + +(ert-deftest indent-align-where-clauses-style4a () + (let ((rust-indent-where-clause nil)) + (test-indent + " +fn foo4a(a: A, b: B, c: C) -> D +where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq { + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-style4b () + (let ((rust-indent-where-clause nil)) + (test-indent + " +fn foo4b(a: A, b: B, c: C) -> D +where A: Clone + Default, + B: Eq, + C: PartialEq, + D: PartialEq +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-impl-example () + (let ((rust-indent-where-clause t)) + (test-indent + " +impl<'a, K, Q: ?Sized, V, S> Index<&'a Q> for HashMap + where K: Eq + Hash + Borrow, + Q: Eq + Hash, + S: HashState, +{ + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-clauses-first-line () + (let ((rust-indent-where-clause t)) + (test-indent + "fn foo1(a: A, b: B) -> A + where A: Clone + Default, B: Eq { + let body; + Foo { + bar: 3 + } +} +"))) + +(ert-deftest indent-align-where-in-comment1 () + (test-indent + "/// - there must not exist an edge U->V in the graph where: +#[derive(Clone, PartialEq, Eq)] +pub struct Region { // <-- this should be flush with left margin! + entry: BasicBlockIndex, + leaves: BTreeMap, +} +")) + +(ert-deftest indent-align-where-in-comment2 () + (let ((rust-indent-where-clause t)) + (test-indent + "fn foo(f:F, g:G) + where F:Send, +// where + G:Sized +{ + let body; +} +"))) + +(ert-deftest indent-align-where-in-comment3 () + (let ((rust-indent-where-clause t)) + (test-indent + "fn foo(f:F, g:G) + where F:Send, +// where F:ThisIsNotActualCode, + G:Sized +{ + let body; +} +"))) + +(ert-deftest indent-square-bracket-alignment () + (test-indent + " +fn args_on_the_next_line( // with a comment + a:i32, + b:String) { + let aaaaaa = [ + 1, + 2, + 3]; + let bbbbbbb = [1, 2, 3, + 4, 5, 6]; + let ccc = [ 10, 9, 8, + 7, 6, 5]; +} +")) + +(ert-deftest indent-closing-square-bracket () + (test-indent + "fn blergh() { + let list = vec![ + 1, + 2, + 3, + ]; +}")) + +(ert-deftest indent-closing-paren () + (test-indent + "fn blergh() { + call( + a, + function + ); +}")) + +(ert-deftest indent-nested-fns () + (test-indent + " +fn nexted_fns(a: fn(b:i32, + c:char) + -> i32, + d: i32) + -> u128 +{ + 0 +} +" + )) + +(ert-deftest indent-multi-line-expr () + (test-indent + " +fn foo() +{ + x(); + let a = + b(); +} +" + )) + +(ert-deftest indent-match () + (test-indent + " +fn foo() { + match blah { + Pattern => stuff(), + _ => whatever + } +} +" + )) + +(ert-deftest indent-match-multiline-pattern () + (test-indent + " +fn foo() { + match blah { + Pattern | + Pattern2 => { + hello() + }, + _ => whatever + } +} +" + )) + +(ert-deftest indent-indented-match () + (test-indent + " +fn foo() { + let x = + match blah { + Pattern | + Pattern2 => { + hello() + }, + _ => whatever + }; + y(); +} +" + )) + +(ert-deftest indent-curly-braces-within-parens () + (test-indent + " +fn foo() { + let x = + foo(bar(|x| { + only_one_indent_here(); + })); + y(); +} +" + )) + +(ert-deftest indent-weirdly-indented-block () + (rust-test-manip-code + " +fn foo() { + { +this_block_is_over_to_the_left_for_some_reason(); + } + +} +" + 16 + #'indent-for-tab-command + " +fn foo() { + { + this_block_is_over_to_the_left_for_some_reason(); + } + +} +" + )) + +(ert-deftest indent-multi-line-attrib () + (test-indent + " +#[attrib( + this, + that, + theotherthing)] +fn function_with_multiline_attribute() {} +" + )) + + +;; Make sure that in effort to cover match patterns we don't mistreat || or expressions +(ert-deftest indent-nonmatch-or-expression () + (test-indent + " +fn foo() { + let x = foo() || + bar(); +} +" + )) + +;; Closing braces in single char literals and strings should not confuse the indentation +(ert-deftest indent-closing-braces-in-char-literals () + (test-indent + " +fn foo() { + { bar('}'); } + { bar(']'); } + { bar(')'); } +} +" + )) + +;; This is a test for #103: a comment after the last struct member that does +;; not have a trailing comma. The comment used to be indented one stop too +;; far. +(ert-deftest indent-comment-after-last-struct-member () + (test-indent + " +struct A { + x: u8 + // comment +} + +struct A { + x: u8 + /* comment */ +} +" + )) + +(defconst rust-test-motion-string + " +fn fn1(arg: i32) -> bool { + let x = 5; + let y = b(); + true +} + +fn fn2(arg: i32) -> bool { + let x = 5; + let y = b(); + true +} + +pub fn fn3(arg: i32) -> bool { + let x = 5; + let y = b(); + true +} + +struct Foo { + x: i32 +} +") +(defconst rust-test-region-string rust-test-motion-string) +(defconst rust-test-indent-motion-string + " +fn blank_line(arg:i32) -> bool { + +} + +fn indenting_closing_brace() { + if(true) { +} +} + +fn indenting_middle_of_line() { + if(true) { + push_me_out(); +} else { + pull_me_back_in(); +} +} + +fn indented_already() { + + // The previous line already has its spaces +} +") + +;; Symbol -> (line column) +(defconst rust-test-positions-alist '((start-of-fn1 (2 0)) + (start-of-fn1-middle-of-line (2 15)) + (middle-of-fn1 (3 7)) + (end-of-fn1 (6 0)) + (between-fn1-fn2 (7 0)) + (start-of-fn2 (8 0)) + (middle-of-fn2 (10 4)) + (before-start-of-fn1 (1 0)) + (after-end-of-fn2 (13 0)) + (beginning-of-fn3 (14 0)) + (middle-of-fn3 (16 4)) + (middle-of-struct (21 10)) + (before-start-of-struct (19 0)) + (after-end-of-struct (23 0)) + (blank-line-indent-start (3 0)) + (blank-line-indent-target (3 4)) + (closing-brace-indent-start (8 1)) + (closing-brace-indent-target (8 5)) + (middle-push-indent-start (13 2)) + (middle-push-indent-target (13 9)) + (after-whitespace-indent-start (13 1)) + (after-whitespace-indent-target (13 8)) + (middle-pull-indent-start (15 19)) + (middle-pull-indent-target (15 12)) + (blank-line-indented-already-bol-start (20 0)) + (blank-line-indented-already-bol-target (20 4)) + (blank-line-indented-already-middle-start (20 2)) + (blank-line-indented-already-middle-target (20 4)) + (nonblank-line-indented-already-bol-start (21 0)) + (nonblank-line-indented-already-bol-target (21 4)) + (nonblank-line-indented-already-middle-start (21 2)) + (nonblank-line-indented-already-middle-target (21 4)))) + +(defun rust-get-buffer-pos (pos-symbol) + "Get buffer position from POS-SYMBOL. + +POS-SYMBOL is a symbol found in `rust-test-positions-alist'. +Convert the line-column information from that list into a buffer position value." + (interactive "P") + (let* ( + (line-and-column (cadr (assoc pos-symbol rust-test-positions-alist))) + (line (nth 0 line-and-column)) + (column (nth 1 line-and-column))) + (save-excursion + (goto-char (point-min)) + (forward-line (1- line)) + (move-to-column column) + (point)))) + +;;; FIXME: Maybe add an ERT explainer function (something that shows the +;;; surrounding code of the final point, not just the position). +(defun rust-test-motion (source-code init-pos final-pos manip-func &rest args) + "Test that MANIP-FUNC moves point from INIT-POS to FINAL-POS. + +If ARGS are provided, send them to MANIP-FUNC. + +INIT-POS, FINAL-POS are position symbols found in `rust-test-positions-alist'." + (with-temp-buffer + (rust-mode) + (insert source-code) + (goto-char (rust-get-buffer-pos init-pos)) + (apply manip-func args) + (should (equal (point) (rust-get-buffer-pos final-pos))))) + +(defun rust-test-region (source-code init-pos reg-beg reg-end manip-func &rest args) + "Test that MANIP-FUNC marks region from REG-BEG to REG-END. + +INIT-POS is the initial position of point. +If ARGS are provided, send them to MANIP-FUNC. +All positions are position symbols found in `rust-test-positions-alist'." + (with-temp-buffer + (rust-mode) + (insert source-code) + (goto-char (rust-get-buffer-pos init-pos)) + (apply manip-func args) + (should (equal (list (region-beginning) (region-end)) + (list (rust-get-buffer-pos reg-beg) + (rust-get-buffer-pos reg-end)))))) + +(ert-deftest rust-beginning-of-defun-from-middle-of-fn () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-from-end () + (rust-test-motion + rust-test-motion-string + 'end-of-fn1 + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-before-open-brace () + (rust-test-motion + rust-test-motion-string + 'start-of-fn1-middle-of-line + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-between-fns () + (rust-test-motion + rust-test-motion-string + 'between-fn1-fn2 + 'start-of-fn1 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-with-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn2 + 'start-of-fn1 + #'beginning-of-defun 2)) + +(ert-deftest rust-beginning-of-defun-with-negative-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'beginning-of-fn3 + #'beginning-of-defun -2)) + +(ert-deftest rust-beginning-of-defun-pub-fn () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn3 + 'beginning-of-fn3 + #'beginning-of-defun)) + +(ert-deftest rust-beginning-of-defun-string-comment () + (let (fn-1 fn-2 p-1 p-2) + (with-temp-buffer + (rust-mode) + (insert "fn test1() { + let s=r#\" +fn test2(); +\"#;") + (setq p-1 (point)) + (setq fn-1 (1+ p-1)) + (insert " +fn test3() { + /* +fn test4();") + (setq p-2 (point)) + (insert "\n*/\n}\n") + (setq fn-2 (point)) + (insert "fn test5() { }") + + (goto-char p-1) + (beginning-of-defun) + (should (eq (point) (point-min))) + + (beginning-of-defun -2) + (should (eq (point) fn-2)) + + (goto-char p-2) + (beginning-of-defun) + (should (eq (point) fn-1)) + + (beginning-of-defun -1) + (should (eq (point) fn-2)) + + (goto-char (point-max)) + (beginning-of-defun 2) + (should (eq (point) fn-1))))) + +(ert-deftest rust-end-of-defun-from-middle-of-fn () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'between-fn1-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-from-beg () + (rust-test-motion + rust-test-motion-string + 'start-of-fn1 + 'between-fn1-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-before-open-brace () + (rust-test-motion + rust-test-motion-string + 'start-of-fn1-middle-of-line + 'between-fn1-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-between-fns () + (rust-test-motion + rust-test-motion-string + 'between-fn1-fn2 + 'after-end-of-fn2 + #'end-of-defun)) + +(ert-deftest rust-end-of-defun-with-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn1 + 'after-end-of-fn2 + #'end-of-defun 2)) + +(ert-deftest rust-end-of-defun-with-negative-arg () + (rust-test-motion + rust-test-motion-string + 'middle-of-fn3 + 'between-fn1-fn2 + #'end-of-defun -2)) + +(ert-deftest rust-mark-defun-from-middle-of-fn () + (rust-test-region + rust-test-region-string + 'middle-of-fn2 + 'between-fn1-fn2 'after-end-of-fn2 + #'mark-defun)) + +(ert-deftest rust-mark-defun-from-end () + (rust-test-region + rust-test-region-string + 'end-of-fn1 + 'before-start-of-fn1 'between-fn1-fn2 + #'mark-defun)) + +(ert-deftest rust-mark-defun-start-of-defun () + (rust-test-region + rust-test-region-string + 'start-of-fn2 + 'between-fn1-fn2 'after-end-of-fn2 + #'mark-defun)) + +(ert-deftest rust-mark-defun-from-middle-of-struct () + (rust-test-region + rust-test-region-string + 'middle-of-struct + 'before-start-of-struct 'after-end-of-struct + #'mark-defun)) + +(ert-deftest indent-line-blank-line-motion () + (rust-test-motion + rust-test-indent-motion-string + 'blank-line-indent-start + 'blank-line-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-closing-brace-motion () + (rust-test-motion + rust-test-indent-motion-string + 'closing-brace-indent-start + 'closing-brace-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-middle-push-motion () + (rust-test-motion + rust-test-indent-motion-string + 'middle-push-indent-start + 'middle-push-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-after-whitespace-motion () + (rust-test-motion + rust-test-indent-motion-string + 'after-whitespace-indent-start + 'after-whitespace-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-middle-pull-motion () + (rust-test-motion + rust-test-indent-motion-string + 'middle-pull-indent-start + 'middle-pull-indent-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-blank-line-indented-already-bol () + (rust-test-motion + rust-test-indent-motion-string + 'blank-line-indented-already-bol-start + 'blank-line-indented-already-bol-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-blank-line-indented-already-middle () + (rust-test-motion + rust-test-indent-motion-string + 'blank-line-indented-already-middle-start + 'blank-line-indented-already-middle-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-nonblank-line-indented-already-bol () + (rust-test-motion + rust-test-indent-motion-string + 'nonblank-line-indented-already-bol-start + 'nonblank-line-indented-already-bol-target + #'indent-for-tab-command)) + +(ert-deftest indent-line-nonblank-line-indented-already-middle () + (rust-test-motion + rust-test-indent-motion-string + 'nonblank-line-indented-already-middle-start + 'nonblank-line-indented-already-middle-target + #'indent-for-tab-command)) + +(ert-deftest no-stack-overflow-in-rust-rewind-irrelevant () + (with-temp-buffer + (rust-mode) + (insert "fn main() {\n let x = 1;") + ;; Insert 150 separate comments on the same line + (dotimes (_i 150) + (insert "/* foo */ ")) + ;; Rewinding from the last comment to the end of the let needs at least + ;; 150 iterations, but if we limit the stack depth to 100 (this appears to + ;; be some minimum), a recursive function would overflow, throwing an + ;; error. + (let ((max-lisp-eval-depth 100)) + (rust-rewind-irrelevant) + ;; Only a non-stack overflowing function would make it this far. Also + ;; check that we rewound till after the ; + (should (= (char-before) ?\;))))) + +(defun rust-test-fontify-string (str) + (with-temp-buffer + (rust-mode) + (insert str) + (font-lock-flush) + (font-lock-ensure) + (buffer-string))) + +(defun rust-test-group-str-by-face (str) + "Fontify `STR' in rust-mode and group it by face, returning a +list of substrings of `STR' each followed by its face." + (cl-loop with fontified = (rust-test-fontify-string str) + for start = 0 then end + while start + for end = (next-single-property-change start 'face fontified) + for prop = (get-text-property start 'face fontified) + for text = (substring-no-properties fontified start end) + if prop + append (list text prop))) + +(defun rust-test-font-lock (source face-groups) + "Test that `SOURCE' fontifies to the expected `FACE-GROUPS'" + (should (equal (rust-test-group-str-by-face source) + face-groups))) + +(ert-deftest font-lock-attribute-simple () + (rust-test-font-lock + "#[foo]" + '("#[foo]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-inner () + (rust-test-font-lock + "#![foo]" + '("#![foo]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-key-value () + (rust-test-font-lock + "#[foo = \"bar\"]" + '("#[foo = " font-lock-preprocessor-face + "\"bar\"" font-lock-string-face + "]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-around-comment () + (rust-test-font-lock + "#[foo /* bar */]" + '("#[foo " font-lock-preprocessor-face + "/* " font-lock-comment-delimiter-face + "bar */" font-lock-comment-face + "]" font-lock-preprocessor-face))) + +(ert-deftest font-lock-attribute-inside-string () + (rust-test-font-lock + "\"#[foo]\"" + '("\"#[foo]\"" font-lock-string-face))) + +(ert-deftest font-lock-attribute-inside-comment () + (rust-test-font-lock + "/* #[foo] */" + '("/* " font-lock-comment-delimiter-face + "#[foo] */" font-lock-comment-face))) + +(ert-deftest font-lock-number-with-type () + (rust-test-font-lock + "-123i32" + '("i32" font-lock-type-face)) + (rust-test-font-lock + "123u32" + '("u32" font-lock-type-face)) + (rust-test-font-lock + "123_123_u32" + '("u32" font-lock-type-face)) + (rust-test-font-lock + "0xff_u8" + '("u8" font-lock-type-face)) + (rust-test-font-lock + "0b1111_1111_1001_0000i64" + '("i64" font-lock-type-face)) + (rust-test-font-lock + "0usize" + '("usize" font-lock-type-face)) + (rust-test-font-lock + "123.0f64 + 1." + '("f64" font-lock-type-face)) + (rust-test-font-lock + "0.1f32" + '("f32" font-lock-type-face)) + (rust-test-font-lock + "12E+99_f64" + '("f64" font-lock-type-face)) + (rust-test-font-lock + "5f32" + '("f32" font-lock-type-face)) + (rust-test-font-lock + "0x5i32" + '("i32" font-lock-type-face)) + (rust-test-font-lock + "1x5i32" + '()) + (rust-test-font-lock + "0x5i321" + '()) + (rust-test-font-lock + "fname5f32" + '()) + (rust-test-font-lock + "0x5i32+1" + '("i32" font-lock-type-face)) + (rust-test-font-lock + "f(0xFFi32)" + '("i32" font-lock-type-face))) + +(ert-deftest font-lock-double-quote-character-literal () + (rust-test-font-lock + "'\"'; let" + '("'\"'" font-lock-string-face + "let" font-lock-keyword-face))) + +(ert-deftest font-lock-fn-contains-capital () + (rust-test-font-lock + "fn foo_Bar() {}" + '("fn" font-lock-keyword-face + "foo_Bar" font-lock-function-name-face))) + +(ert-deftest font-lock-let-bindings () + (rust-test-font-lock + "let foo;" + '("let" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let ref foo;" + '("let" font-lock-keyword-face + "ref" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let mut foo;" + '("let" font-lock-keyword-face + "mut" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let foo = 1;" + '("let" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "let mut foo = 1;" + '("let" font-lock-keyword-face + "mut" font-lock-keyword-face + "foo" font-lock-variable-name-face)) + (rust-test-font-lock + "fn foo() { let bar = 1; }" + '("fn" font-lock-keyword-face + "foo" font-lock-function-name-face + "let" font-lock-keyword-face + "bar" font-lock-variable-name-face)) + (rust-test-font-lock + "fn foo() { let mut bar = 1; }" + '("fn" font-lock-keyword-face + "foo" font-lock-function-name-face + "let" font-lock-keyword-face + "mut" font-lock-keyword-face + "bar" font-lock-variable-name-face))) + +(ert-deftest font-lock-ampersand () + (rust-test-font-lock + "f(&a)" + '("&" rust-ampersand-face)) + (rust-test-font-lock + "a && b &&& c" + nil) + (rust-test-font-lock + "&'a v" + '("&" rust-ampersand-face + "a" font-lock-variable-name-face)) + (rust-test-font-lock + "&'static v" + '("&" rust-ampersand-face + "static" font-lock-keyword-face)) + (rust-test-font-lock + "&mut v" + '("&" rust-ampersand-face + "mut" font-lock-keyword-face)) + (rust-test-font-lock + "&f(&x)" + '("&" rust-ampersand-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "fn f(x: &X)" + '("fn" font-lock-keyword-face + "f" font-lock-function-name-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "X" font-lock-type-face)) + (rust-test-font-lock + "f(&X{x})" + '("&" rust-ampersand-face + "X" font-lock-type-face)) + (rust-test-font-lock + "let x: &'_ f64 = &1.;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "_" font-lock-variable-name-face + "f64" font-lock-type-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &&1;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &*y;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &::std::f64::consts::PI;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "std" font-lock-constant-face + "f64" font-lock-type-face + "consts" font-lock-constant-face + "PI" font-lock-type-face)) + (rust-test-font-lock + "let x = &(1, 2);" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x = &{1};" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let f = &|x| {x + 1};" + '("let" font-lock-keyword-face + "f" font-lock-variable-name-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "let x: &_ = &1;" + '("let" font-lock-keyword-face + "x" font-lock-variable-name-face + "&" rust-ampersand-face + "&" rust-ampersand-face)) + (rust-test-font-lock + "&[1,2]" + '("&" rust-ampersand-face))) + +(ert-deftest font-lock-if-let-binding () + (rust-test-font-lock + "if let Some(var) = some_var { /* no-op */ }" + '("if" font-lock-keyword-face + "let" font-lock-keyword-face + "Some" font-lock-type-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face))) + +(ert-deftest font-lock-single-quote-character-literal () + (rust-test-font-lock + "fn main() { let ch = '\\''; }" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\''" font-lock-string-face))) + +(ert-deftest font-lock-escaped-double-quote-character-literal () + (rust-test-font-lock + "fn main() { let ch = '\\\"'; }" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\\"'" font-lock-string-face))) + +(ert-deftest font-lock-escaped-backslash-character-literal () + (rust-test-font-lock + "fn main() { let ch = '\\\\'; }" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\\\'" font-lock-string-face))) + +(ert-deftest font-lock-hex-escape-character-literal () + (rust-test-font-lock + "let ch = '\\x1f';" + '("let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\x1f'" font-lock-string-face))) + +(ert-deftest font-lock-unicode-escape-character-literal () + (rust-test-font-lock + "let ch = '\\u{1ffff}';" + '("let" font-lock-keyword-face + "ch" font-lock-variable-name-face + "'\\u{1ffff}'" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-no-hashes () + (rust-test-font-lock + "r\"No hashes\";" + '("r\"No hashes\"" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-double-quote () + (rust-test-font-lock + "fn main() { + r#\"With a double quote (\")\"#; +} +" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "r#\"With a double quote (\")\"#" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-two-hashes () + (rust-test-font-lock + "r##\"With two hashes\"##;" + '("r##\"With two hashes\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-strings-backslash-at-end () + (rust-test-font-lock + "r\"With a backslash at the end\\\";" + '("r\"With a backslash at the end\\\"" font-lock-string-face))) + +(ert-deftest font-lock-two-raw-strings () + (rust-test-font-lock + "fn main() { + r\"With a backslash at the end\\\"; + r##\"With two hashes\"##; +}" + '("fn" font-lock-keyword-face + "main" font-lock-function-name-face + "r\"With a backslash at the end\\\"" font-lock-string-face + "r##\"With two hashes\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-with-inner-hash () + (rust-test-font-lock + "r##\"I've got an octothorpe (#)\"##; foo()" + '("r##\"I've got an octothorpe (#)\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-with-inner-quote-and-hash () + (rust-test-font-lock + "not_the_string(); r##\"string \"# still same string\"##; not_the_string()" + '("r##\"string \"# still same string\"##" font-lock-string-face))) + +(ert-deftest font-lock-string-ending-with-r-not-raw-string () + (rust-test-font-lock + "fn f() { + \"Er\"; +} + +fn g() { + \"xs\"; +}" + '("fn" font-lock-keyword-face + "f" font-lock-function-name-face + "\"Er\"" font-lock-string-face + "fn" font-lock-keyword-face + "g" font-lock-function-name-face + "\"xs\"" font-lock-string-face))) + +(ert-deftest font-lock-string-ending-with-r-word-boundary () + (with-temp-buffer + (rust-mode) + (insert "const foo = \"foo bar\"") + (font-lock-flush) + (font-lock-ensure) + ;; right-word should move the point to the end of the words. + (goto-char 14) + (right-word) + (should (equal 17 (point))) + (right-word) + (should (equal 21 (point))) + )) + +(ert-deftest font-lock-raw-string-trick-ending-followed-by-string-with-quote () + (rust-test-font-lock + "r\"With what looks like the start of a raw string at the end r#\"; +not_a_string(); +r##\"With \"embedded\" quote \"##;" + '("r\"With what looks like the start of a raw string at the end r#\"" font-lock-string-face + "r##\"With \"embedded\" quote \"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-starter-inside-raw-string () + ;; Check that it won't look for a raw string beginning inside another raw string. + (rust-test-font-lock + "r#\"In the first string r\" in the first string \"#; +not_in_a_string(); +r##\"In the second string\"##;" + '("r#\"In the first string r\" in the first string \"#" font-lock-string-face + "r##\"In the second string\"##" font-lock-string-face))) + +(ert-deftest font-lock-raw-string-starter-inside-comment () + ;; Check that it won't look for a raw string beginning inside another raw string. + (rust-test-font-lock + "// r\" this is a comment +\"this is a string\"; +this_is_not_a_string();)" + '("// " font-lock-comment-delimiter-face + "r\" this is a comment\n" font-lock-comment-face + "\"this is a string\"" font-lock-string-face))) + +(ert-deftest font-lock-runaway-raw-string () + (rust-test-font-lock + "const Z = r#\"my raw string\";\n// oops this is still in the string" + '("const" font-lock-keyword-face + "Z" font-lock-type-face + "r#\"my raw string\";\n// oops this is still in the string" font-lock-string-face)) + ) + +(ert-deftest font-lock-recognize-closing-raw-string () + (with-temp-buffer + (rust-mode) + (insert "const foo = r##\" +1...............................................50 +1...............................................50 +1...............................................50 +1...............195-->\"; let ...................50 +1...............................................50 +1...............................................50 +1...............................................50 +1...............................................50 +1...............................................50 +1......................500......................50 +\"#; +") + (font-lock-flush) + (font-lock-ensure) + (goto-char 530) + (insert "#") + ;; We have now closed the raw string. Check that the whole string is + ;; recognized after the change + (font-lock-after-change-function (1- (point)) (point) 0) + (should (equal 'font-lock-string-face (get-text-property 195 'face))) ;; The "let" + (should (equal 'font-lock-string-face (get-text-property 500 'face))) ;; The "500" + (should (equal nil (get-text-property 531 'face))) ;; The second ";" + )) + +;;; Documentation comments + +(ert-deftest font-lock-doc-line-comment-parent () + (rust-test-font-lock + "//! doc" + '("//! doc" font-lock-doc-face))) + +(ert-deftest font-lock-doc-line-comment-item () + (rust-test-font-lock + "/// doc" + '("/// doc" font-lock-doc-face))) + +(ert-deftest font-lock-nondoc-line () + (rust-test-font-lock + "////// doc" + '("////// " font-lock-comment-delimiter-face + "doc" font-lock-comment-face))) + +(ert-deftest font-lock-doc-line-in-string () + (rust-test-font-lock + "\"/// doc\"" + '("\"/// doc\"" font-lock-string-face)) + + (rust-test-font-lock + "\"//! doc\"" + '("\"//! doc\"" font-lock-string-face))) + +(ert-deftest font-lock-doc-line-in-nested-comment () + (rust-test-font-lock + "/* /// doc */" + '("/* " font-lock-comment-delimiter-face + "/// doc */" font-lock-comment-face)) + + (rust-test-font-lock + "/* //! doc */" + '("/* " font-lock-comment-delimiter-face + "//! doc */" font-lock-comment-face))) + + +(ert-deftest font-lock-doc-block-comment-parent () + (rust-test-font-lock + "/*! doc */" + '("/*! doc */" font-lock-doc-face))) + +(ert-deftest font-lock-doc-block-comment-item () + (rust-test-font-lock + "/** doc */" + '("/** doc */" font-lock-doc-face))) + +(ert-deftest font-lock-nondoc-block-comment-item () + (rust-test-font-lock + "/***** doc */" + '("/**" font-lock-comment-delimiter-face + "*** doc */" font-lock-comment-face))) + +(ert-deftest font-lock-doc-block-in-string () + (rust-test-font-lock + "\"/** doc */\"" + '("\"/** doc */\"" font-lock-string-face)) + (rust-test-font-lock + "\"/*! doc */\"" + '("\"/*! doc */\"" font-lock-string-face))) + +(ert-deftest font-lock-module-def () + (rust-test-font-lock + "mod foo;" + '("mod" font-lock-keyword-face + "foo" font-lock-constant-face))) + +(ert-deftest font-lock-module-use () + (rust-test-font-lock + "use foo;" + '("use" font-lock-keyword-face + "foo" font-lock-constant-face))) + +(ert-deftest font-lock-module-path () + (rust-test-font-lock + "foo::bar" + '("foo" font-lock-constant-face))) + +(ert-deftest font-lock-submodule-path () + (rust-test-font-lock + "foo::bar::baz" + '("foo" font-lock-constant-face + "bar" font-lock-constant-face))) + +(ert-deftest font-lock-type () + (rust-test-font-lock + "foo::Bar::baz" + '("foo" font-lock-constant-face + "Bar" font-lock-type-face))) + +(ert-deftest font-lock-type-annotation () + "Ensure type annotations are not confused with modules." + (rust-test-font-lock + "parse::();" + ;; Only the i32 should have been highlighted. + '("i32" font-lock-type-face)) + (rust-test-font-lock + "foo:: " + ;; Only the i32 should have been highlighted. + '("i32" font-lock-type-face))) + +(ert-deftest font-lock-question-mark () + "Ensure question mark operator is highlighted." + (rust-test-font-lock + "?" + '("?" rust-question-mark)) + (rust-test-font-lock + "foo\(\)?;" + '("?" rust-question-mark)) + (rust-test-font-lock + "foo\(bar\(\)?\);" + '("?" rust-question-mark)) + (rust-test-font-lock + "\"?\"" + '("\"?\"" font-lock-string-face)) + (rust-test-font-lock + "foo\(\"?\"\);" + '("\"?\"" font-lock-string-face)) + (rust-test-font-lock + "// ?" + '("// " font-lock-comment-delimiter-face + "?" font-lock-comment-face)) + (rust-test-font-lock + "/// ?" + '("/// ?" font-lock-doc-face)) + (rust-test-font-lock + "foo\(\"?\"\);" + '("\"?\"" font-lock-string-face)) + (rust-test-font-lock + "foo\(\"?\"\)?;" + '("\"?\"" font-lock-string-face + "?" rust-question-mark))) + +(ert-deftest rust-test-default-context-sensitive () + (rust-test-font-lock + "let default = 7; impl foo { default fn f() { } }" + '("let" font-lock-keyword-face + "default" font-lock-variable-name-face + "impl" font-lock-keyword-face + "default" font-lock-keyword-face + "fn" font-lock-keyword-face + "f" font-lock-function-name-face))) + +(ert-deftest rust-test-union-context-sensitive () + (rust-test-font-lock + "let union = 7; union foo { x: &'union bar }" + '("let" font-lock-keyword-face + ;; The first union is a variable name. + "union" font-lock-variable-name-face + ;; The second union is a contextual keyword. + "union" font-lock-keyword-face + "foo" font-lock-type-face + "x" font-lock-variable-name-face + ;; This union is the name of a lifetime. + "&" rust-ampersand-face + "union" font-lock-variable-name-face + "bar" font-lock-type-face))) + +(ert-deftest indent-method-chains-no-align () + (let ((rust-indent-method-chain nil)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-no-align-with-question-mark-operator () + (let ((rust-indent-method-chain nil)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment()? + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-with-align () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-with-align-with-question-mark-operator () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment()? + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-with-align-and-second-stmt () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it() + .aligned() + .more_alignment(); + foo.bar(); +} +" + ))) + +(ert-deftest indent-method-chains-field () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.do_it + .aligned + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-double-field-on-first-line () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + let x = thing.a.do_it + .aligned + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-no-let () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + thing.a.do_it + .aligned + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-look-over-comment () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + thing.a.do_it + // A comment + .aligned + // Another comment + .more_alignment(); +} +" + ))) + +(ert-deftest indent-method-chains-comment () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + // thing.do_it() + // .aligned() +} +" + ))) + +(ert-deftest indent-method-chains-close-block () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + foo.bar() +} +" + ))) + +(ert-deftest indent-method-chains-after-comment () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { // comment here should not push next line out + foo.bar() +} +" + ))) + +(ert-deftest indent-method-chains-after-comment2 () + (let ((rust-indent-method-chain t)) (test-indent + " +fn main() { + // Lorem ipsum lorem ipsum lorem ipsum lorem.ipsum + foo.bar() +} +" + ))) + +(ert-deftest indent-function-after-where () + (let ((rust-indent-method-chain t)) (test-indent + " +fn each_split_within<'a, F>(ss: &'a str, lim: usize, mut it: F) + -> bool where F: FnMut(&'a str) -> bool { +} + +#[test] +fn test_split_within() { +} +" + ))) + +(ert-deftest indent-function-after-where-nested () + (let ((rust-indent-method-chain t)) (test-indent + " +fn outer() { + fn each_split_within<'a, F>(ss: &'a str, lim: usize, mut it: F) + -> bool where F: FnMut(&'a str) -> bool { + } + #[test] + fn test_split_within() { + } + fn bar() { + } +} +" + ))) + +(ert-deftest test-for-issue-36-syntax-corrupted-state () + "This is a test for a issue #36, which involved emacs's +internal state getting corrupted when actions were done in a +specific sequence. The test seems arbitrary, and is, but it was +not clear how to narrow it down further. + +The cause of the bug was code that used to set +`syntax-begin-function' to `beginning-of-defun', which doesn't +actually fulfill the expectations--`syntax-begin-function' is +supposed to back out of all parens, but `beginning-of-defun' +could leave it inside parens if a fn appears inside them. + +Having said that, as I write this I don't understand fully what +internal state was corrupted and how. There wasn't an obvious +pattern to what did and did not trip it." + + ;; When bug #36 was present, the following test would pass, but running it + ;; caused some unknown emacs state to be corrupted such that the following + ;; test failed. Both the "blank_line" and "indented_closing_brace" functions + ;; were needed to expose the error, for instance--deleting either of them + ;; would make the failure go away. + (with-temp-buffer + (rust-mode) + (insert "fn blank_line(arg:i32) -> bool { + +} + +fn indenting_closing_brace() { + if(true) { +} +} + +fn indented_already() { + \n // The previous line already has its spaces +} +") + (font-lock-flush) + (font-lock-ensure) + (goto-char (point-min)) + (forward-line 10) + (move-to-column 0) + (indent-for-tab-command) + (should (equal (current-column) 4)) + ) + + ;; This is the test that would fail only after running the previous one. The + ;; code is extracted from src/libstd/collections/table.rs in the rust tree. + ;; It was not clear how to reduce it further--removing various bits of it + ;; would make it no longer fail. In particular, changing only the comment at + ;; the top of the "next" function was sufficient to make it no longer fail. + (test-indent + " +impl Foo for Bar { + + /// Modifies the bucket pointer in place to make it point to the next slot. + pub fn next(&mut self) { + // Branchless bucket + // As we reach the end of the table... + // We take the current idx: 0111111b + // Xor it by its increment: ^ 1000000b + // ------------ + // 1111111b + // Then AND with the capacity: & 1000000b + // ------------ + // to get the backwards offset: 1000000b + let maybe_wraparound_dist = (self.idx ^ (self.idx + 1)) & self.table.capacity(); + // Finally, we obtain the offset 1 or the offset -cap + 1. + let dist = 1 - (maybe_wraparound_dist as isize); + + self.idx += 1; + + unsafe { + self.raw = self.raw.offset(dist); + } + } + + /// Reads a bucket at a given index, returning an enum indicating whether + /// the appropriate types to call most of the other functions in + /// this module. + pub fn peek(self) { + match foo { + EMPTY_BUCKET => + Empty(EmptyBucket { + raw: self.raw, + idx: self.idx, + table: self.table + }), + _ => + Full(FullBucket { + raw: self.raw, + idx: self.idx, + table: self.table + }) + } + } +} +" + )) + +(ert-deftest test-indent-string-with-eol-backslash () + (test-indent + " +pub fn foo() { + format!(\"abc \\ + def\") +} +" + )) + +(ert-deftest test-indent-string-with-eol-backslash-at-start () + (test-indent + " +pub fn foo() { + format!(\"\\ + abc \\ + def\") +} +" + )) + +(ert-deftest test-indent-string-without-eol-backslash-indent-is-not-touched () + (test-indent + " +pub fn foo() { + format!(\" +abc +def\"); +} + +pub fn foo() { + format!(\"la la la +la +la la\"); +} +" + ;; Should still indent the code parts but leave the string internals alone: + " + pub fn foo() { + format!(\" +abc +def\"); +} + +pub fn foo() { + format!(\"la la la +la +la la\"); + } +" + )) + +(ert-deftest test-indent-string-eol-backslash-mixed-with-literal-eol () + (test-indent + " +fn foo() { + println!(\" +Here is the beginning of the string + and here is a line that is arbitrarily indented \\ + and a continuation of that indented line + and another arbitrary indentation + still another + yet another \\ + with a line continuing it +And another line not indented +\") +} +" + " +fn foo() { + println!(\" +Here is the beginning of the string + and here is a line that is arbitrarily indented \\ + and a continuation of that indented line + and another arbitrary indentation + still another + yet another \\ +with a line continuing it +And another line not indented +\") +} +")) + +(ert-deftest test-indent-string-eol-backslash-dont-touch-raw-strings () + (test-indent + " +pub fn foo() { + format!(r\"\ +abc\ + def\"); +} + +pub fn foo() { + format!(r\"la la la + la\ +la la\"); +} +" + ;; Should still indent the code parts but leave the string internals alone: + " + pub fn foo() { + format!(r\"\ +abc\ + def\"); +} + +pub fn foo() { + format!(r\"la la la + la\ +la la\"); +} +" + )) + +(ert-deftest indent-inside-string-first-line () + (test-indent + ;; Needs to leave 1 space before "world" + "\"hello \\\n world\"")) + +(ert-deftest indent-multi-line-type-param-list () + (test-indent + " +pub fn foo() { + hello(); +}")) + +(ert-deftest indent-open-paren-in-column0 () + ;; Just pass the same text for the "deindented" argument. This + ;; avoids the extra spaces normally inserted, which would mess up + ;; the test because string contents aren't touched by reindentation. + (let ((text " +const a: &'static str = r#\" +{}\"#; +fn main() { + let b = \"//\"; + let c = \"\"; + +} +")) + (test-indent text text))) + +(ert-deftest indent-question-mark-operator () + (test-indent "fn foo() { + if bar()? < 1 { + } + baz(); +}")) + +;; Regression test for #212. +(ert-deftest indent-left-shift () + (test-indent " +fn main() { + let a = [[0u32, 0u32]; 1]; + let i = 0; + let x = a[i][(1 < i)]; + let x = a[i][(1 << i)]; +} +")) + +(defun rust-test-matching-parens (content pairs &optional nonparen-positions) + "Assert that in rust-mode, given a buffer with the given `content', + emacs's paren matching will find all of the pairs of positions + as matching braces. The list of nonparen-positions asserts + specific positions that should NOT be considered to be + parens/braces of any kind. + + This does not assert that the `pairs' list is + comprehensive--there can be additional pairs that don't appear + in the list and the test still passes (as long as none of their + positions appear in `nonparen-positions'.)" + (with-temp-buffer + (rust-mode) + (insert content) + (font-lock-flush) + (font-lock-ensure) + (dolist (pair pairs) + (let* ((open-pos (nth 0 pair)) + (close-pos (nth 1 pair))) + (should (equal 4 (syntax-class (syntax-after open-pos)))) + (should (equal 5 (syntax-class (syntax-after close-pos)))) + (should (equal (scan-sexps open-pos 1) (+ 1 close-pos))) + (should (equal (scan-sexps (+ 1 close-pos) -1) open-pos)))) + (dolist (nonpar-pos nonparen-positions) + (let ((nonpar-syntax-class (syntax-class (syntax-after nonpar-pos)))) + (should-not (equal 4 nonpar-syntax-class)) + (should-not (equal 5 nonpar-syntax-class)))))) + +(ert-deftest rust-test-unmatched-single-quote-in-comment-paren-matching () + ;; This was a bug from the char quote handling that affected the paren + ;; matching. An unmatched quote char in a comment caused the problems. + (rust-test-matching-parens + "// If this appeared first in the file... +\"\\ +{\"; + +// And the { was not the on the first column: + { + // This then messed up the paren matching: '\\' +} + +" + '((97 150) ;; The { and } at the bottom + ))) + +(ert-deftest rust-test-two-character-quotes-in-a-row () + (with-temp-buffer + (rust-mode) + (font-lock-flush) + (font-lock-ensure) + (insert "'\\n','a', fn") + (font-lock-after-change-function 1 12 0) + + (should (equal 'font-lock-string-face (get-text-property 3 'face))) + (should (equal nil (get-text-property 5 'face))) + (should (equal 'font-lock-string-face (get-text-property 7 'face))) + (should (equal nil (get-text-property 9 'face))) + (should (equal 'font-lock-keyword-face (get-text-property 12 'face))) + ) + ) + +(ert-deftest single-quote-null-char () + (rust-test-font-lock + "'\\0' 'a' fn" + '("'\\0'" font-lock-string-face + "'a'" font-lock-string-face + "fn" font-lock-keyword-face))) + +(ert-deftest r-in-string-after-single-quoted-double-quote () + (rust-test-font-lock + "'\"';\n\"r\";\n\"oops\";" + '("'\"'" font-lock-string-face + "\"r\"" font-lock-string-face + "\"oops\"" font-lock-string-face + ))) + +(ert-deftest char-literal-after-quote-in-raw-string () + (rust-test-font-lock + "r#\"\"\"#;\n'q'" + '("r#\"\"\"#" font-lock-string-face + "'q'" font-lock-string-face))) + +(ert-deftest rust-macro-font-lock () + (rust-test-font-lock + "foo!\(\);" + '("foo!" font-lock-preprocessor-face)) + (rust-test-font-lock + "foo!{};" + '("foo!" font-lock-preprocessor-face)) + (rust-test-font-lock + "foo![];" + '("foo!" font-lock-preprocessor-face))) + +(ert-deftest rust-string-interpolation-matcher-works () + (dolist (test '(("print!\(\"\"\)" 9 11 nil) + ("print!\(\"abcd\"\)" 9 15 nil) + ("print!\(\"abcd {{}}\"\);" 9 19 nil) + ("print!\(\"abcd {{\"\);" 9 18 nil) + ("print!\(\"abcd {}\"\);" 9 18 ((14 16))) + ("print!\(\"abcd {{{}\"\);" 9 20 ((16 18))) + ("print!\(\"abcd {}{{\"\);" 9 20 ((14 16))) + ("print!\(\"abcd {} {{\"\);" 9 21 ((14 16))) + ("print!\(\"abcd {}}}\"\);" 9 20 ((14 16))) + ("print!\(\"abcd {{{}}}\"\);" 9 20 ((16 18))) + ("print!\(\"abcd {0}\"\);" 9 18 ((14 17))) + ("print!\(\"abcd {0} efgh\"\);" 9 23 ((14 17))) + ("print!\(\"{1} abcd {0} efgh\"\);" 9 27 ((9 12) (18 21))) + ("print!\(\"{{{1} abcd }} {0}}} {{efgh}}\"\);" 9 33 ((11 14) (23 26))))) + (cl-destructuring-bind (text cursor limit matches) test + (with-temp-buffer + ;; make sure we have a clean slate + (save-match-data + (set-match-data nil) + (insert text) + (goto-char cursor) + (if (null matches) + (should (equal (progn + (rust-string-interpolation-matcher limit) + (match-data)) + nil)) + (dolist (pair matches) + (rust-string-interpolation-matcher limit) + (should (equal (match-beginning 0) (car pair))) + (should (equal (match-end 0) (cadr pair)))))))))) + +(ert-deftest rust-formatting-macro-font-lock () + ;; test that the block delimiters aren't highlighted and the comment + ;; is ignored + (rust-test-font-lock + "print!(\"\"); { /* print!(\"\"); */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "print!(\"\"); */" font-lock-comment-face)) + ;; with newline directly following delimiter + (rust-test-font-lock + "print!(\n\"\"\n); { /* print!(\"\"); */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "print!(\"\"); */" font-lock-comment-face)) + ;; with empty println!() + (rust-test-font-lock + "println!(); { /* println!(); */ }" + '("println!" rust-builtin-formatting-macro + "/* " font-lock-comment-delimiter-face + "println!(); */" font-lock-comment-face)) + ;; other delimiters + (rust-test-font-lock + "print!{\"\"}; { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; other delimiters + (rust-test-font-lock + "print![\"\"]; { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; no interpolation + (rust-test-font-lock + "print!(\"abcd\"); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; only interpolation + (rust-test-font-lock + "print!(\"{}\"); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"" font-lock-string-face + "{}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; text + interpolation + (rust-test-font-lock + "print!(\"abcd {}\", foo); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; text + interpolation with specification + (rust-test-font-lock + "print!(\"abcd {0}\", foo); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; text + interpolation with specification and escape + (rust-test-font-lock + "print!(\"abcd {0}}}\", foo); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "}}\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; multiple pairs + (rust-test-font-lock + "print!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("print!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; println + (rust-test-font-lock + "println!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("println!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; eprint + (rust-test-font-lock + "eprint!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("eprint!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; eprintln + (rust-test-font-lock + "eprintln!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("eprintln!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; format + (rust-test-font-lock + "format!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; print + raw string + (rust-test-font-lock + "format!(r\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "r\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; print + raw string with hash + (rust-test-font-lock + "format!(r#\"abcd {0} efgh {1}\"#, foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "r#\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"#" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + ;; print + raw string with two hashes + (rust-test-font-lock + "format!(r##\"abcd {0} efgh {1}\"##, foo, bar); { /* no-op */ }" + '("format!" rust-builtin-formatting-macro + "r##\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + " efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"##" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face))) + +(ert-deftest rust-write-macro-font-lock () + (rust-test-font-lock + "write!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }" + '("write!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "}} efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + (rust-test-font-lock + "writeln!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }" + '("writeln!" rust-builtin-formatting-macro + "\"abcd " font-lock-string-face + "{0}" rust-string-interpolation + "}} efgh " font-lock-string-face + "{1}" rust-string-interpolation + "\"" font-lock-string-face + "/* " font-lock-comment-delimiter-face + "no-op */" font-lock-comment-face)) + (rust-test-font-lock + "println!(\"123\"); eprintln!(\"123\"); cprintln!(\"123\");" + '("println!" rust-builtin-formatting-macro + "\"123\"" font-lock-string-face + "eprintln!" rust-builtin-formatting-macro + "\"123\"" font-lock-string-face + "cprintln!" font-lock-preprocessor-face + "\"123\"" font-lock-string-face))) + +(ert-deftest font-lock-fontify-angle-brackets () + "Test that angle bracket fontify" + (should (equal (rust-test-fontify-string "<>") "<>")) + (should (equal (rust-test-fontify-string "") "")) + (should (equal (rust-test-fontify-string "<<>>") "<<>>")) + (should (equal (rust-test-fontify-string "<>>") "<>>")) + (should (equal (rust-test-fontify-string "<<>") "<<>"))) + +(ert-deftest rust-test-basic-paren-matching () + (rust-test-matching-parens + " +fn foo() { + let a = [1, 2, 3]; +}" + '((8 9) ;; Parens of foo() + (11 36) ;; Curly braces + (25 33) ;; Square brackets + ))) + +(ert-deftest rust-test-paren-matching-generic-fn () + (rust-test-matching-parens + " +fn foo() { +}" + '((8 10) ;; Angle brackets + (11 12) ;; Parens + (14 16) ;; Curly braces + ))) + +(ert-deftest rust-test-paren-matching-generic-fn-with-return-value () + (rust-test-matching-parens + " +fn foo() -> bool { + false +}" + '((8 10) ;; Angle brackets + (11 12) ;; Parens + (22 34) ;; Curly braces + ) + + '(15 ;; The ">" in "->" is not an angle bracket + ))) + +(ert-deftest rust-test-paren-matching-match-stmt () + (rust-test-matching-parens + " +fn foo() { + something_str(match ::method() { + Some(_) => \"Got some\", + None => \"Nada\" + }); +}" + '((8 9) ;; parens of fn foo + (11 127) ;; curly braces of foo + (30 124) ;; parens of something_str + (37 51) ;; angle brackets of + (60 61) ;; parens of method() + (63 123) ;; curly braces of match + (77 79) ;; parens of Some(_) + ) + + '(82 ;; > in first => + 112 ;; > in second => + ))) + +(ert-deftest rust-test-paren-matching-bitshift-operators () + (rust-test-matching-parens + " +fn foo(z:i32) { + let a:Option> = Some(Ok(4 >> 1)); + let b = a.map(|x| x.map(|y| y << 3)); + let trick_question = z<<::method(); // First two + ) + '(64 ;; The >> inside Some(Ok()) are not angle brackets + 65 ;; The >> inside Some(Ok()) are not angle brackets + 106 ;; The << inside map() are not angle brackets + 107 ;; The << inside map() are not angle brackets + 140 ;; The << before are not angle brackets + 141 ;; The << before are not angle brackets + 183 ;; The < inside the comment + ))) + +(ert-deftest rust-test-paren-matching-angle-bracket-after-colon-ident () + (rust-test-matching-parens + " +struct Bla { + a:Option<(i32,Option)>, + b:Option, + c:bool +} + +fn f(x:i32,y:Option) { + let z:Option = None; + let b:Bla = Bla{ + a:None, + b:None, + c:x + (30 49) ;; Outer angle brackets of a:Option<...> + (42 47) ;; Inner angle brackets of Option + (64 66) ;; Angle brackets of Option + (102 106) ;; Angle brackets of y:Option + (127 131) ;; Angle brackets of z:Option + (154 157) ;; Angle brackets of b:Bla + ) + '(209 ;; less than operator in c:x Bar { + Bar { + b:x<3 + } +}" + '() + '(17 ;; the -> is not a brace + 46 ;; x<3 the < is a less than sign + )) + ) + +(ert-deftest rust-test-paren-matching-nested-struct-literals () + (rust-test-matching-parens + " +fn f(x:i32,y:i32) -> Foo { + Foo{ + bar:Bar{ + a:3, + b:x + ) + '(92 ;; less than operator x X>() -> Z { +} +" + '((8 23) ;; The angle brackets of foo + (20 22 ;; The angle brackets of X + )) + '(17 ;; The first -> + 28 ;; The second -> + ) + )) + +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () + (rust-test-matching-parens + " +fn foo(x:i32) { + f(x < 3); +} +" + '() + '(26 ;; The < inside f is a less than operator + ) + )) + +(ert-deftest rust-test-paren-matching-lt-ops-in-fn-params () + (rust-test-matching-parens + " +fn foo(x:i32) -> bool { + return x < 3; +} +" + '() + '(17 ;; The -> + 39 ;; The < after return is a less than operator + ) + )) + +(ert-deftest rust-test-type-paren-matching-angle-brackets-in-type-items () + (rust-test-matching-parens + " +type Foo = Blah; +type Bar = (Foo, Bletch); +type ThisThing = HereYouGo B>,E>>;" + '((17 21) ;; Angle brackets of Blah + (32 34) ;; Angle brackets of Bar + (50 52) ;; Angle brackets of Bletch + (70 78) ;; Angle brackets of ThisThing + (91 118) ;; Angle brackets of HereYouGo<...> + (95 117) ;; Angle brackets of Y + (106 111) ;; Angle brackets of B> + (108 110) ;; Angle brackets of C + (114 116) ;; Angle brackets of E + ))) + +(ert-deftest rust-test-paren-matching-tuple-like-struct () + (rust-test-matching-parens + " +struct A(Option); +struct C(Result);" + '((17 19) ;; The angle brackets + (10 20) ;; The parens of A(); + (31 33) ;; The angle brackets of C + (41 47) ;; The angle brackets of Result + ) + '())) + +(ert-deftest rust-test-paren-matching-in-enum () + (rust-test-matching-parens + " +enum Boo { + TupleLike(Option), + StructLike{foo: Result} +}" + '((10 12) ;; Angle brackets of Boo + (36 38) ;; Angle brackets of Option + (68 74) ;; Angle brackets of Result + ))) + +(ert-deftest rust-test-paren-matching-assoc-type-bounds () + (rust-test-matching-parens + "impl >> Thing {}" + '((6 29) ;; Outer angle brackets of impl + (10 28) ;; Outer angle brackets of B> + (24 26) ;; Inner angle brackets of C + (36 38) ;; Angle brackets of Thing + ) + )) + +(ert-deftest rust-test-paren-matching-plus-signs-in-expressions-and-bounds () + ;; Note that as I write this, the function "bluh" below does not compile, but + ;; it warns that the equality constraint in a where clause is "not yet + ;; supported." It seems that the compiler will support this eventually, so + ;; the emacs mode needs to support it. + (rust-test-matching-parens + "fn foo,B>(a:A,b:B) -> bool where B:Trait3+Trait4 { + 2 + a < 3 && 3 + b > 11 +} + +fn bluh() where A:Fn()+MyTrait, MyTrait::AssocType = Option { +} + +fn fluh() where C:Fn(i32) -> (i32, i32) + SomeTrait, C::AssocType = OtherThing { +}" + '((7 30) ;; Angle brackets of foo<...> + (23 27) ;; Angle brackets of Trait2 + (63 67) ;; Angle brackets of Trait3 + (75 79) ;; Angle brackets of Trait4 + + (121 123) ;; Angle brackets of bluh + (147 151) ;; Angle brackets of MyTrait + + (161 163) ;; Angle brackets of MyTrait + (184 189) ;; Angle brackets of Option + + (203 205) ;; Angle brackets of + (250 254) ;; Angle brackets of SomeTrait + (282 287) ;; Angle brackets of Option + ) + '(93 ;; Less-than sign of a < 3 + 106 ;; Greater than sign of b > 11 + ))) + +(ert-deftest rust-test-paren-matching-generic-type-in-tuple-return-type () + (rust-test-matching-parens + "pub fn take(mut self) -> (EmptyBucket, K, V) {}" + '((38 46)) + )) + +(ert-deftest rust-test-paren-matching-references-and-logical-and () + (rust-test-matching-parens + " +fn ampersand_check(a: &Option, b:bool) -> &Option { + a.map(|x| { + b && x < 32 + }) +}" + '((31 35) ;; Option + (56 60) ;; Option + ) + '(95 ;; x < 32 + ) + ) + ) + +(ert-deftest rust-test-paren-matching-lt-sign-in-if-statement () + (rust-test-matching-parens + " +fn if_check(a:i32,b:i32,c:i32) { + if a + b < c { + + } + if a < b { + + } + if (c < a) { + + } +} + +fn while_check(x:i32,y:i32) -> bool { + while x < y { + } + for x in y < x { + } + match y < z { + true => (), _ => () + } + return z < y; +}" + '() + '(48 ;; b < c + 78 ;; a < b + 109 ;; (c < a) + + 184 ;; x < y + 211 ;; y < x + 235 ;; y < z + 288 ;; z < y + ))) + +(ert-deftest rust-test-paren-matching-lt-expr-with-field () + (rust-test-matching-parens + "fn foo() { x.y < 3 }" + '() + '(16 ;; x.y < 3 + ))) + +(ert-deftest rust-test-paren-matching-lt-expr-with-quote () + (rust-test-matching-parens + " +fn quote_check() { + 'x' < y; + \"y\" < x; + r##\"z\"## < q; + a <= 3 && b < '2' +}" + '() + '(29 ;; 'x' < y + 42 ;; "y" < x + 60 ;; r##"z"## < q + 71 ;; a <= '3' + 81 ;; b < '2' + ))) + +(ert-deftest rust-test-paren-matching-keywords-capitalized-are-ok-type-names () + (rust-test-matching-parens + " +fn foo() -> Box { + let z:If = If(a < 3); +}" + '((17 21) ;; Box + (37 42) ;; If + ) + '(51 ;; If(a < 3) + ))) + +(ert-deftest rust-test-paren-matching-lt-expression-inside-macro () + (rust-test-matching-parens + "fn bla() { assert!(x < y); }" + '() + '(22 ;; x < y + ))) + +(ert-deftest rust-test-paren-matching-array-types-with-generics () + (rust-test-matching-parens + "fn boo () -> [Option] {}" + '((21 25)))) + +(ert-deftest rust-test-paren-matching-angle-bracket-inner-reference () + (rust-test-matching-parens + "fn x() -> Option<&Node> {}" + '((17 26) ;; Option + (23 25) ;; Node + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-semicolon () + (rust-test-matching-parens + "fn f(x:i32) -> bool { (); x < 3 }" + '() + '(29 + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-comma () + (rust-test-matching-parens + "fn foo() { + (e, a < b) +}" + '((16 25) ;; The parens () + ) + '(22 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-let () + (rust-test-matching-parens + "fn main() { + let x = a < b; +}" + '((11 32) ;; The { } + ) + '(27 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-two-lt-ops-in-a-row () + (rust-test-matching-parens + "fn next(&mut self) -> Option<::Item>" + '((29 51) ;; Outer Option<> + (30 44) ;; Inner + ) + '(21 + ))) + +(ert-deftest rust-test-paren-matching-lt-after-caret () + (rust-test-matching-parens + "fn foo() { x^2 < 3 }" + '((10 20) ;; The { } + ) + '(16 ;; The < operator + ))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-special-type () + (rust-test-matching-parens + "fn foo() { low as u128 <= c }" + '((10 29)) + '(24))) + +(ert-deftest rust-test-paren-matching-lt-operator-after-closing-curly-brace () + (rust-test-matching-parens + "fn main() { if true {} a < 3 }" + '((11 30) + ) + '(26))) + +(ert-deftest rust-test-paren-matching-const () + (rust-test-matching-parens + " +const BLA = 1 << 3; +const BLUB = 2 < 4;" + '() + '(16 + 17 ;; Both chars of the << in 1 << 3 + 37 ;; The < in 2 < 4 + ))) + +(ert-deftest rust-test-paren-matching-c-like-enum () + (rust-test-matching-parens + " +enum CLikeEnum { + Two = 1 << 1, + Four = 1 << 2 +}" + '((17 56 ;; The { } of the enum + )) + '(31 + 32 ;; The first << + 50 + 51 ;; The second << + ))) + +(ert-deftest rust-test-paren-matching-no-angle-brackets-in-macros () + (rust-test-matching-parens + " +fn foo(a:A) { + macro_a!( foo:: ); + macro_b![ foo as Option ]; +} + +macro_c!{ + struct Boo {} +}" + '((8 10)) + ;; Inside macros, it should not find any angle brackets, even if it normally + ;; would + '(38 ;; macro_a < + 57 ;; macro_a > + 89 ;; macro_b < + 91 ;; macro_b > + 123 ;; macro_c < + 125 ;; macro_d > + ))) + +(ert-deftest rust-test-paren-matching-no-angle-brackets-in-macro-rules () + (rust-test-matching-parens + " +fn foo(a:A) { + macro_rules! foo ( foo:: ); + macro_rules! bar [ foo as Option ]; +} + +macro_c!{ + struct Boo {} +}" + '((8 10)) + ;; Inside macros, it should not find any angle brackets, even if it normally + ;; would + '(47 ;; foo < + 62 ;; foo > + 107 ;; bar < + 109 ;; bar > + 141 ;; macro_c < + 143 ;; macro_c > + ))) + +(ert-deftest rust-test-in-macro-do-not-fail-on-unbalance () + (should + ;; We don't care about the results here, so long as they do not error + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (goto-char (point-max)) + (syntax-ppss)))) + + +(ert-deftest rust-test-in-macro-no-caching () + (should-not + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (search-backward "macro") + ;; do not use the cache + (let ((rust-macro-scopes nil)) + (rust-in-macro))))) + +(ert-deftest rust-test-in-macro-fake-cache () + (should + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (search-backward "macro") + ;; make the cache lie to make the whole buffer in scope + ;; we need to be at paren level 1 for this to work + (let ((rust-macro-scopes `((,(point-min) ,(point-max))))) + (rust-in-macro))))) + +(ert-deftest rust-test-in-macro-broken-cache () + (should-error + (with-temp-buffer + (insert + "fn foo(a:A) { + macro_c!{ + struct Boo {} +") + (rust-mode) + (search-backward "Boo") + ;; do we use the cache at all + (let ((rust-macro-scopes '(I should break))) + (rust-in-macro))))) + +(ert-deftest rust-test-in-macro-nested () + (should + (equal + (with-temp-buffer + (insert + "macro_rules! outer { + () => { vec![] }; +}") + (rust-mode) + (rust-macro-scope (point-min) (point-max))) + '((38 40) (20 45))))) + +(ert-deftest rust-test-in-macro-not-with-space () + (should + (equal + (with-temp-buffer + (insert + "fn foo() { + if !(mem::size_of::() > 8) { + bar() + } +}") + (rust-mode) + (rust-macro-scope (point-min) (point-max))) + 'empty))) + + +(ert-deftest rust-test-paren-matching-type-with-module-name () + (rust-test-matching-parens + " +const X: libc::c_int = 1 << 2; +fn main() { + let z: libc::c_uint = 1 << 4; +} +" + '((43 79)) ;; The curly braces + '(27 + 28 ;; The first << + 73 + 74 ;; The second << + ))) + +(ert-deftest rust-test-paren-matching-qualififed-struct-literal () + (rust-test-matching-parens + " +fn foo() -> Fn(asd) -> F { + let z = foo::Struct{ b: 1 << 4, c: 2 < 4 } +}" + '((30 80) ;; Outer curly brackets + ) + '(62 + 63 ;; The shift operator + 73 ;; The less than operator + ))) + +(ert-deftest rust-test-paren-matching-let-mut () + (rust-test-matching-parens + " +fn f() { + let mut b = 1 < 3; + let mut i = 1 << 3; +} +" + '() + '(28 ;; 1 < 3 + 51 + 52 ;; 1 << 3 + ))) + +(ert-deftest rust-test-paren-matching-as-ref-type () + (rust-test-matching-parens + "fn f() { + let a = b as &Foo; +}" + '((31 35) ;; Angle brackets Foo + ))) + +(ert-deftest rust-test-paren-matching-type-ascription () + (rust-test-matching-parens + " +fn rfc803() { + let z = a < b:FunnkyThing; + let s = Foo { + a: b < 3, + b: d:CrazyStuff < 3, + c: 2 < x:CrazyStuff + } +}" + '((45 49) ;; FunkyThing + (111 115) ;; CrazyStuff + (149 154) ;; CrazyStuff + ) + '(30 ;; a < b + 83 ;; b < 3 + 117 ;; d... < 3 + 135 ;; 2 < x + ))) + +(ert-deftest rust-test-paren-matching-angle-brackets-in-enum-with-where-claause () + (rust-test-matching-parens + " +enum MyEnum where T:std::fmt::Debug { + Thing(Option) +}" + '((13 15) ;; MyEnum + (59 61) ;; Option + ))) + +(ert-deftest rust-test-paren-matching-where-clauses-with-closure-types () + (rust-test-matching-parens + " +enum Boo<'a,T> where T:Fn() -> Option<&'a str> + 'a { + Thingy(Option<&'a T>) +} + +fn foo<'a>() -> Result where C::X: D, B:FnMut() -> Option+'a { + Foo(a < b) +} + +type Foo where T: Copy = Box; +" + '((10 15) ;; Boo<'a,T> + (39 47) ;; Option<&'a str> + (72 78) ;; Option<&'a T> + + (106 110) ;; Result + (125 127) ;; D + (149 151) ;; Option + (184 186) ;; Foo + (207 209) ;; Box + ) + + '(168 ;; Foo(a < b) + ) + )) + +(ert-deftest rust-test-angle-bracket-matching-turned-off () + (let ((rust-match-angle-brackets nil)) + (rust-test-matching-parens + "fn foo() {}" + '((10 11)) + '(7 9)))) + + +(ert-deftest redo-syntax-after-change-far-from-point () + (let* + ((tmp-file-name (make-temp-file "rust-mdoe-test-issue104")) + (base-contents (apply #'concat (append '("fn foo() {\n\n}\n") (make-list 500 "// More stuff...\n") '("fn bar() {\n\n}\n"))))) + ;; Create the temp file... + (with-temp-file tmp-file-name + (insert base-contents)) + (with-temp-buffer + (insert-file-contents tmp-file-name 'VISIT nil nil 'REPLACE) + (rust-mode) + (goto-char (point-max)) + (should (= 0 (rust-paren-level))) + (with-temp-file tmp-file-name + (insert base-contents) + (goto-char 12) ;; On the blank line in the middle of fn foo + (insert " let z = 1 < 3;") + ) + (revert-buffer 'IGNORE-AUTO 'NOCONFIRM 'PRESERVE-MODES) + (should (= 0 (rust-paren-level))) + ) + ) + ) + +(defun test-imenu (code expected-items) + (with-temp-buffer + (rust-mode) + (insert code) + (let ((actual-items + ;; Replace ("item" . #() { +} + +pub ( in self::super ) fn f9() { +} + +pub ( in super ) fn f10() { +} + +pub(in crate) fn f11() { +} + +pub (in self) fn f12() { +} +" + '(("Fn" + "f1" + "f2" + "f3" + "f4" + "f5" + "f6" + "f7" + "f8" + "f9" + "f10" + "f11" + "f12")))) + +(ert-deftest rust-test-imenu-impl-with-lifetime () + (test-imenu + " +impl<'a> One<'a> { + fn one() {} +} + +impl Two<'a> { + fn two() {} +} +" + '(("Impl" "One" "Two") + ("Fn" "one" "two")))) + +(ert-deftest font-lock-function-parameters () + (rust-test-font-lock + "fn foo(a: u32, b : u32) {}" + '("fn" font-lock-keyword-face + "foo" font-lock-function-name-face + "a" font-lock-variable-name-face + "u32" font-lock-type-face + "b" font-lock-variable-name-face + "u32" font-lock-type-face))) + +(ert-deftest variable-in-for-loop () + (rust-test-font-lock + "for var in iter" + '("for" font-lock-keyword-face + "var" font-lock-variable-name-face + "in" font-lock-keyword-face)) + (rust-test-font-lock + "for Foo{var} in iter" + '("for" font-lock-keyword-face + "Foo" font-lock-type-face + "in" font-lock-keyword-face))) + +(ert-deftest rust-test-dbg-wrap-symbol () + (rust-test-manip-code + "let x = add(first, second);" + 15 + #'rust-dbg-wrap-or-unwrap + "let x = add(dbg!(first), second);")) + +(ert-deftest rust-test-dbg-wrap-symbol-unbalanced () + (rust-test-manip-code + "let x = add((first, second);" + 14 + #'rust-dbg-wrap-or-unwrap + "let x = add((dbg!(first), second);")) + +(ert-deftest rust-test-dbg-wrap-region () + (rust-test-manip-code + "let x = add(first, second);" + 9 + (lambda () + (transient-mark-mode 1) + (push-mark nil t t) + (goto-char 26) + (rust-dbg-wrap-or-unwrap)) + "let x = dbg!(add(first, second));")) + +(defun rust-test-dbg-unwrap (position) + (rust-test-manip-code + "let x = add(dbg!(first), second);" + position + #'rust-dbg-wrap-or-unwrap + "let x = add(first, second);")) + +(ert-deftest rust-test-dbg-uwnrap-within () + (rust-test-dbg-unwrap 19)) + +(ert-deftest rust-test-dbg-uwnrap-on-paren () + (rust-test-dbg-unwrap 17)) + +(ert-deftest rust-test-dbg-uwnrap-on-dbg-middle () + (rust-test-dbg-unwrap 15)) + +(ert-deftest rust-test-dbg-uwnrap-on-dbg-start () + (rust-test-dbg-unwrap 13)) + +(ert-deftest rust-test-dbg-unwrap-inside-string-literal () + (rust-test-manip-code + "let x = \"foo, bar\"";" + 15 + #'rust-dbg-wrap-or-unwrap + "let x = dbg!(\"foo, bar\")")) + +(ert-deftest rust-test-project-located () + (skip-unless (executable-find rust-cargo-bin)) + (let* ((test-dir (expand-file-name "test-project/" default-directory)) + (manifest-file (expand-file-name "Cargo.toml" test-dir))) + (let ((default-directory test-dir)) + (should (equal (expand-file-name (rust-buffer-project)) manifest-file))))) + +(defun rust-collect-matches (spec) + (let ((matches nil)) + (goto-char (point-min)) + (while (re-search-forward (car spec) nil t) + (push + (mapcar (lambda (r) + (let ((match-pos + (nth (cdr r) spec))) + (cond ((and (eq :type (car r)) (consp match-pos)) + (compilation-face match-pos)) + ((eq :type (car r)) + (cdr (assoc match-pos '((1 . compilation-warning) + (0 . compilation-info) + (2 . compilation-error))))) + ((and (null match-pos) (eq :column (car r))) + 'back-to-indentation) + ((and (null match-pos) (eq :file (car r))) + 'like-previous-one) + ((null match-pos) + (error (format "%S" (car r)))) + (t + (match-string match-pos))))) + ;; see compilation-error-regexp-alist + '((:file . 1) + (:line . 2) + (:column . 3) + (:type . 4) + (:mouse-highlight . 5))) + matches)) + (nreverse matches))) + +(ert-deftest compilation-regexp-dashes () + (with-temp-buffer + ;; should match + (insert "error found a -> b\n --> file1.rs:12:34\n\n") + (insert "error[E1234]: found a -> b\n --> file2.rs:12:34\n\n") + (insert "warning found a -> b\n --> file3.rs:12:34\n\n") + (insert "note: `ZZZ` could also refer to the constant imported here -> b\n --> file4.rs:12:34\n\n") + (insert " ::: file5.rs:12:34\n\n") + ;; should not match + (insert "werror found a -> b\n --> no_match.rs:12:34\n\n") + (insert "error[E0061]: this function takes 1 parameter but 2 parameters were supplied\n --> file6.rs:132:34 + | +82 | fn duration_ms_since(time: &Option) -> u128 { + | ------------------------------------------------------- defined here +... +132 | self.total_time_ms = duration_ms_since(&self.program_start, 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +") + (should (equal + '((("file1.rs" "12" "34" compilation-error "file1.rs:12:34") + ("file2.rs" "12" "34" compilation-error "file2.rs:12:34") + ("file3.rs" "12" "34" compilation-warning "file3.rs:12:34") + ("file4.rs" "12" "34" compilation-info "file4.rs:12:34") + ("file6.rs" "132" "34" compilation-error "file6.rs:132:34")) + (("file5.rs" "12" "34" compilation-info "file5.rs:12:34")) + ((like-previous-one "82" back-to-indentation compilation-info "82") + (like-previous-one "132" back-to-indentation compilation-info "132"))) + (mapcar #'rust-collect-matches + (list rustc-compilation-regexps + rustc-colon-compilation-regexps + rustc-refs-compilation-regexps)))))) + +;; If electric-pair-mode is available, load it and run the tests that use it. If not, +;; no error--the tests will be skipped. +(require 'elec-pair nil t) + +;; The emacs 23 and 24 versions of ERT do not have test skipping +;; functionality. So don't even define these tests if elec-pair is +;; not available. +(when (featurep 'elec-pair) + (defun test-electric-pair-insert (original point-pos char closer) + (let ((old-electric-pair-mode electric-pair-mode)) + (electric-pair-mode 1) + (unwind-protect + (with-temp-buffer + (rust-mode) + (insert original) + (font-lock-flush) + (font-lock-ensure) + + (goto-char point-pos) + (deactivate-mark) + (let ((last-command-event char)) (self-insert-command 1)) + (should (equal (char-after) + (or closer (aref original point-pos))))) + (electric-pair-mode (or old-electric-pair-mode 1))))) + + (ert-deftest rust-test-electric-pair-generic-fn () + (test-electric-pair-insert "fn foo() { }" 7 ?< ?>)) + + (ert-deftest rust-test-electric-pair-impl-param () + (test-electric-pair-insert "impl Foo for Bar" 5 ?< ?>)) + + (ert-deftest rust-test-electric-pair-impl-for-type-param () + (test-electric-pair-insert "impl Foo for Bar" 22 ?< ?>)) + + (ert-deftest rust-test-electric-pair-lt-expression () + (test-electric-pair-insert "fn foo(bar:i32) -> bool { bar }" 30 ?< nil)) + + (ert-deftest rust-test-electric-pair-lt-expression-in-struct-literal () + (test-electric-pair-insert "fn foo(x:i32) -> Bar { Bar { a:(bleh() + whatever::()), b:x } }" 63 ?< nil)) + + (ert-deftest rust-test-electric-pair-lt-expression-capitalized-keyword () + (test-electric-pair-insert "fn foo() -> Box" 16 ?< ?>)) + + (ert-deftest rust-test-electric-pair-<-> () + (let ((old-electric-pair-mode electric-pair-mode)) + (electric-pair-mode 1) + (unwind-protect + (with-temp-buffer + (electric-pair-mode 1) + (rust-mode) + (mapc (lambda (c) + (let ((last-command-event c)) (self-insert-command 1))) + "tmp") + (should + (equal "tmp" (buffer-substring-no-properties (point-min) + (point-max))))) + (electric-pair-mode (or old-electric-pair-mode 1)))))) + +;; Ensure the byte compiler knows about this function, even though it’s +;; conditionally-defined. +(declare-function test-electric-pair-insert "rust-mode-tests" + (original point-pos char closer)) + +(ert-deftest rust-mode-map () + (with-temp-buffer + (let (from to match (match-count 0)) + (rust-mode) + (describe-buffer-bindings (current-buffer)) + (goto-char (point-min)) + (re-search-forward "Major Mode Bindings") + (setq from (point)) + (re-search-forward " ") + (setq to (point)) + (goto-char from) + (while (re-search-forward "^C-c.*$" to t) + (setq match-count (1+ match-count)) + (setq match (match-string 0)) + (eval + `(should + (or + (string-match "Prefix Command" ,match) + (string-match "^C-c C" ,match))) + t)) + (should (< 0 match-count))))) diff --git a/.emacs.d/lisp/rust-mode.el b/.emacs.d/lisp/rust-mode.el new file mode 100644 index 0000000..80106b8 --- /dev/null +++ b/.emacs.d/lisp/rust-mode.el @@ -0,0 +1,1632 @@ +;;; rust-mode.el --- A major-mode for editing Rust source code -*-lexical-binding: t-*- + +;; Version: 1.0.5 +;; Author: Mozilla +;; Url: https://github.com/rust-lang/rust-mode +;; Keywords: languages +;; Package-Requires: ((emacs "25.1")) + +;; This file is distributed under the terms of both the MIT license and the +;; Apache License (version 2.0). + +;;; Commentary: + +;; This package implements a major-mode for editing Rust source code. + +;;; Code: + +(eval-when-compile (require 'rx)) + +(defvar rust-load-optional-libraries t + "Whether loading `rust-mode' also loads optional libraries. +This variable might soon be remove again.") + +(when rust-load-optional-libraries + (require 'rust-cargo) + (require 'rust-compile) + (require 'rust-playpen) + (require 'rust-rustfmt)) + +(defvar electric-pair-inhibit-predicate) +(defvar electric-pair-skip-self) +(defvar electric-indent-chars) + +(defcustom rust-before-save-hook 'rust-before-save-method + "Function for formatting before save." + :type 'function + :group 'rust-mode) + +(defcustom rust-after-save-hook 'rust-after-save-method + "Default method to handle rustfmt invocation after save." + :type 'function + :group 'rust-mode) + +(defvar rust-prettify-symbols-alist + '(("&&" . ?∧) ("||" . ?∨) + ("<=" . ?≤) (">=" . ?≥) ("!=" . ?≠) + ("INFINITY" . ?∞) ("->" . ?→) ("=>" . ?⇒)) + "Alist of symbol prettifications used for `prettify-symbols-alist'.") + +;;; Customization + +(defgroup rust-mode nil + "Support for Rust code." + :link '(url-link "https://www.rust-lang.org/") + :group 'languages) + +(defcustom rust-indent-offset 4 + "Indent Rust code by this number of spaces." + :type 'integer + :group 'rust-mode + :safe #'integerp) + +(defcustom rust-indent-method-chain nil + "Indent Rust method chains, aligned by the `.' operators." + :type 'boolean + :group 'rust-mode + :safe #'booleanp) + +(defcustom rust-indent-where-clause nil + "Indent lines starting with the `where' keyword following a function or trait. +When nil, `where' will be aligned with `fn' or `trait'." + :type 'boolean + :group 'rust-mode + :safe #'booleanp) + +(defcustom rust-match-angle-brackets t + "Whether to enable angle bracket (`<' and `>') matching where appropriate." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-indent-return-type-to-arguments t + "Indent a line starting with the `->' (RArrow) following a function, aligning +to the function arguments. When nil, `->' will be indented one level." + :type 'boolean + :group 'rust-mode + :safe #'booleanp) + +;;; Faces + +(define-obsolete-face-alias 'rust-unsafe-face + 'rust-unsafe "0.6.0") +(define-obsolete-face-alias 'rust-question-mark-face + 'rust-question-mark "0.6.0") +(define-obsolete-face-alias 'rust-builtin-formatting-macro-face + 'rust-builtin-formatting-macro "0.6.0") +(define-obsolete-face-alias 'rust-string-interpolation-face + 'rust-string-interpolation "0.6.0") + +(defface rust-unsafe + '((t :inherit font-lock-warning-face)) + "Face for the `unsafe' keyword." + :group 'rust-mode) + +(defface rust-question-mark + '((t :weight bold :inherit font-lock-builtin-face)) + "Face for the question mark operator." + :group 'rust-mode) + +(defface rust-ampersand-face + '((t :inherit default)) + "Face for the ampersand reference mark." + :group 'rust-mode) + +(defface rust-builtin-formatting-macro + '((t :inherit font-lock-builtin-face)) + "Face for builtin formatting macros (print! &c.)." + :group 'rust-mode) + +(defface rust-string-interpolation + '((t :slant italic :inherit font-lock-string-face)) + "Face for interpolating braces in builtin formatting macro strings." + :group 'rust-mode) + +;;; Syntax + +(defun rust-re-word (inner) (concat "\\<" inner "\\>")) +(defun rust-re-grab (inner) (concat "\\(" inner "\\)")) +(defun rust-re-shy (inner) (concat "\\(?:" inner "\\)")) + +(defconst rust-re-ident "[[:word:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") +(defconst rust-re-lc-ident "[[:lower:][:multibyte:]_][[:word:][:multibyte:]_[:digit:]]*") +(defconst rust-re-uc-ident "[[:upper:]][[:word:][:multibyte:]_[:digit:]]*") +(defvar rust-re-vis + ;; pub | pub ( crate ) | pub ( self ) | pub ( super ) | pub ( in SimplePath ) + (concat + "pub" + (rust-re-shy + (concat + "[[:space:]]*([[:space:]]*" + (rust-re-shy + (concat "crate" "\\|" + "\\(?:s\\(?:elf\\|uper\\)\\)" "\\|" + ;; in SimplePath + (rust-re-shy + (concat + "in[[:space:]]+" + rust-re-ident + (rust-re-shy (concat "::" rust-re-ident)) "*")))) + "[[:space:]]*)")) + "?")) +(defconst rust-re-unsafe "unsafe") +(defconst rust-re-extern "extern") +(defconst rust-re-async-or-const "async\\|const") +(defconst rust-re-generic + (concat "<[[:space:]]*'" rust-re-ident "[[:space:]]*>")) +(defconst rust-re-union + (rx-to-string + `(seq + (or space line-start) + (group symbol-start "union" symbol-end) + (+ space) (regexp ,rust-re-ident)))) + +(defun rust-re-item-def (itype) + (concat (rust-re-word itype) + (rust-re-shy rust-re-generic) "?" + "[[:space:]]+" (rust-re-grab rust-re-ident))) + +;; TODO some of this does only make sense for `fn' (unsafe, extern...) +;; and not other items +(defun rust-re-item-def-imenu (itype) + (concat "^[[:space:]]*" + (rust-re-shy (concat rust-re-vis "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-word "default") "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-shy rust-re-async-or-const) "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-word rust-re-unsafe) "[[:space:]]+")) "?" + (rust-re-shy (concat (rust-re-word rust-re-extern) "[[:space:]]+" + (rust-re-shy "\"[^\"]+\"[[:space:]]+") "?")) "?" + (rust-re-item-def itype))) + +(defvar rust-imenu-generic-expression + (append (mapcar #'(lambda (x) + (list (capitalize x) (rust-re-item-def-imenu x) 1)) + '("enum" "struct" "union" "type" "mod" "fn" "trait" "impl")) + `(("Macro" ,(rust-re-item-def-imenu "macro_rules!") 1))) + "Value for `imenu-generic-expression' in Rust mode. + +Create a hierarchical index of the item definitions in a Rust file. + +Imenu will show all the enums, structs, etc. in their own subheading. +Use idomenu (imenu with `ido-mode') for best mileage.") + +(defvar rust-mode-syntax-table + (let ((table (make-syntax-table))) + + ;; Operators + (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?^ ?! ?< ?> ?~ ?@)) + (modify-syntax-entry i "." table)) + + ;; Strings + (modify-syntax-entry ?\" "\"" table) + (modify-syntax-entry ?\\ "\\" table) + + ;; Angle brackets. We suppress this with syntactic propertization + ;; when needed + (modify-syntax-entry ?< "(>" table) + (modify-syntax-entry ?> ")<" table) + + ;; Comments + (modify-syntax-entry ?/ ". 124b" table) + (modify-syntax-entry ?* ". 23n" table) + (modify-syntax-entry ?\n "> b" table) + (modify-syntax-entry ?\^m "> b" table) + + table) + "Syntax definitions and helpers.") + +;;; Prettify + +(defun rust--prettify-symbols-compose-p (start end match) + "Return true iff the symbol MATCH should be composed. +See `prettify-symbols-compose-predicate'." + (and (fboundp 'prettify-symbols-default-compose-p) + (prettify-symbols-default-compose-p start end match) + ;; Make sure || is not a closure with 0 arguments and && is not + ;; a double reference. + (pcase match + ("||" (not (save-excursion + (goto-char start) + (looking-back "\\(?:\\") + "Start of a Rust item.") + +(defconst rust-re-type-or-constructor + (rx symbol-start + (group upper (0+ (any word nonascii digit "_"))) + symbol-end)) + +(defconst rust-keywords + '("as" "async" "await" + "box" "break" + "const" "continue" "crate" + "do" "dyn" + "else" "enum" "extern" "existential" + "false" "fn" "for" + "if" "impl" "in" + "let" "loop" + "match" "mod" "move" "mut" + "priv" "pub" + "ref" "return" + "self" "static" "struct" "super" + "true" "trait" "type" "try" + "use" + "virtual" + "where" "while" + "yield") + "Font-locking definitions and helpers.") + +(defconst rust-special-types + '("u8" "i8" + "u16" "i16" + "u32" "i32" + "u64" "i64" + "u128" "i128" + + "f32" "f64" + "isize" "usize" + "bool" + "str" "char")) + +(defconst rust-number-with-type + (eval-when-compile + (concat + "\\_<\\(?:0[box]?\\|[1-9]\\)[[:digit:]a-fA-F_.]*\\(?:[eE][+-]?[[:digit:]_]\\)?" + (regexp-opt '("u8" "i8" "u16" "i16" "u32" "i32" "u64" "i64" + "u128" "i128" "usize" "isize" "f32" "f64") + t) + "\\_>")) + "Regular expression matching a number with a type suffix.") + +(defvar rust-builtin-formatting-macros + '("eprint" + "eprintln" + "format" + "print" + "println") + "List of builtin Rust macros for string formatting. +This is used by `rust-font-lock-keywords'. +\(`write!' is handled separately).") + +(defvar rust-formatting-macro-opening-re + "[[:space:]\n]*[({[][[:space:]\n]*" + "Regular expression to match the opening delimiter of a Rust formatting macro.") + +(defvar rust-start-of-string-re + "\\(?:r#*\\)?\"" + "Regular expression to match the start of a Rust raw string.") + +(defun rust-path-font-lock-matcher (re-ident) + "Match occurrences of RE-IDENT followed by a double-colon. +Examples include to match names like \"foo::\" or \"Foo::\". +Does not match type annotations of the form \"foo::<\"." + `(lambda (limit) + (catch 'rust-path-font-lock-matcher + (while t + (let* ((symbol-then-colons (rx-to-string '(seq (group (regexp ,re-ident)) "::"))) + (match (re-search-forward symbol-then-colons limit t))) + (cond + ;; If we didn't find a match, there are no more occurrences + ;; of foo::, so return. + ((null match) (throw 'rust-path-font-lock-matcher nil)) + ;; If this isn't a type annotation foo::<, we've found a + ;; match, so a return it! + ((not (looking-at (rx (0+ space) "<"))) + (throw 'rust-path-font-lock-matcher match)))))))) + +(defvar rust-font-lock-keywords + (append + `( + ;; Keywords proper + (,(regexp-opt rust-keywords 'symbols) . font-lock-keyword-face) + + ;; Contextual keywords + ("\\_<\\(default\\)[[:space:]]+fn\\_>" 1 font-lock-keyword-face) + (,rust-re-union 1 font-lock-keyword-face) + + ;; Special types + (,(regexp-opt rust-special-types 'symbols) . font-lock-type-face) + + ;; The unsafe keyword + ("\\_" . 'rust-unsafe) + + ;; Attributes like `#[bar(baz)]` or `#![bar(baz)]` or `#[bar = "baz"]` + (,(rust-re-grab (concat "#\\!?\\[" rust-re-ident "[^]]*\\]")) + 1 font-lock-preprocessor-face keep) + + ;; Builtin formatting macros + (,(concat (rust-re-grab + (concat (rust-re-word (regexp-opt rust-builtin-formatting-macros)) + "!")) + rust-formatting-macro-opening-re + "\\(?:" rust-start-of-string-re "\\)?") + (1 'rust-builtin-formatting-macro) + (rust-string-interpolation-matcher + (rust-end-of-string) + nil + (0 'rust-string-interpolation t nil))) + + ;; write! macro + (,(concat (rust-re-grab (concat (rust-re-word "write\\(ln\\)?") "!")) + rust-formatting-macro-opening-re + "[[:space:]]*[^\"]+,[[:space:]]*" + rust-start-of-string-re) + (1 'rust-builtin-formatting-macro) + (rust-string-interpolation-matcher + (rust-end-of-string) + nil + (0 'rust-string-interpolation t nil))) + + ;; 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) "[[:space:]]*:[^:]") + 1 font-lock-variable-name-face) + + ;; CamelCase Means Type Or Constructor + (,rust-re-type-or-constructor 1 font-lock-type-face) + + ;; Type-inferred binding + (,(concat "\\_<\\(?:let\\s-+ref\\|let\\|ref\\|for\\)\\s-+\\(?:mut\\s-+\\)?" + (rust-re-grab rust-re-ident) + "\\_>") + 1 font-lock-variable-name-face) + + ;; Type names like `Foo::`, highlight excluding the :: + (,(rust-path-font-lock-matcher rust-re-uc-ident) 1 font-lock-type-face) + + ;; Module names like `foo::`, highlight excluding the :: + (,(rust-path-font-lock-matcher rust-re-lc-ident) 1 font-lock-constant-face) + + ;; Lifetimes like `'foo` + (,(concat "'" (rust-re-grab rust-re-ident) "[^']") 1 font-lock-variable-name-face) + + ;; Question mark operator + ("\\?" . 'rust-question-mark) + ("\\(&+\\)\\(?:'\\(?:\\<\\|_\\)\\|\\<\\|[[({:*_|]\\)" + 1 'rust-ampersand-face) + ;; Numbers with type suffix + (,rust-number-with-type 1 font-lock-type-face) + ) + + ;; Ensure we highlight `Foo` in `struct Foo` as a type. + (mapcar #'(lambda (x) + (list (rust-re-item-def (car x)) + 1 (cdr x))) + '(("enum" . font-lock-type-face) + ("struct" . font-lock-type-face) + ("union" . font-lock-type-face) + ("type" . font-lock-type-face) + ("mod" . font-lock-constant-face) + ("use" . font-lock-constant-face) + ("fn" . font-lock-function-name-face))))) + +(defun rust-end-of-string () + "Skip to the end of the current string." + (save-excursion + (skip-syntax-forward "^\"|") + (skip-syntax-forward "\"|") + (point))) + +(defun rust-looking-back-str (str) + "Return non-nil if there's a match on the text before point and STR. +Like `looking-back' but for fixed strings rather than regexps (so +that it's not so slow)." + (let ((len (length str))) + (and (> (point) len) + (equal str (buffer-substring-no-properties (- (point) len) (point)))))) + +(defun rust-looking-back-symbols (symbols) + "Return non-nil if the point is after a member of SYMBOLS. +SYMBOLS is a list of strings that represent the respective +symbols." + (save-excursion + (let* ((pt-orig (point)) + (beg-of-symbol (progn (forward-thing 'symbol -1) (point))) + (end-of-symbol (progn (forward-thing 'symbol 1) (point)))) + (and + (= end-of-symbol pt-orig) + (member (buffer-substring-no-properties beg-of-symbol pt-orig) + symbols))))) + +(defun rust-looking-back-ident () + "Non-nil if we are looking backwards at a valid rust identifier." + (let ((beg-of-symbol (save-excursion (forward-thing 'symbol -1) (point)))) + (looking-back rust-re-ident beg-of-symbol))) + +(defun rust-looking-back-macro () + "Non-nil if looking back at an ident followed by a ! + +This is stricter than rust syntax which allows a space between +the ident and the ! symbol. If this space is allowed, then we +would also need a keyword check to avoid `if !(condition)` being +seen as a macro." + (if (> (- (point) (point-min)) 1) + (save-excursion + (backward-char) + (and (= ?! (char-after)) + (rust-looking-back-ident))))) + +;;; Syntax definitions and helpers + +(defun rust-paren-level () (nth 0 (syntax-ppss))) +(defun rust-in-str () (nth 3 (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 ((continue t)) + (while continue + (let ((starting (point))) + (skip-chars-backward "[:space:]\n") + (when (rust-looking-back-str "*/") + (backward-char)) + (when (rust-in-str-or-cmnt) + (rust-rewind-past-str-cmnt)) + ;; Rewind until the point no longer moves + (setq continue (/= starting (point))))))) + +(defvar-local rust-macro-scopes nil + "Cache for the scopes calculated by `rust-macro-scope'. + +This variable can be `let' bound directly or indirectly around +`rust-macro-scope' as an optimization but should not be otherwise +set.") + +(defun rust-macro-scope (start end) + "Return the scope of macros in the buffer. + +The return value is a list of (START END) positions in the +buffer. + +If set START and END are optimizations which limit the return +value to scopes which are approximately with this range." + (save-excursion + ;; need to special case macro_rules which has unique syntax + (let ((scope nil) + (start (or start (point-min))) + (end (or end (point-max)))) + (goto-char start) + ;; if there is a start move back to the previous top level, + ;; as any macros before that must have closed by this time. + (let ((top (syntax-ppss-toplevel-pos (syntax-ppss)))) + (when top + (goto-char top))) + (while + (and + ;; The movement below may have moved us passed end, in + ;; which case search-forward will error + (< (point) end) + (search-forward "!" end t)) + (let ((pt (point))) + (cond + ;; in a string or comment is boring, move straight on + ((rust-in-str-or-cmnt)) + ;; in a normal macro, + ((and (skip-chars-forward " \t\n\r") + (memq (char-after) + '(?\[ ?\( ?\{)) + ;; Check that we have a macro declaration after. + (rust-looking-back-macro)) + (let ((start (point))) + (ignore-errors (forward-list)) + (setq scope (cons (list start (point)) scope)))) + ;; macro_rules, why, why, why did you not use macro syntax?? + ((save-excursion + ;; yuck -- last test moves point, even if it fails + (goto-char (- pt 1)) + (skip-chars-backward " \t\n\r") + (rust-looking-back-str "macro_rules")) + (save-excursion + (when (re-search-forward "[[({]" nil t) + (backward-char) + (let ((start (point))) + (ignore-errors (forward-list)) + (setq scope (cons (list start (point)) scope))))))))) + ;; Return 'empty rather than nil, to indicate a buffer with no + ;; macros at all. + (or scope 'empty)))) + +(defun rust-in-macro (&optional start end) + "Return non-nil when point is within the scope of a macro. + +If START and END are set, minimize the buffer analysis to +approximately this location as an optimization. + +Alternatively, if `rust-macro-scopes' is a list use the scope +information in this variable. This last is an optimization and +the caller is responsible for ensuring that the data in +`rust-macro-scopes' is up to date." + (when (> (rust-paren-level) 0) + (let ((scopes + (or + rust-macro-scopes + (rust-macro-scope start end)))) + ;; `rust-macro-scope' can return the symbol `empty' if the + ;; buffer has no macros at all. + (when (listp scopes) + (seq-some + (lambda (sc) + (and (>= (point) (car sc)) + (< (point) (cadr sc)))) + scopes))))) + +(defun rust-looking-at-where () + "Return T when looking at the \"where\" keyword." + (and (looking-at-p "\\bwhere\\b") + (not (rust-in-str-or-cmnt)))) + +(defun rust-rewind-to-where (&optional limit) + "Rewind the point to the closest occurrence of the \"where\" keyword. +Return T iff a where-clause was found. Does not rewind past +LIMIT when passed, otherwise only stops at the beginning of the +buffer." + (when (re-search-backward "\\bwhere\\b" limit t) + (if (rust-in-str-or-cmnt) + (rust-rewind-to-where limit) + t))) + +(defconst rust-re-pre-expression-operators "[-=!%&*/:<>[{(|.^;}]") + +(defconst rust-re-special-types (regexp-opt rust-special-types 'symbols)) + +(defun rust-align-to-expr-after-brace () + (save-excursion + (forward-char) + ;; We don't want to indent out to the open bracket if the + ;; open bracket ends the line + (when (not (looking-at "[[:blank:]]*\\(?://.*\\)?$")) + (when (looking-at "[[:space:]]") + (forward-word 1) + (backward-word 1)) + (current-column)))) + +(defun rust-rewind-to-beginning-of-current-level-expr () + (let ((current-level (rust-paren-level))) + (back-to-indentation) + (when (looking-at "->") + (rust-rewind-irrelevant) + (back-to-indentation)) + (while (> (rust-paren-level) current-level) + (backward-up-list) + (back-to-indentation)) + ;; When we're in the where clause, skip over it. First find out the start + ;; of the function and its paren level. + (let ((function-start nil) (function-level nil)) + (save-excursion + (rust-beginning-of-defun) + (back-to-indentation) + ;; Avoid using multiple-value-bind + (setq function-start (point) + function-level (rust-paren-level))) + ;; On a where clause + (when (or (rust-looking-at-where) + ;; or in one of the following lines, e.g. + ;; where A: Eq + ;; B: Hash <- on this line + (and (save-excursion + (rust-rewind-to-where function-start)) + (= current-level function-level))) + (goto-char function-start))))) + +(defun rust-align-to-method-chain () + (save-excursion + ;; for method-chain alignment to apply, we must be looking at + ;; another method call or field access or something like + ;; that. This avoids rather "eager" jumps in situations like: + ;; + ;; { + ;; something.foo() + ;; + ;; + ;; Without this check, we would wind up with the cursor under the + ;; `.`. In an older version, I had the inverse of the current + ;; check, where we checked for situations that should NOT indent, + ;; vs checking for the one situation where we SHOULD. It should be + ;; clear that this is more robust, but also I find it mildly less + ;; annoying to have to press tab again to align to a method chain + ;; than to have an over-eager indent in all other cases which must + ;; be undone via tab. + + (when (looking-at (concat "\s*\." rust-re-ident)) + (forward-line -1) + (end-of-line) + ;; Keep going up (looking for a line that could contain a method chain) + ;; while we're in a comment or on a blank line. Stop when the paren + ;; level changes. + (let ((level (rust-paren-level))) + (while (and (or (rust-in-str-or-cmnt) + ;; Only whitespace (or nothing) from the beginning to + ;; the end of the line. + (looking-back "^\s*" (line-beginning-position))) + (= (rust-paren-level) level)) + (forward-line -1) + (end-of-line))) + + (let + ;; skip-dot-identifier is used to position the point at the + ;; `.` when looking at something like + ;; + ;; foo.bar + ;; ^ ^ + ;; | | + ;; | position of point + ;; returned offset + ;; + ((skip-dot-identifier + (lambda () + (when (and (rust-looking-back-ident) + (save-excursion + (forward-thing 'symbol -1) + (= ?. (char-before)))) + (forward-thing 'symbol -1) + (backward-char) + (- (current-column) rust-indent-offset))))) + (cond + ;; foo.bar(...) + ((looking-back "[)?]" (1- (point))) + (backward-list 1) + (funcall skip-dot-identifier)) + + ;; foo.bar + (t (funcall skip-dot-identifier))))))) + +(defun rust-mode-indent-line () + (interactive) + (let ((indent + (save-excursion + (back-to-indentation) + ;; Point is now at beginning of current line + (let* ((level (rust-paren-level)) + (baseline + ;; Our "baseline" is one level out from the + ;; indentation of the expression containing the + ;; innermost enclosing opening bracket. That way + ;; if we are within a block that has a different + ;; indentation than this mode would give it, we + ;; still indent the inside of it correctly relative + ;; to the outside. + (if (= 0 level) + 0 + (or + (when rust-indent-method-chain + (rust-align-to-method-chain)) + (save-excursion + (rust-rewind-irrelevant) + (backward-up-list) + (rust-rewind-to-beginning-of-current-level-expr) + (+ (current-column) rust-indent-offset)))))) + (cond + ;; Indent inside a non-raw string only if the previous line + ;; ends with a backslash that is inside the same string + ((nth 3 (syntax-ppss)) + (let* + ((string-begin-pos (nth 8 (syntax-ppss))) + (end-of-prev-line-pos + (and (not (rust--same-line-p (point) (point-min))) + (line-end-position 0)))) + (when + (and + ;; If the string begins with an "r" it's a raw string and + ;; we should not change the indentation + (/= ?r (char-after string-begin-pos)) + + ;; If we're on the first line this will be nil and the + ;; rest does not apply + end-of-prev-line-pos + + ;; The end of the previous line needs to be inside the + ;; current string... + (> end-of-prev-line-pos string-begin-pos) + + ;; ...and end with a backslash + (= ?\\ (char-before end-of-prev-line-pos))) + + ;; Indent to the same level as the previous line, or the + ;; start of the string if the previous line starts the string + (if (rust--same-line-p end-of-prev-line-pos string-begin-pos) + ;; The previous line is the start of the string. + ;; If the backslash is the only character after the + ;; string beginning, indent to the next indent + ;; level. Otherwise align with the start of the string. + (if (> (- end-of-prev-line-pos string-begin-pos) 2) + (save-excursion + (goto-char (+ 1 string-begin-pos)) + (current-column)) + baseline) + + ;; The previous line is not the start of the string, so + ;; match its indentation. + (save-excursion + (goto-char end-of-prev-line-pos) + (back-to-indentation) + (current-column)))))) + + ;; A function return type is indented to the corresponding + ;; function arguments, if -to-arguments is selected. + ((and rust-indent-return-type-to-arguments + (looking-at "->")) + (save-excursion + (backward-list) + (or (rust-align-to-expr-after-brace) + (+ baseline rust-indent-offset)))) + + ;; A closing brace is 1 level unindented + ((looking-at "[]})]") (- baseline rust-indent-offset)) + + ;; Doc comments in /** style with leading * indent to line up the *s + ((and (nth 4 (syntax-ppss)) (looking-at "*")) + (+ 1 baseline)) + + ;; When the user chose not to indent the start of the where + ;; clause, put it on the baseline. + ((and (not rust-indent-where-clause) + (rust-looking-at-where)) + baseline) + + ;; If we're in any other token-tree / sexp, then: + (t + (or + ;; If we are inside a pair of braces, with something after the + ;; open brace on the same line and ending with a comma, treat + ;; it as fields and align them. + (when (> level 0) + (save-excursion + (rust-rewind-irrelevant) + (backward-up-list) + ;; Point is now at the beginning of the containing set of braces + (rust-align-to-expr-after-brace))) + + ;; When where-clauses are spread over multiple lines, clauses + ;; should be aligned on the type parameters. In this case we + ;; take care of the second and following clauses (the ones + ;; that don't start with "where ") + (save-excursion + ;; Find the start of the function, we'll use this to limit + ;; our search for "where ". + (let ((function-start nil) (function-level nil)) + (save-excursion + ;; If we're already at the start of a function, + ;; don't go back any farther. We can easily do + ;; this by moving to the end of the line first. + (end-of-line) + (rust-beginning-of-defun) + (back-to-indentation) + ;; Avoid using multiple-value-bind + (setq function-start (point) + function-level (rust-paren-level))) + ;; When we're not on a line starting with "where ", but + ;; still on a where-clause line, go to "where " + (when (and + (not (rust-looking-at-where)) + ;; We're looking at something like "F: ..." + (looking-at (concat rust-re-ident ":")) + ;; There is a "where " somewhere after the + ;; start of the function. + (rust-rewind-to-where function-start) + ;; Make sure we're not inside the function + ;; already (e.g. initializing a struct) by + ;; checking we are the same level. + (= function-level level)) + ;; skip over "where" + (forward-char 5) + ;; Unless "where" is at the end of the line + (if (eolp) + ;; in this case the type parameters bounds are just + ;; indented once + (+ baseline rust-indent-offset) + ;; otherwise, skip over whitespace, + (skip-chars-forward "[:space:]") + ;; get the column of the type parameter and use that + ;; as indentation offset + (current-column))))) + + (progn + (back-to-indentation) + ;; Point is now at the beginning of the current line + (if (or + ;; If this line begins with "else" or "{", stay on the + ;; baseline as well (we are continuing an expression, + ;; but the "else" or "{" should align with the beginning + ;; of the expression it's in.) + ;; Or, if this line starts a comment, stay on the + ;; baseline as well. + (looking-at "\\\\|{\\|/[/*]") + + ;; If this is the start of a top-level item, + ;; stay on the baseline. + (looking-at rust-top-item-beg-re) + + (save-excursion + (rust-rewind-irrelevant) + ;; Point is now at the end of the previous line + (or + ;; If we are at the start of the buffer, no + ;; indentation is needed, so stay at baseline... + (= (point) 1) + ;; ..or if the previous line ends with any of these: + ;; { ? : ( , ; [ } + ;; then we are at the beginning of an + ;; expression, so stay on the baseline... + (looking-back "[(,:;[{}]\\|[^|]|" (- (point) 2)) + ;; or if the previous line is the end of an + ;; attribute, stay at the baseline... + (progn (rust-rewind-to-beginning-of-current-level-expr) + (looking-at "#"))))) + baseline + + ;; Otherwise, we are continuing the same expression from + ;; the previous line, so add one additional indent level + (+ baseline rust-indent-offset)))))))))) + + (when indent + ;; If we're at the beginning of the line (before or at the current + ;; indentation), jump with the indentation change. Otherwise, save the + ;; excursion so that adding the indentations will leave us at the + ;; equivalent position within the line to where we were before. + (if (<= (current-column) (current-indentation)) + (indent-line-to indent) + (save-excursion (indent-line-to indent)))))) + +(defun rust--same-line-p (pos1 pos2) + "Return non-nil if POS1 and POS2 are on the same line." + (save-excursion (= (progn (goto-char pos1) (line-end-position)) + (progn (goto-char pos2) (line-end-position))))) + +;;; Font-locking definitions and helpers + +(defun rust-next-string-interpolation (limit) + "Search forward from point for next Rust interpolation marker before LIMIT. +Set point to the end of the occurrence found, and return match beginning +and end." + (catch 'match + (save-match-data + (save-excursion + (while (search-forward "{" limit t) + (if (eql (char-after (point)) ?{) + (forward-char) + (let ((start (match-beginning 0))) + ;; According to fmt_macros::Parser::next, an opening brace + ;; must be followed by an optional argument and/or format + ;; specifier, then a closing brace. A single closing brace + ;; without a corresponding unescaped opening brace is an + ;; error. We don't need to do anything special with + ;; arguments, specifiers, or errors, so we only search for + ;; the single closing brace. + (when (search-forward "}" limit t) + (throw 'match (list start (point))))))))))) + +(defun rust-string-interpolation-matcher (limit) + "Match next Rust interpolation marker before LIMIT and set match data if found. +Returns nil if not within a Rust string." + (when (rust-in-str) + (let ((match (rust-next-string-interpolation limit))) + (when match + (set-match-data match) + (goto-char (cadr match)) + match)))) + +(defun rust-syntax-class-before-point () + (when (> (point) 1) + (syntax-class (syntax-after (1- (point)))))) + +(defun rust-rewind-qualified-ident () + (while (rust-looking-back-ident) + (backward-sexp) + (when (save-excursion (rust-rewind-irrelevant) (rust-looking-back-str "::")) + (rust-rewind-irrelevant) + (backward-char 2) + (rust-rewind-irrelevant)))) + +(defun rust-rewind-type-param-list () + (cond + ((and (rust-looking-back-str ">") (equal 5 (rust-syntax-class-before-point))) + (backward-sexp) + (rust-rewind-irrelevant)) + + ;; We need to be able to back up past the Fn(args) -> RT form as well. If + ;; we're looking back at this, we want to end up just after "Fn". + ((member (char-before) '(?\] ?\) )) + (let* ((is-paren (rust-looking-back-str ")")) + (dest (save-excursion + (backward-sexp) + (rust-rewind-irrelevant) + (or + (when (rust-looking-back-str "->") + (backward-char 2) + (rust-rewind-irrelevant) + (when (rust-looking-back-str ")") + (backward-sexp) + (point))) + (and is-paren (point)))))) + (when dest + (goto-char dest)))))) + +(defun rust-rewind-to-decl-name () + "Return the point at the beginning of the name in a declaration. +I.e. if we are before an ident that is part of a declaration that +can have a where clause, rewind back to just before the name of +the subject of that where clause and return the new point. +Otherwise return nil." + (let* ((ident-pos (point)) + (newpos (save-excursion + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (cond + ((rust-looking-back-symbols + '("fn" "trait" "enum" "struct" "union" "impl" "type")) + ident-pos) + + ((equal 5 (rust-syntax-class-before-point)) + (backward-sexp) + (rust-rewind-to-decl-name)) + + ((looking-back "[:,'+=]" (1- (point))) + (backward-char) + (rust-rewind-to-decl-name)) + + ((rust-looking-back-str "->") + (backward-char 2) + (rust-rewind-to-decl-name)) + + ((rust-looking-back-ident) + (rust-rewind-qualified-ident) + (rust-rewind-to-decl-name)))))) + (when newpos (goto-char newpos)) + newpos)) + +(defun rust-is-in-expression-context (token) + "Return t if what comes right after the point is part of an +expression (as opposed to starting a type) by looking at what +comes before. Takes a symbol that roughly indicates what is +after the point. + +This function is used as part of `rust-is-lt-char-operator' as +part of angle bracket matching, and is not intended to be used +outside of this context." + (save-excursion + (let ((postchar (char-after))) + (rust-rewind-irrelevant) + ;; A type alias or ascription could have a type param list. Skip backwards past it. + (when (member token '(ambiguous-operator open-brace)) + (rust-rewind-type-param-list)) + (cond + + ;; Certain keywords always introduce expressions + ((rust-looking-back-symbols '("if" "while" "match" "return" "box" "in")) t) + + ;; "as" introduces a type + ((rust-looking-back-symbols '("as")) nil) + + ;; An open angle bracket never introduces expression context WITHIN the angle brackets + ((and (equal token 'open-brace) (equal postchar ?<)) nil) + + ;; An ident! followed by an open brace is a macro invocation. Consider + ;; it to be an expression. + ((and (equal token 'open-brace) (rust-looking-back-macro)) t) + + ;; In a brace context a "]" introduces an expression. + ((and (eq token 'open-brace) (rust-looking-back-str "]"))) + + ;; An identifier is right after an ending paren, bracket, angle bracket + ;; or curly brace. It's a type if the last sexp was a type. + ((and (equal token 'ident) (equal 5 (rust-syntax-class-before-point))) + (backward-sexp) + (rust-is-in-expression-context 'open-brace)) + + ;; If a "for" appears without a ; or { before it, it's part of an + ;; "impl X for y", so the y is a type. Otherwise it's + ;; introducing a loop, so the y is an expression + ((and (equal token 'ident) (rust-looking-back-symbols '("for"))) + (backward-sexp) + (rust-rewind-irrelevant) + (looking-back "[{;]" (1- (point)))) + + ((rust-looking-back-ident) + (rust-rewind-qualified-ident) + (rust-rewind-irrelevant) + (cond + ((equal token 'open-brace) + ;; We now know we have: + ;; ident [{([] + ;; where [{([] denotes either a {, ( or [. + ;; This character is bound as postchar. + (cond + ;; If postchar is a paren or square bracket, then if the + ;; brace is a type if the identifier is one + ((member postchar '(?\( ?\[ )) (rust-is-in-expression-context 'ident)) + + ;; If postchar is a curly brace, the brace can only be a type if + ;; ident2 is the name of an enum, struct or trait being declared. + ;; Note that if there is a -> before the ident then the ident would + ;; be a type but the { is not. + ((equal ?{ postchar) + (not (and (rust-rewind-to-decl-name) + (progn + (rust-rewind-irrelevant) + (rust-looking-back-symbols + '("enum" "struct" "union" "trait" "type")))))))) + + ((equal token 'ambiguous-operator) + (cond + ;; An ampersand after an ident has to be an operator rather + ;; than a & at the beginning of a ref type + ((equal postchar ?&) t) + + ;; A : followed by a type then an = introduces an + ;; expression (unless it is part of a where clause of a + ;; "type" declaration) + ((and (equal postchar ?=) + (looking-back "[^:]:" (- (point) 2)) + (not (save-excursion + (and (rust-rewind-to-decl-name) + (progn (rust-rewind-irrelevant) + (rust-looking-back-symbols '("type")))))))) + + ;; "let ident =" introduces an expression--and so does "const" and "mut" + ((and (equal postchar ?=) (rust-looking-back-symbols '("let" "const" "mut"))) t) + + ;; As a specific special case, see if this is the = in this situation: + ;; enum EnumName { Ident = + ;; In this case, this is a c-like enum and despite Ident + ;; representing a type, what comes after the = is an expression + ((and + (> (rust-paren-level) 0) + (save-excursion + (backward-up-list) + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (and + (rust-looking-back-ident) + (progn + (rust-rewind-qualified-ident) + (rust-rewind-irrelevant) + (rust-looking-back-str "enum"))))) + t) + + ;; Otherwise the ambiguous operator is a type if the identifier is a type + ((rust-is-in-expression-context 'ident) t))) + + ((equal token 'colon) + (cond + ;; If we see a ident: not inside any braces/parens, we're at top level. + ;; There are no allowed expressions after colons there, just types. + ((<= (rust-paren-level) 0) nil) + + ;; We see ident: inside a list + ((looking-back "[{,]" (1- (point))) + (backward-up-list) + + ;; If a : appears whose surrounding paren/brackets/braces are + ;; anything other than curly braces, it can't be a field + ;; initializer and must be denoting a type. + (when (looking-at "{") + (rust-rewind-irrelevant) + (rust-rewind-type-param-list) + (when (rust-looking-back-ident) + ;; We have a context that looks like this: + ;; ident2 { [maybe paren-balanced code ending in comma] ident1: + ;; the point is sitting just after ident2, and we trying to + ;; figure out if the colon introduces an expression or a type. + ;; The answer is that ident1 is a field name, and what comes + ;; after the colon is an expression, if ident2 is an + ;; expression. + (rust-rewind-qualified-ident) + (rust-is-in-expression-context 'ident)))) + + ;; Otherwise, if the ident: appeared with anything other than , or { + ;; before it, it can't be part of a struct initializer and therefore + ;; must be denoting a type. + (t nil))))) + + ;; An operator-like character after a string is indeed an operator + ((and (equal token 'ambiguous-operator) + (member (rust-syntax-class-before-point) '(5 7 15))) t) + + ;; A colon that has something other than an identifier before it is a + ;; type ascription + ((equal token 'colon) nil) + + ;; A :: introduces a type (or module, but not an expression in any case) + ((rust-looking-back-str "::") nil) + + ((rust-looking-back-str ":") + (backward-char) + (rust-is-in-expression-context 'colon)) + + ;; A -> introduces a type + ((rust-looking-back-str "->") nil) + + ;; If we are up against the beginning of a list, or after a comma inside + ;; of one, back up out of it and check what the list itself is + ((or + (equal 4 (rust-syntax-class-before-point)) + (rust-looking-back-str ",")) + (condition-case nil + (progn + (backward-up-list) + (rust-is-in-expression-context 'open-brace)) + (scan-error nil))) + + ;; A => introduces an expression + ((rust-looking-back-str "=>") t) + + ;; A == introduces an expression + ((rust-looking-back-str "==") t) + + ;; These operators can introduce expressions or types + ((looking-back "[-+=!?&*]" (1- (point))) + (backward-char) + (rust-is-in-expression-context 'ambiguous-operator)) + + ;; These operators always introduce expressions. (Note that if this + ;; regexp finds a < it must not be an angle bracket, or it'd + ;; have been caught in the syntax-class check above instead of this.) + ((looking-back rust-re-pre-expression-operators (1- (point))) t))))) + +(defun rust-is-lt-char-operator () + "Return non-nil if the `<' sign just after point is an operator. +Otherwise, if it is an opening angle bracket, then return nil." + (let ((case-fold-search nil)) + (save-excursion + (rust-rewind-irrelevant) + ;; We are now just after the character syntactically before the <. + (cond + + ;; If we are looking back at a < that is not an angle bracket (but not + ;; two of them) then this is the second < in a bit shift operator + ((and (rust-looking-back-str "<") + (not (equal 4 (rust-syntax-class-before-point))) + (not (rust-looking-back-str "<<")))) + + ;; On the other hand, if we are after a closing paren/brace/bracket it + ;; can only be an operator, not an angle bracket. Likewise, if we are + ;; after a string it's an operator. (The string case could actually be + ;; valid in rust for character literals.) + ((member (rust-syntax-class-before-point) '(5 7 15)) t) + + ;; If we are looking back at an operator, we know that we are at + ;; the beginning of an expression, and thus it has to be an angle + ;; bracket (starting a "::" construct.) + ((looking-back rust-re-pre-expression-operators (1- (point))) nil) + + ;; If we are looking back at a keyword, it's an angle bracket + ;; unless that keyword is "self", "true" or "false" + ((rust-looking-back-symbols rust-keywords) + (rust-looking-back-symbols '("self" "true" "false"))) + + ((rust-looking-back-str "?") + (rust-is-in-expression-context 'ambiguous-operator)) + + ;; If we're looking back at an identifier, this depends on whether + ;; the identifier is part of an expression or a type + ((rust-looking-back-ident) + (backward-sexp) + (or + ;; The special types can't take type param lists, so a < after one is + ;; always an operator + (looking-at rust-re-special-types) + + (rust-is-in-expression-context 'ident))) + + ;; Otherwise, assume it's an angle bracket + )))) + +(defun rust-electric-pair-inhibit-predicate-wrap (char) + "Prevent \"matching\" with a `>' when CHAR is the less-than operator. +This wraps the default defined by `electric-pair-inhibit-predicate'." + (or + (when (= ?< char) + (save-excursion + (backward-char) + (rust-is-lt-char-operator))) + (funcall (default-value 'electric-pair-inhibit-predicate) char))) + +(defun rust-electric-pair-skip-self (char) + "Skip CHAR instead of inserting a second closing character. +This is added to the default skips defined by `electric-pair-skip-self'." + (= ?> char)) + +(defun rust-ordinary-lt-gt-p () + "Test whether the `<' or `>' at point is an ordinary operator of some kind. + +This returns t if the `<' or `>' is an ordinary operator (like +less-than) or part of one (like `->'); and nil if the character +should be considered a paired angle bracket." + (cond + ;; If matching is turned off suppress all of them + ((not rust-match-angle-brackets) t) + + ;; This is a cheap check so we do it early. + ;; Don't treat the > in -> or => as an angle bracket + ((and (= (following-char) ?>) (memq (preceding-char) '(?- ?=))) t) + + ;; We don't take < or > in strings or comments to be angle brackets + ((rust-in-str-or-cmnt) t) + + ;; Inside a macro we don't really know the syntax. Any < or > may be an + ;; angle bracket or it may not. But we know that the other braces have + ;; to balance regardless of the < and >, so if we don't treat any < or > + ;; as angle brackets it won't mess up any paren balancing. + ((rust-in-macro) t) + + ((= (following-char) ?<) + (rust-is-lt-char-operator)) + + ;; Since rust-ordinary-lt-gt-p is called only when either < or > are at the point, + ;; we know that the following char must be > in the clauses below. + + ;; If we are at top level and not in any list, it can't be a closing + ;; angle bracket + ((>= 0 (rust-paren-level)) t) + + ;; Otherwise, treat the > as a closing angle bracket if it would + ;; match an opening one + ((save-excursion + (backward-up-list) + (/= (following-char) ?<))))) + +(defun rust-mode-syntactic-face-function (state) + "Return face that distinguishes doc and normal comments in given syntax STATE." + (if (nth 3 state) + 'font-lock-string-face + (save-excursion + (goto-char (nth 8 state)) + (if (looking-at "/\\([*][*!][^*!]\\|/[/!][^/!]\\)") + 'font-lock-doc-face + 'font-lock-comment-face)))) + +(eval-and-compile + (defconst rust--char-literal-rx + (rx (seq + (group "'") + (or + (seq + "\\" + (or + (: "u{" (** 1 6 xdigit) "}") + (: "x" (= 2 xdigit)) + (any "'nrt0\"\\"))) + (not (any "'\\"))) + (group "'"))) + "A regular expression matching a character literal.")) + +(defun rust--syntax-propertize-raw-string (str-start end) + "A helper for rust-syntax-propertize. + +This will apply the appropriate string syntax to the character +from the STR-START up to the end of the raw string, or to END, +whichever comes first." + (when (save-excursion + (goto-char str-start) + (looking-at "r\\(#*\\)\\(\"\\)")) + ;; In a raw string, so try to find the end. + (let ((hashes (match-string 1))) + ;; Match \ characters at the end of the string to suppress + ;; their normal character-quote syntax. + (when (re-search-forward (concat "\\(\\\\*\\)\\(\"" hashes "\\)") end t) + (put-text-property (match-beginning 1) (match-end 1) + 'syntax-table (string-to-syntax "_")) + (put-text-property (1- (match-end 2)) (match-end 2) + 'syntax-table (string-to-syntax "|")) + (goto-char (match-end 0)))))) + +;;; Syntax Propertize + +(defun rust-syntax-propertize (start end) + "A `syntax-propertize-function' to apply properties from START to END." + ;; Cache all macro scopes as an optimization. See issue #208 + (let ((rust-macro-scopes (rust-macro-scope start end))) + (goto-char start) + (let ((str-start (rust-in-str-or-cmnt))) + (when str-start + (rust--syntax-propertize-raw-string str-start end))) + (funcall + (syntax-propertize-rules + ;; Character literals. + (rust--char-literal-rx (1 "\"") (2 "\"")) + ;; Raw strings. + ("\\(r\\)#*\"" + (0 (ignore + (goto-char (match-end 0)) + (unless (save-excursion (nth 8 (syntax-ppss (match-beginning 0)))) + (put-text-property (match-beginning 1) (match-end 1) + 'syntax-table (string-to-syntax "|")) + (rust--syntax-propertize-raw-string (match-beginning 0) end))))) + ("[<>]" + (0 (ignore + (when (save-match-data + (save-excursion + (goto-char (match-beginning 0)) + (rust-ordinary-lt-gt-p))) + (put-text-property (match-beginning 0) (match-end 0) + 'syntax-table (string-to-syntax ".")) + (goto-char (match-end 0))))))) + (point) end))) + +(defun rust-fill-prefix-for-comment-start (line-start) + "Determine what to use for `fill-prefix' based on the text at LINE-START." + (let ((result + ;; Replace /* with same number of spaces + (replace-regexp-in-string + "\\(?:/\\*+?\\)[!*]?" + (lambda (s) + ;; We want the * to line up with the first * of the + ;; comment start + (let ((offset (if (eq t + (compare-strings "/*" nil nil + s + (- (length s) 2) + (length s))) + 1 2))) + (concat (make-string (- (length s) offset) + ?\x20) "*"))) + line-start))) + ;; Make sure we've got at least one space at the end + (if (not (= (aref result (- (length result) 1)) ?\x20)) + (setq result (concat result " "))) + result)) + +(defun rust-in-comment-paragraph (body) + ;; We might move the point to fill the next comment, but we don't want it + ;; seeming to jump around on the user + (save-excursion + ;; If we're outside of a comment, with only whitespace and then a comment + ;; in front, jump to the comment and prepare to fill it. + (when (not (nth 4 (syntax-ppss))) + (beginning-of-line) + (when (looking-at (concat "[[:space:]\n]*" comment-start-skip)) + (goto-char (match-end 0)))) + + ;; We need this when we're moving the point around and then checking syntax + ;; while doing paragraph fills, because the cache it uses isn't always + ;; invalidated during this. + (syntax-ppss-flush-cache 1) + ;; If we're at the beginning of a comment paragraph with nothing but + ;; whitespace til the next line, jump to the next line so that we use the + ;; existing prefix to figure out what the new prefix should be, rather than + ;; inferring it from the comment start. + (let ((next-bol (line-beginning-position 2))) + (while (save-excursion + (end-of-line) + (syntax-ppss-flush-cache 1) + (and (nth 4 (syntax-ppss)) + (save-excursion + (beginning-of-line) + (looking-at paragraph-start)) + (looking-at "[[:space:]]*$") + (nth 4 (syntax-ppss next-bol)))) + (goto-char next-bol))) + + (syntax-ppss-flush-cache 1) + ;; If we're on the last line of a multiline-style comment that started + ;; above, back up one line so we don't mistake the * of the */ that ends + ;; the comment for a prefix. + (when (save-excursion + (and (nth 4 (syntax-ppss (line-beginning-position 1))) + (looking-at "[[:space:]]*\\*/"))) + (goto-char (line-end-position 0))) + (funcall body))) + +(defun rust-with-comment-fill-prefix (body) + (let* + ((line-string (buffer-substring-no-properties + (line-beginning-position) (line-end-position))) + (line-comment-start + (when (nth 4 (syntax-ppss)) + (cond + ;; If we're inside the comment and see a * prefix, use it + ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)" + line-string) + (match-string 1 line-string)) + ;; If we're at the start of a comment, figure out what prefix + ;; to use for the subsequent lines after it + ((string-match (concat "[[:space:]]*" comment-start-skip) line-string) + (rust-fill-prefix-for-comment-start + (match-string 0 line-string)))))) + (fill-prefix + (or line-comment-start + fill-prefix))) + (funcall body))) + +(defun rust-find-fill-prefix () + (rust-in-comment-paragraph + (lambda () + (rust-with-comment-fill-prefix + (lambda () + fill-prefix))))) + +(defun rust-fill-paragraph (&rest args) + "Special wrapping for `fill-paragraph'. +This handles multi-line comments with a * prefix on each line." + (rust-in-comment-paragraph + (lambda () + (rust-with-comment-fill-prefix + (lambda () + (let + ((fill-paragraph-function + (if (not (eq fill-paragraph-function #'rust-fill-paragraph)) + fill-paragraph-function)) + (fill-paragraph-handle-comment t)) + (apply #'fill-paragraph args) + t)))))) + +(defun rust-do-auto-fill (&rest args) + "Special wrapping for `do-auto-fill'. +This handles multi-line comments with a * prefix on each line." + (rust-with-comment-fill-prefix + (lambda () + (apply #'do-auto-fill args) + t))) + +(defun rust-fill-forward-paragraph (arg) + ;; This is to work around some funny behavior when a paragraph separator is + ;; at the very top of the file and there is a fill prefix. + (let ((fill-prefix nil)) (forward-paragraph arg))) + +(defun rust-comment-indent-new-line (&optional arg) + (rust-with-comment-fill-prefix + (lambda () (comment-indent-new-line arg)))) + +;;; Defun Motions + +(defun rust-beginning-of-defun (&optional arg) + "Move backward to the beginning of the current defun. + +With ARG, move backward multiple defuns. Negative ARG means +move forward. + +This is written mainly to be used as `beginning-of-defun-function' for Rust. +Don't move to the beginning of the line. `beginning-of-defun', +which calls this, does that afterwards." + (interactive "p") + (let* ((arg (or arg 1)) + (magnitude (abs arg)) + (sign (if (< arg 0) -1 1))) + ;; If moving forward, don't find the defun we might currently be + ;; on. + (when (< sign 0) + (end-of-line)) + (catch 'done + (dotimes (_ magnitude) + ;; Search until we find a match that is not in a string or comment. + (while (if (re-search-backward (concat "^\\(" rust-top-item-beg-re "\\)") + nil 'move sign) + (rust-in-str-or-cmnt) + ;; Did not find it. + (throw 'done nil))))) + t)) + +(defun rust-end-of-defun () + "Move forward to the next end of defun. + +With argument, do it that many times. +Negative argument -N means move back to Nth preceding end of defun. + +Assume that this is called after `beginning-of-defun'. So point is +at the beginning of the defun body. + +This is written mainly to be used as `end-of-defun-function' for Rust." + (interactive) + ;; Find the opening brace + (if (re-search-forward "[{]" nil t) + (progn + (goto-char (match-beginning 0)) + ;; Go to the closing brace + (condition-case nil + (forward-sexp) + (scan-error + ;; The parentheses are unbalanced; instead of being unable + ;; to fontify, just jump to the end of the buffer + (goto-char (point-max))))) + ;; There is no opening brace, so consider the whole buffer to be one "defun" + (goto-char (point-max)))) + +;;; _ + +(defun rust-mode-reload () + (interactive) + (unload-feature 'rust-mode) + (require 'rust-mode) + (rust-mode)) + +(provide 'rust-mode) +(require 'rust-utils) + +;;; rust-mode.el ends here diff --git a/.emacs.d/lisp/rust-playpen.el b/.emacs.d/lisp/rust-playpen.el new file mode 100644 index 0000000..1a9e583 --- /dev/null +++ b/.emacs.d/lisp/rust-playpen.el @@ -0,0 +1,59 @@ +;;; rust-playpen.el --- Support for the Rust Playground -*- lexical-binding:t -*- +;;; Commentary: + +;; This library implements support for the Rust Playground, +;; which is hosted at https://play.rust-lang.org and developed +;; at https://github.com/integer32llc/rust-playground. + +;;; Code: + +(eval-when-compile (require 'url)) + +;;; Options + +(defcustom rust-playpen-url-format "https://play.rust-lang.org/?code=%s" + "Format string to use when submitting code to the playpen." + :type 'string + :group 'rust-mode) + +(defcustom rust-shortener-url-format "https://is.gd/create.php?format=simple&url=%s" + "Format string to use for creating the shortened link of a playpen submission." + :type 'string + :group 'rust-mode) + +;;; Commands + +(defun rust-playpen-region (begin end) + "Create a shareable URL for the region from BEGIN to END on the Rust playpen." + (interactive "r") + (let* ((data (buffer-substring begin end)) + (escaped-data (url-hexify-string data)) + (escaped-playpen-url (url-hexify-string + (format rust-playpen-url-format escaped-data)))) + (if (> (length escaped-playpen-url) 5000) + (error "encoded playpen data exceeds 5000 character limit (length %s)" + (length escaped-playpen-url)) + (let ((shortener-url (format rust-shortener-url-format escaped-playpen-url)) + (url-request-method "POST")) + (url-retrieve shortener-url + (lambda (state) + ;; filter out the headers etc. included at the + ;; start of the buffer: the relevant text + ;; (shortened url or error message) is exactly + ;; the last line. + (goto-char (point-max)) + (let ((last-line (thing-at-point 'line t)) + (err (plist-get state :error))) + (kill-buffer) + (if err + (error "failed to shorten playpen url: %s" last-line) + (message "%s" last-line))))))))) + +(defun rust-playpen-buffer () + "Create a shareable URL for the contents of the buffer on the Rust playpen." + (interactive) + (rust-playpen-region (point-min) (point-max))) + +;;; _ +(provide 'rust-playpen) +;;; rust-playpen.el ends here diff --git a/.emacs.d/lisp/rust-rustfmt.el b/.emacs.d/lisp/rust-rustfmt.el new file mode 100644 index 0000000..bb88647 --- /dev/null +++ b/.emacs.d/lisp/rust-rustfmt.el @@ -0,0 +1,372 @@ +;;; rust-rustfmt.el --- Support for rustfmt -*- lexical-binding:t -*- +;;; Commentary: + +;; This library implements support for "rustfmt", a tool for +;; formatting Rust code according to style guidelines. + +;;; Code: +;;; Options + +(defcustom rust-format-on-save nil + "Format future rust buffers before saving using rustfmt." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-format-show-buffer t + "Show *rustfmt* buffer if formatting detected problems." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-format-goto-problem t + "Jump to location of first detected problem when formatting buffer." + :type 'boolean + :safe #'booleanp + :group 'rust-mode) + +(defcustom rust-rustfmt-bin "rustfmt" + "Path to rustfmt executable." + :type 'string + :group 'rust-mode) + +(defcustom rust-rustfmt-switches '("--edition" "2018") + "Arguments to pass when invoking the `rustfmt' executable." + :type '(repeat string) + :group 'rust-mode) + +;;; _ + +(defconst rust-rustfmt-buffername "*rustfmt*") + +(defun rust--format-call (buf) + "Format BUF using rustfmt." + (with-current-buffer (get-buffer-create rust-rustfmt-buffername) + (view-mode +1) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert-buffer-substring buf) + (let* ((tmpf (make-temp-file "rustfmt")) + (ret (apply #'call-process-region + (point-min) + (point-max) + rust-rustfmt-bin + t + `(t ,tmpf) + nil + rust-rustfmt-switches))) + (unwind-protect + (cond + ((zerop ret) + (if (not (string= (buffer-string) + (with-current-buffer buf (buffer-string)))) + ;; replace-buffer-contents was in emacs 26.1, but it + ;; was broken for non-ASCII strings, so we need 26.2. + (if (and (fboundp 'replace-buffer-contents) + (version<= "26.2" emacs-version)) + (with-current-buffer buf + (replace-buffer-contents rust-rustfmt-buffername)) + (copy-to-buffer buf (point-min) (point-max)))) + (kill-buffer)) + ((= ret 3) + (if (not (string= (buffer-string) + (with-current-buffer buf (buffer-string)))) + (copy-to-buffer buf (point-min) (point-max))) + (erase-buffer) + (insert-file-contents tmpf) + (rust--format-fix-rustfmt-buffer (buffer-name buf)) + (error "Rustfmt could not format some lines, see %s buffer for details" + rust-rustfmt-buffername)) + (t + (erase-buffer) + (insert-file-contents tmpf) + (rust--format-fix-rustfmt-buffer (buffer-name buf)) + (error "Rustfmt failed, see %s buffer for details" + rust-rustfmt-buffername)))) + (delete-file tmpf))))) + +;; Since we run rustfmt through stdin we get markers in the +;; output. This replaces them with the buffer name instead. +(defun rust--format-fix-rustfmt-buffer (buffer-name) + (save-match-data + (with-current-buffer (get-buffer rust-rustfmt-buffername) + (let ((inhibit-read-only t) + (fixed (format "--> %s:" buffer-name))) + (goto-char (point-min)) + (while (re-search-forward "--> \\(?:\\|stdin\\):" nil t) + (replace-match fixed)))))) + +;; If rust-mode has been configured to navigate to source of the error +;; or display it, do so -- and return true. Otherwise return nil to +;; indicate nothing was done. +(defun rust--format-error-handler () + (let ((ok nil)) + (when rust-format-show-buffer + (display-buffer (get-buffer rust-rustfmt-buffername)) + (setq ok t)) + (when rust-format-goto-problem + (rust-goto-format-problem) + (setq ok t)) + ok)) + +(defun rust-goto-format-problem () + "Jumps to problem reported by rustfmt, if any. + +In case of multiple problems cycles through them. Displays the +rustfmt complain in the echo area." + (interactive) + ;; This uses position in *rustfmt* buffer to know which is the next + ;; error to jump to, and source: line in the buffer to figure which + ;; buffer it is from. + (let ((rustfmt (get-buffer rust-rustfmt-buffername))) + (if (not rustfmt) + (message "No %s, no problems." rust-rustfmt-buffername) + (let ((target-buffer (with-current-buffer rustfmt + (save-excursion + (goto-char (point-min)) + (when (re-search-forward "--> \\([^:]+\\):" nil t) + (match-string 1))))) + (target-point (with-current-buffer rustfmt + ;; No save-excursion, this is how we cycle through! + (let ((regex "--> [^:]+:\\([0-9]+\\):\\([0-9]+\\)")) + (when (or (re-search-forward regex nil t) + (progn (goto-char (point-min)) + (re-search-forward regex nil t))) + (cons (string-to-number (match-string 1)) + (string-to-number (match-string 2))))))) + (target-problem (with-current-buffer rustfmt + (save-excursion + (when (re-search-backward "^error:.+\n" nil t) + (forward-char (length "error: ")) + (let ((p0 (point))) + (if (re-search-forward "\nerror:.+\n" nil t) + (buffer-substring p0 (point)) + (buffer-substring p0 (point-max))))))))) + (when (and target-buffer (get-buffer target-buffer) target-point) + (switch-to-buffer target-buffer) + (goto-char (point-min)) + (forward-line (1- (car target-point))) + (forward-char (1- (cdr target-point)))) + (message target-problem))))) + +(defconst rust--format-word "\ +\\b\\(else\\|enum\\|fn\\|for\\|if\\|let\\|loop\\|\ +match\\|struct\\|union\\|unsafe\\|while\\)\\b") +(defconst rust--format-line "\\(\n\\)") + +;; Counts number of matches of regex beginning up to max-beginning, +;; leaving the point at the beginning of the last match. +(defun rust--format-count (regex max-beginning) + (let ((count 0) + save-point + beginning) + (while (and (< (point) max-beginning) + (re-search-forward regex max-beginning t)) + (setq count (1+ count)) + (setq beginning (match-beginning 1))) + ;; try one more in case max-beginning lies in the middle of a match + (setq save-point (point)) + (when (re-search-forward regex nil t) + (let ((try-beginning (match-beginning 1))) + (if (> try-beginning max-beginning) + (goto-char save-point) + (setq count (1+ count)) + (setq beginning try-beginning)))) + (when beginning (goto-char beginning)) + count)) + +;; Gets list describing pos or (point). +;; The list contains: +;; 1. the number of matches of rust--format-word, +;; 2. the number of matches of rust--format-line after that, +;; 3. the number of columns after that. +(defun rust--format-get-loc (buffer &optional pos) + (with-current-buffer buffer + (save-excursion + (let ((pos (or pos (point))) + words lines columns) + (goto-char (point-min)) + (setq words (rust--format-count rust--format-word pos)) + (setq lines (rust--format-count rust--format-line pos)) + (if (> lines 0) + (if (= (point) pos) + (setq columns -1) + (forward-char 1) + (goto-char pos) + (setq columns (current-column))) + (let ((initial-column (current-column))) + (goto-char pos) + (setq columns (- (current-column) initial-column)))) + (list words lines columns))))) + +;; Moves the point forward by count matches of regex up to max-pos, +;; and returns new max-pos making sure final position does not include another match. +(defun rust--format-forward (regex count max-pos) + (when (< (point) max-pos) + (let ((beginning (point))) + (while (> count 0) + (setq count (1- count)) + (re-search-forward regex nil t) + (setq beginning (match-beginning 1))) + (when (re-search-forward regex nil t) + (setq max-pos (min max-pos (match-beginning 1)))) + (goto-char beginning))) + max-pos) + +;; Gets the position from a location list obtained using rust--format-get-loc. +(defun rust--format-get-pos (buffer loc) + (with-current-buffer buffer + (save-excursion + (goto-char (point-min)) + (let ((max-pos (point-max)) + (words (pop loc)) + (lines (pop loc)) + (columns (pop loc))) + (setq max-pos (rust--format-forward rust--format-word words max-pos)) + (setq max-pos (rust--format-forward rust--format-line lines max-pos)) + (when (> lines 0) (forward-char)) + (let ((initial-column (current-column)) + (save-point (point))) + (move-end-of-line nil) + (when (> (current-column) (+ initial-column columns)) + (goto-char save-point) + (forward-char columns))) + (min (point) max-pos))))) + +(defun rust-format-diff-buffer () + "Show diff to current buffer from rustfmt. + +Return the created process." + (interactive) + (unless (executable-find rust-rustfmt-bin) + (error "Could not locate executable %S" rust-rustfmt-bin)) + (let* ((buffer + (with-current-buffer + (get-buffer-create "*rustfmt-diff*") + (let ((inhibit-read-only t)) + (erase-buffer)) + (current-buffer))) + (proc + (apply #'start-process + "rustfmt-diff" + buffer + rust-rustfmt-bin + "--check" + (cons (buffer-file-name) + rust-rustfmt-switches)))) + (set-process-sentinel proc #'rust-format-diff-buffer-sentinel) + proc)) + +(defun rust-format-diff-buffer-sentinel (process _e) + (when (eq 'exit (process-status process)) + (if (> (process-exit-status process) 0) + (with-current-buffer "*rustfmt-diff*" + (let ((inhibit-read-only t)) + (diff-mode)) + (pop-to-buffer (current-buffer))) + (message "rustfmt check passed.")))) + +(defun rust--format-buffer-using-replace-buffer-contents () + (condition-case err + (progn + (rust--format-call (current-buffer)) + (message "Formatted buffer with rustfmt.")) + (error + (or (rust--format-error-handler) + (signal (car err) (cdr err)))))) + +(defun rust--format-buffer-saving-position-manually () + (let* ((current (current-buffer)) + (base (or (buffer-base-buffer current) current)) + buffer-loc + window-loc) + (dolist (buffer (buffer-list)) + (when (or (eq buffer base) + (eq (buffer-base-buffer buffer) base)) + (push (list buffer + (rust--format-get-loc buffer nil)) + buffer-loc))) + (dolist (frame (frame-list)) + (dolist (window (window-list frame)) + (let ((buffer (window-buffer window))) + (when (or (eq buffer base) + (eq (buffer-base-buffer buffer) base)) + (let ((start (window-start window)) + (point (window-point window))) + (push (list window + (rust--format-get-loc buffer start) + (rust--format-get-loc buffer point)) + window-loc)))))) + (condition-case err + (unwind-protect + ;; save and restore window start position + ;; after reformatting + ;; to avoid the disturbing scrolling + (let ((w-start (window-start))) + (rust--format-call (current-buffer)) + (set-window-start (selected-window) w-start) + (message "Formatted buffer with rustfmt.")) + (dolist (loc buffer-loc) + (let* ((buffer (pop loc)) + (pos (rust--format-get-pos buffer (pop loc)))) + (with-current-buffer buffer + (goto-char pos)))) + (dolist (loc window-loc) + (let* ((window (pop loc)) + (buffer (window-buffer window)) + (start (rust--format-get-pos buffer (pop loc))) + (pos (rust--format-get-pos buffer (pop loc)))) + (unless (eq buffer current) + (set-window-start window start)) + (set-window-point window pos)))) + (error + (or (rust--format-error-handler) + (signal (car err) (cdr err))))))) + +(defun rust-format-buffer () + "Format the current buffer using rustfmt." + (interactive) + (unless (executable-find rust-rustfmt-bin) + (error "Could not locate executable \"%s\"" rust-rustfmt-bin)) + ;; If emacs version >= 26.2, we can use replace-buffer-contents to + ;; preserve location and markers in buffer, otherwise we can try to + ;; save locations as best we can, though we still lose markers. + (save-excursion + (if (version<= "26.2" emacs-version) + (rust--format-buffer-using-replace-buffer-contents) + (rust--format-buffer-saving-position-manually)))) + +(defun rust-enable-format-on-save () + "Enable formatting using rustfmt when saving buffer." + (interactive) + (setq-local rust-format-on-save t)) + +(defun rust-disable-format-on-save () + "Disable formatting using rustfmt when saving buffer." + (interactive) + (setq-local rust-format-on-save nil)) + +;;; Hooks + +(defun rust-before-save-method () + (when rust-format-on-save + (condition-case e + (rust-format-buffer) + (message (format "rust-before-save-hook: %S %S" + (car e) + (cdr e)))))) + +(defun rust-after-save-method () + (when rust-format-on-save + (if (not (executable-find rust-rustfmt-bin)) + (error "Could not locate executable \"%s\"" rust-rustfmt-bin) + (when (get-buffer rust-rustfmt-buffername) + ;; KLDUGE: re-run the error handlers -- otherwise message area + ;; would show "Wrote ..." instead of the error description. + (or (rust--format-error-handler) + (message "rustfmt detected problems, see %s for more." + rust-rustfmt-buffername)))))) + +;;; _ +(provide 'rust-rustfmt) +;;; rust-rustfmt.el ends here diff --git a/.emacs.d/lisp/rust-utils.el b/.emacs.d/lisp/rust-utils.el new file mode 100644 index 0000000..41f1ba8 --- /dev/null +++ b/.emacs.d/lisp/rust-utils.el @@ -0,0 +1,91 @@ +;;; rust-utils.el --- Various Rust utilities -*- lexical-binding:t -*- +;;; Commentary: + +;; This library implements various utilities for dealing with Rust +;; code. + +;;; Code: + +(require 'thingatpt) + +(require 'rust-mode) ; for `rust-in-str' and `rust-looking-back-str' + +;;; Promote module + +(defun rust-promote-module-into-dir () + "Promote the module file visited by the current buffer into its own directory. + +For example, if the current buffer is visiting the file `foo.rs', +then this function creates the directory `foo' and renames the +file to `foo/mod.rs'. The current buffer will be updated to +visit the new file." + (interactive) + (let ((filename (buffer-file-name))) + (if (not filename) + (message "Buffer is not visiting a file.") + (if (string-equal (file-name-nondirectory filename) "mod.rs") + (message "Won't promote a module file already named mod.rs.") + (let* ((basename (file-name-sans-extension + (file-name-nondirectory filename))) + (mod-dir (file-name-as-directory + (concat (file-name-directory filename) basename))) + (new-name (concat mod-dir "mod.rs"))) + (mkdir mod-dir t) + (rename-file filename new-name 1) + (set-visited-file-name new-name)))))) + +;;; dbg! macro + +(defun rust-insert-dbg () + "Insert the dbg! macro." + (cond ((region-active-p) + (when (< (mark) (point)) + (exchange-point-and-mark)) + (let ((old-point (point))) + (insert-parentheses) + (goto-char old-point))) + (t + (when (rust-in-str) + (up-list -1 t t)) + (insert "(") + (forward-sexp) + (insert ")") + (backward-sexp))) + (insert "dbg!")) + +;;;###autoload +(defun rust-dbg-wrap-or-unwrap () + "Either remove or add the dbg! macro." + (interactive) + (save-excursion + (if (region-active-p) + (rust-insert-dbg) + + (let ((beginning-of-symbol (ignore-errors (beginning-of-thing 'symbol)))) + (when beginning-of-symbol + (goto-char beginning-of-symbol))) + + (let ((dbg-point (save-excursion + (or (and (looking-at-p "dbg!") (+ 4 (point))) + (ignore-errors + (while (not (rust-looking-back-str "dbg!")) + (backward-up-list)) + (point)))))) + (cond (dbg-point + (goto-char dbg-point) + (delete-char -4) + (delete-pair)) + (t (rust-insert-dbg))))))) + +(defun rust-toggle-mutability () + "Toggles the mutability of the variable defined on the current line" + (interactive) + (save-excursion + (back-to-indentation) + (forward-word) + (if (string= " mut" (buffer-substring (point) (+ (point) 4))) + (delete-region (point) (+ (point) 4)) + (insert " mut")))) +;;; _ +(provide 'rust-utils) +;;; rust-utils.el ends here diff --git a/.emacs.d/lisp/yaml-mode.el b/.emacs.d/lisp/yaml-mode.el index 666f128..07ac224 100644 --- a/.emacs.d/lisp/yaml-mode.el +++ b/.emacs.d/lisp/yaml-mode.el @@ -306,8 +306,8 @@ artificially limited to the value of (if (eolp) (goto-char (1+ (point)))) (unless (or (eobp) (>= (point) bound)) (let ((begin (point)) - (end (min (1+ (point-at-eol)) bound))) - (goto-char (point-at-bol)) + (end (min (1+ (line-end-position)) bound))) + (goto-char (line-beginning-position)) (while (and (looking-at yaml-blank-line-re) (not (bobp))) (forward-line -1)) @@ -426,7 +426,7 @@ margin." otherwise do nothing." (interactive) (save-excursion - (goto-char (point-at-bol)) + (goto-char (line-beginning-position)) (while (and (looking-at-p yaml-blank-line-re) (not (bobp))) (forward-line -1)) (let ((nlines yaml-block-literal-search-lines) -- cgit v1.3