@@ -9,10 +9,18 @@ use crate::{LineEnding, RopeSlice};
9
9
// 5 is the vim default
10
10
const LINES_TO_CHECK : usize = 5 ;
11
11
12
- static MODELINE_REGEX : Lazy < rope:: Regex > =
12
+ static HELIX_MODELINE_REGEX : Lazy < rope:: Regex > =
13
13
Lazy :: new ( || rope:: Regex :: new ( r"^(.{0,100}\s{1,100})?helix:" ) . unwrap ( ) ) ;
14
- static MODELINE_OPTION_REGEX : Lazy < rope:: Regex > =
14
+ static HELIX_MODELINE_OPTION_REGEX : Lazy < rope:: Regex > =
15
15
Lazy :: new ( || rope:: Regex :: new ( r"[a-zA-Z0-9_-]{1,100}(?:=[a-zA-Z0-9_-]{1,100})" ) . unwrap ( ) ) ;
16
+ static VIM_MODELINE_REGEX : Lazy < rope:: Regex > = Lazy :: new ( || {
17
+ rope:: Regex :: new (
18
+ r"^(.{0,100}\s{1,100})?(vi|[vV]im[<=>]?\d{0,100}|ex):\s{0,100}(set?\s{1,100})?" ,
19
+ )
20
+ . unwrap ( )
21
+ } ) ;
22
+ static VIM_MODELINE_OPTION_REGEX : Lazy < rope:: Regex > =
23
+ Lazy :: new ( || rope:: Regex :: new ( r"[a-zA-Z0-9_-]{1,100}(?:=(?:\\:|[^:\s]){0,1000})?" ) . unwrap ( ) ) ;
16
24
17
25
#[ derive( Default , Debug , Eq , PartialEq ) ]
18
26
pub struct Modeline {
@@ -49,9 +57,9 @@ impl Modeline {
49
57
}
50
58
51
59
fn parse_from_line ( & mut self , mut line : RopeSlice ) {
52
- if let Some ( pos) = MODELINE_REGEX . find ( line. regex_input ( ) ) {
60
+ if let Some ( pos) = HELIX_MODELINE_REGEX . find ( line. regex_input ( ) ) {
53
61
line = line. slice ( line. byte_to_char ( pos. end ( ) ) ..) ;
54
- while let Some ( opt_pos) = MODELINE_OPTION_REGEX . find ( line. regex_input ( ) ) {
62
+ while let Some ( opt_pos) = HELIX_MODELINE_OPTION_REGEX . find ( line. regex_input ( ) ) {
55
63
let option =
56
64
Cow :: from ( line. slice (
57
65
line. byte_to_char ( opt_pos. start ( ) ) ..line. byte_to_char ( opt_pos. end ( ) ) ,
@@ -83,10 +91,63 @@ impl Modeline {
83
91
let whitespace = line. chars ( ) . take_while ( |c| char:: is_whitespace ( * c) ) . count ( ) ;
84
92
line = line. slice ( whitespace..) ;
85
93
}
94
+ } else if let Some ( pos) = VIM_MODELINE_REGEX . find ( line. regex_input ( ) ) {
95
+ line = line. slice ( line. byte_to_char ( pos. end ( ) ) ..) ;
96
+ while let Some ( opt_pos) = VIM_MODELINE_OPTION_REGEX . find ( line. regex_input ( ) ) {
97
+ let option =
98
+ Cow :: from ( line. slice (
99
+ line. byte_to_char ( opt_pos. start ( ) ) ..line. byte_to_char ( opt_pos. end ( ) ) ,
100
+ ) ) ;
101
+ let mut parts = option. as_ref ( ) . splitn ( 2 , '=' ) ;
102
+ match parts. next ( ) . unwrap ( ) {
103
+ "ft" | "filetype" => {
104
+ if let Some ( val) = parts. next ( ) {
105
+ self . language = Some ( val. to_string ( ) ) ;
106
+ }
107
+ }
108
+ "sw" | "shiftwidth" => {
109
+ if let Some ( val) = parts. next ( ) . and_then ( |val| val. parse ( ) . ok ( ) ) {
110
+ if self . indent_style != Some ( IndentStyle :: Tabs ) {
111
+ self . indent_style = Some ( IndentStyle :: Spaces ( val) ) ;
112
+ }
113
+ }
114
+ }
115
+ "ff" | "fileformat" => {
116
+ if let Some ( val) = parts. next ( ) {
117
+ self . line_ending = vim_ff_to_helix_line_ending ( val) ;
118
+ }
119
+ }
120
+ "noet" | "noexpandtab" => {
121
+ self . indent_style = Some ( IndentStyle :: Tabs ) ;
122
+ }
123
+ "et" | "expandtab" => {
124
+ if !matches ! ( self . indent_style, Some ( IndentStyle :: Spaces ( _) ) ) {
125
+ self . indent_style = Some ( IndentStyle :: Spaces ( 0 ) ) ;
126
+ }
127
+ }
128
+ _ => { }
129
+ }
130
+ line = line. slice ( line. byte_to_char ( opt_pos. end ( ) ) ..) ;
131
+ let whitespace = line
132
+ . chars ( )
133
+ . take_while ( |c| char:: is_whitespace ( * c) || * c == ':' )
134
+ . count ( ) ;
135
+ line = line. slice ( whitespace..) ;
136
+ }
86
137
}
87
138
}
88
139
}
89
140
141
+ fn vim_ff_to_helix_line_ending ( val : & str ) -> Option < LineEnding > {
142
+ match val {
143
+ "dos" => Some ( LineEnding :: Crlf ) ,
144
+ "unix" => Some ( LineEnding :: LF ) ,
145
+ #[ cfg( feature = "unicode-lines" ) ]
146
+ "mac" => Some ( LineEnding :: CR ) ,
147
+ _ => None ,
148
+ }
149
+ }
150
+
90
151
#[ cfg( test) ]
91
152
mod test {
92
153
use super :: * ;
@@ -145,11 +206,128 @@ mod test {
145
206
line_ending : Some ( LineEnding :: Crlf ) ,
146
207
} ,
147
208
) ,
209
+ (
210
+ "vi:noai:sw=3 ts=6" ,
211
+ Modeline {
212
+ indent_style : Some ( IndentStyle :: Spaces ( 3 ) ) ,
213
+ ..Default :: default ( )
214
+ } ,
215
+ ) ,
216
+ (
217
+ "vim: tw=77" ,
218
+ Modeline {
219
+ ..Default :: default ( )
220
+ } ,
221
+ ) ,
222
+ (
223
+ "/* vim: set ai sw=5: */" ,
224
+ Modeline {
225
+ indent_style : Some ( IndentStyle :: Spaces ( 5 ) ) ,
226
+ ..Default :: default ( )
227
+ } ,
228
+ ) ,
229
+ (
230
+ "# vim: set noexpandtab:" ,
231
+ Modeline {
232
+ indent_style : Some ( IndentStyle :: Tabs ) ,
233
+ ..Default :: default ( )
234
+ } ,
235
+ ) ,
236
+ (
237
+ "# vim: set expandtab:" ,
238
+ Modeline {
239
+ indent_style : Some ( IndentStyle :: Spaces ( 0 ) ) ,
240
+ ..Default :: default ( )
241
+ } ,
242
+ ) ,
243
+ (
244
+ "// vim: noai:ts=4:sw=4" ,
245
+ Modeline {
246
+ indent_style : Some ( IndentStyle :: Spaces ( 4 ) ) ,
247
+ ..Default :: default ( )
248
+ } ,
249
+ ) ,
250
+ (
251
+ "/* vim: set noai ts=4 sw=4: */" ,
252
+ Modeline {
253
+ indent_style : Some ( IndentStyle :: Spaces ( 4 ) ) ,
254
+ ..Default :: default ( )
255
+ } ,
256
+ ) ,
257
+ (
258
+ "/* vim: set fdm=expr ft=c fde=getline(v\\ :lnum)=~'{'?'>1'\\ :'1' sw=4: */" ,
259
+ Modeline {
260
+ language : Some ( "c" . to_string ( ) ) ,
261
+ indent_style : Some ( IndentStyle :: Spaces ( 4 ) ) ,
262
+ ..Default :: default ( )
263
+ } ,
264
+ ) ,
265
+ (
266
+ "/* vim: set ts=8 sw=4 tw=0 noet : */" ,
267
+ Modeline {
268
+ indent_style : Some ( IndentStyle :: Tabs ) ,
269
+ ..Default :: default ( )
270
+ } ,
271
+ ) ,
272
+ (
273
+ "vim:ff=unix ts=4 sw=4" ,
274
+ Modeline {
275
+ indent_style : Some ( IndentStyle :: Spaces ( 4 ) ) ,
276
+ line_ending : Some ( LineEnding :: LF ) ,
277
+ ..Default :: default ( )
278
+ } ,
279
+ ) ,
280
+ (
281
+ "vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:" ,
282
+ Modeline {
283
+ language : Some ( "help" . to_string ( ) ) ,
284
+ indent_style : Some ( IndentStyle :: Spaces ( 2 ) ) ,
285
+ ..Default :: default ( )
286
+ } ,
287
+ ) ,
288
+ (
289
+ "# vim: ft=zsh sw=2 ts=2 et" ,
290
+ Modeline {
291
+ language : Some ( "zsh" . to_string ( ) ) ,
292
+ indent_style : Some ( IndentStyle :: Spaces ( 2 ) ) ,
293
+ ..Default :: default ( )
294
+ } ,
295
+ ) ,
296
+ (
297
+ "# vim:ft=sh:" ,
298
+ Modeline {
299
+ language : Some ( "sh" . to_string ( ) ) ,
300
+ ..Default :: default ( )
301
+ } ,
302
+ ) ,
303
+ (
304
+ "\" vim:ts=8:sts=4:sw=4:expandtab:ft=vim" ,
305
+ Modeline {
306
+ language : Some ( "vim" . to_string ( ) ) ,
307
+ indent_style : Some ( IndentStyle :: Spaces ( 4 ) ) ,
308
+ ..Default :: default ( )
309
+ } ,
310
+ ) ,
311
+ (
312
+ "\" vim: ts=8 noet tw=100 sw=8 sts=0 ft=vim isk+=-" ,
313
+ Modeline {
314
+ language : Some ( "vim" . to_string ( ) ) ,
315
+ indent_style : Some ( IndentStyle :: Tabs ) ,
316
+ ..Default :: default ( )
317
+ } ,
318
+ ) ,
319
+ (
320
+ "; vim:ft=gitconfig:" ,
321
+ Modeline {
322
+ language : Some ( "gitconfig" . to_string ( ) ) ,
323
+ ..Default :: default ( )
324
+ } ,
325
+ ) ,
148
326
] ;
149
327
for ( line, expected) in tests {
150
328
let mut got = Modeline :: default ( ) ;
151
329
got. parse_from_line ( line. into ( ) ) ;
152
- assert_eq ! ( got, expected) ;
330
+ assert_eq ! ( got, expected, "{line}" ) ;
153
331
}
154
332
}
155
333
}
0 commit comments