@@ -45,13 +45,16 @@ impl ModelineSettings {
4545/// Parse modelines from file content.
4646///
4747/// Supports:
48- /// - Emacs modelines: -*- mode: rust; tab-width: 4; indent-tabs-mode: nil; -*-
48+ /// - Emacs modelines: -*- mode: rust; tab-width: 4; indent-tabs-mode: nil; -*- and "Local Variables"
4949/// - Vim modelines: vim: set ft=rust ts=4 sw=4 et:
5050pub fn parse_modeline ( first_lines : & [ & str ] , last_lines : & [ & str ] ) -> Option < ModelineSettings > {
5151 let mut settings = ModelineSettings :: default ( ) ;
5252
5353 parse_modelines ( first_lines, & mut settings) ;
5454
55+ // Parse Emacs Local Variables in last lines
56+ parse_emacs_local_variables ( last_lines, & mut settings) ;
57+
5558 // Also check for vim modelines in last lines if we don't have settings yet
5659 if !settings. has_settings ( ) {
5760 parse_vim_modelines ( last_lines, & mut settings) ;
@@ -90,6 +93,61 @@ fn parse_emacs_modeline(line: &str, settings: &mut ModelineSettings) {
9093 }
9194}
9295
96+ /// Parse Emacs-style Local Variables block
97+ ///
98+ /// Emacs supports a "Local Variables" block at the end of files:
99+ /// ```text
100+ /// /* Local Variables: */
101+ /// /* mode: c */
102+ /// /* tab-width: 4 */
103+ /// /* End: */
104+ /// ```
105+ ///
106+ /// Emacs related code is hack-local-variables--find-variables in
107+ /// https://cgit.git.savannah.gnu.org/cgit/emacs.git/tree/lisp/files.el#n4346
108+ fn parse_emacs_local_variables ( lines : & [ & str ] , settings : & mut ModelineSettings ) {
109+ const LOCAL_VARIABLES : & str = "Local Variables:" ;
110+
111+ let Some ( ( start_idx, prefix, suffix) ) = lines. iter ( ) . enumerate ( ) . find_map ( |( i, line) | {
112+ let prefix_len = line. find ( LOCAL_VARIABLES ) ?;
113+ let suffix_start = prefix_len + LOCAL_VARIABLES . len ( ) ;
114+ Some ( ( i, line. get ( ..prefix_len) ?, line. get ( suffix_start..) ?) )
115+ } ) else {
116+ return ;
117+ } ;
118+
119+ let mut continuation = String :: new ( ) ;
120+
121+ for line in & lines[ start_idx + 1 ..] {
122+ let Some ( content) = line
123+ . strip_prefix ( prefix)
124+ . and_then ( |l| l. strip_suffix ( suffix) )
125+ . map ( str:: trim)
126+ else {
127+ return ;
128+ } ;
129+
130+ if let Some ( continued) = content. strip_suffix ( '\\' ) {
131+ continuation. push_str ( continued) ;
132+ continue ;
133+ }
134+
135+ let to_parse = if continuation. is_empty ( ) {
136+ content
137+ } else {
138+ continuation. push_str ( content) ;
139+ & continuation
140+ } ;
141+
142+ if to_parse == "End:" {
143+ return ;
144+ }
145+
146+ parse_emacs_key_value ( to_parse, settings, false ) ;
147+ continuation. clear ( ) ;
148+ }
149+ }
150+
93151fn parse_emacs_key_value ( part : & str , settings : & mut ModelineSettings , bare : bool ) {
94152 let part = part. trim ( ) ;
95153 if part. is_empty ( ) {
@@ -266,6 +324,7 @@ fn parse_vim_settings(content: &str, settings: &mut ModelineSettings) {
266324#[ cfg( test) ]
267325mod tests {
268326 use super :: * ;
327+ use indoc:: indoc;
269328 use pretty_assertions:: assert_eq;
270329
271330 #[ test]
@@ -302,6 +361,53 @@ mod tests {
302361 ) ;
303362 }
304363
364+ #[ test]
365+ fn test_emacs_last_line_parsing ( ) {
366+ let content = indoc ! { r#"
367+ # Local Variables:
368+ # compile-command: "cc foo.c -Dfoo=bar -Dhack=whatever \
369+ # -Dmumble=blaah"
370+ # End:
371+ "# }
372+ . lines ( )
373+ . collect :: < Vec < _ > > ( ) ;
374+ let settings = parse_modeline ( & [ ] , & content) . unwrap ( ) ;
375+ assert_eq ! (
376+ settings,
377+ ModelineSettings {
378+ emacs_extra_variables: vec![ (
379+ "compile-command" . to_string( ) ,
380+ "\" cc foo.c -Dfoo=bar -Dhack=whatever -Dmumble=blaah\" " . to_string( )
381+ ) , ] ,
382+ ..Default :: default ( )
383+ }
384+ ) ;
385+
386+ let content = indoc ! { "
387+ foo
388+ /* Local Variables: */
389+ /* eval: (font-lock-mode -1) */
390+ /* mode: old-c */
391+ /* mode: c */
392+ /* End: */
393+ /* mode: ignored */
394+ " }
395+ . lines ( )
396+ . collect :: < Vec < _ > > ( ) ;
397+ let settings = parse_modeline ( & [ ] , & content) . unwrap ( ) ;
398+ assert_eq ! (
399+ settings,
400+ ModelineSettings {
401+ mode: Some ( "c" . to_string( ) ) ,
402+ emacs_extra_variables: vec![ (
403+ "eval" . to_string( ) ,
404+ "(font-lock-mode -1)" . to_string( )
405+ ) , ] ,
406+ ..Default :: default ( )
407+ }
408+ ) ;
409+ }
410+
305411 #[ test]
306412 fn test_vim_modeline_parsing ( ) {
307413 // Test second form (set format)
0 commit comments