diff --git a/rust-mode-tests.el b/rust-mode-tests.el index 4b32e94d..007d116e 100644 --- a/rust-mode-tests.el +++ b/rust-mode-tests.el @@ -284,8 +284,8 @@ very very very long string */" )) -(defun test-indent (indented) - (let ((deindented (replace-regexp-in-string "^[[:blank:]]*" " " indented))) +(defun test-indent (indented &optional deindented) + (let ((deindented (or deindented (replace-regexp-in-string "^[[:blank:]]*" " " indented)))) (rust-test-manip-code deindented 1 @@ -1207,3 +1207,122 @@ impl Foo for Bar { } " )) + +(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\"")) diff --git a/rust-mode.el b/rust-mode.el index 53d2e5ea..c82a4846 100644 --- a/rust-mode.el +++ b/rust-mode.el @@ -117,7 +117,7 @@ ;; be undone via tab. (when (looking-at (concat "\s*\." rust-re-ident)) - (previous-logical-line) + (forward-line -1) (end-of-line) (let @@ -164,10 +164,58 @@ (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 the previous line + ;; ends with a backslash that is is inside the same string + ((nth 3 (syntax-ppss)) + (let* + ((string-begin-pos (nth 8 (syntax-ppss))) + (end-of-prev-line-pos (when (> (line-number-at-pos) 1) + (save-excursion + (forward-line -1) + (end-of-line) + (point))))) + (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 (= (line-number-at-pos end-of-prev-line-pos) (line-number-at-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 ((looking-at "->") (save-excursion @@ -223,13 +271,14 @@ ;; so add one additional indent level (+ baseline rust-indent-offset)))))))))) - ;; 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))))) + (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)))))) ;; Font-locking definitions and helpers