Skip to content

Commit b10ad41

Browse files
authored
Merge pull request #220 from Aankhen/highlight-string-interpolation
Highlight interpolation in arguments to print! &c.
2 parents afeddec + 89320ad commit b10ad41

File tree

2 files changed

+302
-0
lines changed

2 files changed

+302
-0
lines changed

rust-mode-tests.el

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,6 +2074,226 @@ fn main() {
20742074
'("r#\"\"\"#" font-lock-string-face
20752075
"'q'" font-lock-string-face)))
20762076

2077+
(ert-deftest rust-macro-font-lock ()
2078+
(rust-test-font-lock
2079+
"foo!\(\);"
2080+
'("foo!" font-lock-preprocessor-face))
2081+
(rust-test-font-lock
2082+
"foo!{};"
2083+
'("foo!" font-lock-preprocessor-face))
2084+
(rust-test-font-lock
2085+
"foo![];"
2086+
'("foo!" font-lock-preprocessor-face)))
2087+
2088+
(ert-deftest rust-string-interpolation-matcher-works ()
2089+
(dolist (test '(("print!\(\"\"\)" 9 11 nil)
2090+
("print!\(\"abcd\"\)" 9 15 nil)
2091+
("print!\(\"abcd {{}}\"\);" 9 19 nil)
2092+
("print!\(\"abcd {{\"\);" 9 18 nil)
2093+
("print!\(\"abcd {}\"\);" 9 18 ((14 16)))
2094+
("print!\(\"abcd {{{}\"\);" 9 20 ((16 18)))
2095+
("print!\(\"abcd {}{{\"\);" 9 20 ((14 16)))
2096+
("print!\(\"abcd {} {{\"\);" 9 21 ((14 16)))
2097+
("print!\(\"abcd {}}}\"\);" 9 20 ((14 16)))
2098+
("print!\(\"abcd {{{}}}\"\);" 9 20 ((16 18)))
2099+
("print!\(\"abcd {0}\"\);" 9 18 ((14 17)))
2100+
("print!\(\"abcd {0} efgh\"\);" 9 23 ((14 17)))
2101+
("print!\(\"{1} abcd {0} efgh\"\);" 9 27 ((9 12) (18 21)))
2102+
("print!\(\"{{{1} abcd }} {0}}} {{efgh}}\"\);" 9 33 ((11 14) (23 26)))))
2103+
(destructuring-bind (text cursor limit matches) test
2104+
(with-temp-buffer
2105+
;; make sure we have a clean slate
2106+
(save-match-data
2107+
(set-match-data nil)
2108+
(insert text)
2109+
(goto-char cursor)
2110+
(if (null matches)
2111+
(should (equal (progn
2112+
(rust-string-interpolation-matcher limit)
2113+
(match-data))
2114+
nil))
2115+
(dolist (pair matches)
2116+
(rust-string-interpolation-matcher limit)
2117+
(should (equal (match-beginning 0) (car pair)))
2118+
(should (equal (match-end 0) (cadr pair))))))))))
2119+
2120+
(ert-deftest rust-formatting-macro-font-lock ()
2121+
;; test that the block delimiters aren't highlighted and the comment
2122+
;; is ignored
2123+
(rust-test-font-lock
2124+
"print!(\"\"); { /* print!(\"\"); */ }"
2125+
'("print!" rust-builtin-formatting-macro-face
2126+
"\"\"" font-lock-string-face
2127+
"/* " font-lock-comment-delimiter-face
2128+
"print!(\"\"); */" font-lock-comment-face))
2129+
;; other delimiters
2130+
(rust-test-font-lock
2131+
"print!{\"\"}; { /* no-op */ }"
2132+
'("print!" rust-builtin-formatting-macro-face
2133+
"\"\"" font-lock-string-face
2134+
"/* " font-lock-comment-delimiter-face
2135+
"no-op */" font-lock-comment-face))
2136+
;; other delimiters
2137+
(rust-test-font-lock
2138+
"print![\"\"]; { /* no-op */ }"
2139+
'("print!" rust-builtin-formatting-macro-face
2140+
"\"\"" font-lock-string-face
2141+
"/* " font-lock-comment-delimiter-face
2142+
"no-op */" font-lock-comment-face))
2143+
;; no interpolation
2144+
(rust-test-font-lock
2145+
"print!(\"abcd\"); { /* no-op */ }"
2146+
'("print!" rust-builtin-formatting-macro-face
2147+
"\"abcd\"" font-lock-string-face
2148+
"/* " font-lock-comment-delimiter-face
2149+
"no-op */" font-lock-comment-face))
2150+
;; only interpolation
2151+
(rust-test-font-lock
2152+
"print!(\"{}\"); { /* no-op */ }"
2153+
'("print!" rust-builtin-formatting-macro-face
2154+
"\"" font-lock-string-face
2155+
"{}" rust-string-interpolation-face
2156+
"\"" font-lock-string-face
2157+
"/* " font-lock-comment-delimiter-face
2158+
"no-op */" font-lock-comment-face))
2159+
;; text + interpolation
2160+
(rust-test-font-lock
2161+
"print!(\"abcd {}\", foo); { /* no-op */ }"
2162+
'("print!" rust-builtin-formatting-macro-face
2163+
"\"abcd " font-lock-string-face
2164+
"{}" rust-string-interpolation-face
2165+
"\"" font-lock-string-face
2166+
"/* " font-lock-comment-delimiter-face
2167+
"no-op */" font-lock-comment-face))
2168+
;; text + interpolation with specification
2169+
(rust-test-font-lock
2170+
"print!(\"abcd {0}\", foo); { /* no-op */ }"
2171+
'("print!" rust-builtin-formatting-macro-face
2172+
"\"abcd " font-lock-string-face
2173+
"{0}" rust-string-interpolation-face
2174+
"\"" font-lock-string-face
2175+
"/* " font-lock-comment-delimiter-face
2176+
"no-op */" font-lock-comment-face))
2177+
;; text + interpolation with specification and escape
2178+
(rust-test-font-lock
2179+
"print!(\"abcd {0}}}\", foo); { /* no-op */ }"
2180+
'("print!" rust-builtin-formatting-macro-face
2181+
"\"abcd " font-lock-string-face
2182+
"{0}" rust-string-interpolation-face
2183+
"}}\"" font-lock-string-face
2184+
"/* " font-lock-comment-delimiter-face
2185+
"no-op */" font-lock-comment-face))
2186+
;; multiple pairs
2187+
(rust-test-font-lock
2188+
"print!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }"
2189+
'("print!" rust-builtin-formatting-macro-face
2190+
"\"abcd " font-lock-string-face
2191+
"{0}" rust-string-interpolation-face
2192+
" efgh " font-lock-string-face
2193+
"{1}" rust-string-interpolation-face
2194+
"\"" font-lock-string-face
2195+
"/* " font-lock-comment-delimiter-face
2196+
"no-op */" font-lock-comment-face))
2197+
;; println
2198+
(rust-test-font-lock
2199+
"println!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }"
2200+
'("println!" rust-builtin-formatting-macro-face
2201+
"\"abcd " font-lock-string-face
2202+
"{0}" rust-string-interpolation-face
2203+
" efgh " font-lock-string-face
2204+
"{1}" rust-string-interpolation-face
2205+
"\"" font-lock-string-face
2206+
"/* " font-lock-comment-delimiter-face
2207+
"no-op */" font-lock-comment-face))
2208+
;; eprint
2209+
(rust-test-font-lock
2210+
"eprint!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }"
2211+
'("eprint!" rust-builtin-formatting-macro-face
2212+
"\"abcd " font-lock-string-face
2213+
"{0}" rust-string-interpolation-face
2214+
" efgh " font-lock-string-face
2215+
"{1}" rust-string-interpolation-face
2216+
"\"" font-lock-string-face
2217+
"/* " font-lock-comment-delimiter-face
2218+
"no-op */" font-lock-comment-face))
2219+
;; eprintln
2220+
(rust-test-font-lock
2221+
"eprintln!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }"
2222+
'("eprintln!" rust-builtin-formatting-macro-face
2223+
"\"abcd " font-lock-string-face
2224+
"{0}" rust-string-interpolation-face
2225+
" efgh " font-lock-string-face
2226+
"{1}" rust-string-interpolation-face
2227+
"\"" font-lock-string-face
2228+
"/* " font-lock-comment-delimiter-face
2229+
"no-op */" font-lock-comment-face))
2230+
;; format
2231+
(rust-test-font-lock
2232+
"format!(\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }"
2233+
'("format!" rust-builtin-formatting-macro-face
2234+
"\"abcd " font-lock-string-face
2235+
"{0}" rust-string-interpolation-face
2236+
" efgh " font-lock-string-face
2237+
"{1}" rust-string-interpolation-face
2238+
"\"" font-lock-string-face
2239+
"/* " font-lock-comment-delimiter-face
2240+
"no-op */" font-lock-comment-face))
2241+
;; print + raw string
2242+
(rust-test-font-lock
2243+
"format!(r\"abcd {0} efgh {1}\", foo, bar); { /* no-op */ }"
2244+
'("format!" rust-builtin-formatting-macro-face
2245+
"r\"abcd " font-lock-string-face
2246+
"{0}" rust-string-interpolation-face
2247+
" efgh " font-lock-string-face
2248+
"{1}" rust-string-interpolation-face
2249+
"\"" font-lock-string-face
2250+
"/* " font-lock-comment-delimiter-face
2251+
"no-op */" font-lock-comment-face))
2252+
;; print + raw string with hash
2253+
(rust-test-font-lock
2254+
"format!(r#\"abcd {0} efgh {1}\"#, foo, bar); { /* no-op */ }"
2255+
'("format!" rust-builtin-formatting-macro-face
2256+
"r#\"abcd " font-lock-string-face
2257+
"{0}" rust-string-interpolation-face
2258+
" efgh " font-lock-string-face
2259+
"{1}" rust-string-interpolation-face
2260+
"\"#" font-lock-string-face
2261+
"/* " font-lock-comment-delimiter-face
2262+
"no-op */" font-lock-comment-face))
2263+
;; print + raw string with two hashes
2264+
(rust-test-font-lock
2265+
"format!(r##\"abcd {0} efgh {1}\"##, foo, bar); { /* no-op */ }"
2266+
'("format!" rust-builtin-formatting-macro-face
2267+
"r##\"abcd " font-lock-string-face
2268+
"{0}" rust-string-interpolation-face
2269+
" efgh " font-lock-string-face
2270+
"{1}" rust-string-interpolation-face
2271+
"\"##" font-lock-string-face
2272+
"/* " font-lock-comment-delimiter-face
2273+
"no-op */" font-lock-comment-face)))
2274+
2275+
(ert-deftest rust-write-macro-font-lock ()
2276+
(rust-test-font-lock
2277+
"write!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }"
2278+
'("write!" rust-builtin-formatting-macro-face
2279+
"\"abcd " font-lock-string-face
2280+
"{0}" rust-string-interpolation-face
2281+
"}} efgh " font-lock-string-face
2282+
"{1}" rust-string-interpolation-face
2283+
"\"" font-lock-string-face
2284+
"/* " font-lock-comment-delimiter-face
2285+
"no-op */" font-lock-comment-face))
2286+
(rust-test-font-lock
2287+
"writeln!(f, \"abcd {0}}} efgh {1}\", foo, bar); { /* no-op */ }"
2288+
'("writeln!" rust-builtin-formatting-macro-face
2289+
"\"abcd " font-lock-string-face
2290+
"{0}" rust-string-interpolation-face
2291+
"}} efgh " font-lock-string-face
2292+
"{1}" rust-string-interpolation-face
2293+
"\"" font-lock-string-face
2294+
"/* " font-lock-comment-delimiter-face
2295+
"no-op */" font-lock-comment-face)))
2296+
20772297
(ert-deftest rust-test-basic-paren-matching ()
20782298
(rust-test-matching-parens
20792299
"

rust-mode.el

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,18 @@ function or trait. When nil, where will be aligned with fn or trait."
176176
"Face for the question mark operator."
177177
:group 'rust-mode)
178178

179+
(defface rust-builtin-formatting-macro-face
180+
'((t :inherit font-lock-builtin-face))
181+
"Face for builtin formatting macros (print! &c.)."
182+
:group 'rust-mode)
183+
184+
(defface rust-string-interpolation-face
185+
'((t :slant italic :inherit font-lock-string-face))
186+
"Face for interpolating braces in builtin formatting macro strings."
187+
:group 'rust-mode)
188+
179189
(defun rust-paren-level () (nth 0 (syntax-ppss)))
190+
(defun rust-in-str () (nth 3 (syntax-ppss)))
180191
(defun rust-in-str-or-cmnt () (nth 8 (syntax-ppss)))
181192
(defun rust-rewind-past-str-cmnt () (goto-char (nth 8 (syntax-ppss))))
182193

@@ -579,6 +590,54 @@ the desired identifiers), but does not match type annotations \"foo::<\"."
579590
((not (looking-at (rx (0+ space) "<")))
580591
(throw 'rust-path-font-lock-matcher match))))))))
581592

593+
(defun rust-next-string-interpolation (limit)
594+
"Search forward from point for next Rust interpolation marker
595+
before LIMIT.
596+
Set point to the end of the occurrence found, and return match beginning
597+
and end."
598+
(catch 'match
599+
(save-match-data
600+
(save-excursion
601+
(while (search-forward "{" limit t)
602+
(if (eql (char-after (point)) ?{)
603+
(forward-char)
604+
(let ((start (match-beginning 0)))
605+
;; According to fmt_macros::Parser::next, an opening brace
606+
;; must be followed by an optional argument and/or format
607+
;; specifier, then a closing brace. A single closing brace
608+
;; without a corresponding unescaped opening brace is an
609+
;; error. We don't need to do anything special with
610+
;; arguments, specifiers, or errors, so we only search for
611+
;; the single closing brace.
612+
(when (search-forward "}" limit t)
613+
(throw 'match (list start (point)))))))))))
614+
615+
(defun rust-string-interpolation-matcher (limit)
616+
"Match next Rust interpolation marker before LIMIT and set
617+
match data if found. Returns nil if not within a Rust string."
618+
(when (rust-in-str)
619+
(let ((match (rust-next-string-interpolation limit)))
620+
(when match
621+
(set-match-data match)
622+
(goto-char (cadr match))
623+
match))))
624+
625+
(defvar rust-builtin-formatting-macros
626+
'("eprint"
627+
"eprintln"
628+
"format"
629+
"print"
630+
"println")
631+
"List of builtin Rust macros for string formatting used by `rust-mode-font-lock-keywords'. (`write!' is handled separately.)")
632+
633+
(defvar rust-formatting-macro-opening-re
634+
"[[:space:]]*[({[][[:space:]]*"
635+
"Regular expression to match the opening delimiter of a Rust formatting macro.")
636+
637+
(defvar rust-start-of-string-re
638+
"\\(?:r#*\\)?\""
639+
"Regular expression to match the start of a Rust raw string.")
640+
582641
(defvar rust-mode-font-lock-keywords
583642
(append
584643
`(
@@ -599,6 +658,22 @@ the desired identifiers), but does not match type annotations \"foo::<\"."
599658
(,(rust-re-grab (concat "#\\!?\\[" rust-re-ident "[^]]*\\]"))
600659
1 font-lock-preprocessor-face keep)
601660

661+
;; Builtin formatting macros
662+
(,(concat (rust-re-grab (concat (regexp-opt rust-builtin-formatting-macros) "!")) (concat rust-formatting-macro-opening-re rust-start-of-string-re))
663+
(1 'rust-builtin-formatting-macro-face)
664+
(rust-string-interpolation-matcher
665+
(rust-end-of-string)
666+
nil
667+
(0 'rust-string-interpolation-face t nil)))
668+
669+
;; write! macro
670+
(,(concat (rust-re-grab "write\\(ln\\)?!") (concat rust-formatting-macro-opening-re "[[:space:]]*[^\"]+,[[:space:]]*" rust-start-of-string-re))
671+
(1 'rust-builtin-formatting-macro-face)
672+
(rust-string-interpolation-matcher
673+
(rust-end-of-string)
674+
nil
675+
(0 'rust-string-interpolation-face t nil)))
676+
602677
;; Syntax extension invocations like `foo!`, highlight including the !
603678
(,(concat (rust-re-grab (concat rust-re-ident "!")) "[({[:space:][]")
604679
1 font-lock-preprocessor-face)
@@ -1239,6 +1314,13 @@ This is written mainly to be used as `end-of-defun-function' for Rust."
12391314
;; There is no opening brace, so consider the whole buffer to be one "defun"
12401315
(goto-char (point-max))))
12411316

1317+
(defun rust-end-of-string ()
1318+
"Skip to the end of the current string."
1319+
(save-excursion
1320+
(skip-syntax-forward "^\"|")
1321+
(skip-syntax-forward "\"|")
1322+
(point)))
1323+
12421324
;; Formatting using rustfmt
12431325
(defun rust--format-call (buf)
12441326
"Format BUF using rustfmt."

0 commit comments

Comments
 (0)