@@ -5,85 +5,119 @@ scriptencoding utf-8
55"
66" Puppet Style Guide: All arrows in a resource body should be aligned.
77" file { '/etc/foo':
8- " ensure => file,
8+ " ensure => ' file' ,
99" owner => 'root',
1010" mode => '0644',
1111" }
12+ "
13+ " Safety model:
14+ " - Only arrows (`=>`) that are in "code" context are considered.
15+ " - Arrows inside strings, comments, heredocs, or interpolations are ignored.
16+ " - This prevents corrupting content, regexes, or comments that happen to contain '=>'.
17+ " - Multiline values are left untouched (the line containing the key+arrow is still aligned).
1218
1319function ! openvox#align#arrows () range abort
1420 let l: lines = getline (a: firstline , a: lastline )
1521 let l: max_key_len = 0
1622 let l: arrow_count = 0
23+ let l: safe_lines = [] " list of {lnum, key, indent, value, arrow_col}
1724
18- " First pass: find the longest key name (text before =>).
19- " We match: leading whitespace, then the key (everything up to the
20- " first => that is preceded by optional whitespace). This handles
21- " keys like 'ensure', 'my-key', 'some_thing', and even keys with
22- " no space before => (e.g., 'ensure=>present').
25+ " First pass: find longest *safe* key (only code-context arrows).
26+ let l: lnum = a: firstline
2327 for l: line in l: lines
24- let l: match = matchlist (l: line , ' ^\(\s*\)\(\S.\{-}\)\s*=>' )
25- if ! empty (l: match )
26- let l: key_len = len (l: match [2 ])
27- if l: key_len > l: max_key_len
28- let l: max_key_len = l: key_len
28+ let l: arrow_col = s: FindFirstSafeArrowCol (l: line , l: lnum )
29+ if l: arrow_col >= 0
30+ " Extract indent + key (everything before the safe =>)
31+ let l: prefix = l: line [0 : l: arrow_col - 1 ]
32+ let l: match = matchlist (l: prefix , ' ^\(\s*\)\(\S.*\)$' )
33+ if ! empty (l: match )
34+ let l: indent = l: match [1 ]
35+ let l: key = substitute (l: match [2 ], ' \s\+$' , ' ' , ' ' ) " trim trailing ws
36+ let l: key_len = len (l: key )
37+
38+ if l: key_len > l: max_key_len
39+ let l: max_key_len = l: key_len
40+ endif
41+
42+ " Capture the rest of the line after this safe arrow for rewriting
43+ let l: after = l: line [l: arrow_col + 2 : ] " skip '=>'
44+ let l: value = substitute (l: after , ' ^\s*' , ' ' , ' ' ) " trim leading ws after =>
45+ call add (l: safe_lines , {
46+ \ ' lnum' : l: lnum ,
47+ \ ' indent' : l: indent ,
48+ \ ' key' : l: key ,
49+ \ ' value' : l: value ,
50+ \ })
51+ let l: arrow_count += 1
2952 endif
30- let l: arrow_count += 1
3153 endif
54+ let l: lnum += 1
3255 endfor
3356
3457 if l: arrow_count == 0
35- echo ' No arrows (=>) found in selection'
58+ echo ' No arrows (=>) found in selection (in code context) '
3659 return
3760 endif
3861
39- " Second pass: rewrite each line so => is aligned.
40- " The arrow column = indent + max_key_len + 1 space.
41- " Key length is measured relative to the indent, not absolute column.
42- let l: lnum = a: firstline
43- for l: line in l: lines
44- let l: match = matchlist (l: line , ' ^\(\s*\)\(\S.\{-}\)\s*=>\s*\(.*\)' )
45- if ! empty (l: match )
46- let l: indent = l: match [1 ]
47- let l: key = l: match [2 ]
48- let l: value = l: match [3 ]
49- " Padding = spaces needed after the key to reach the alignment column
50- let l: pad = l: max_key_len - len (l: key )
51- if l: pad < 0
52- let l: pad = 0
53- endif
54- let l: new_line = l: indent . l: key . repeat (' ' , l: pad ) . ' => ' . l: value
55- call setline (l: lnum , l: new_line )
56- endif
57- let l: lnum += 1
62+ " Second pass: rewrite only the safe lines so => is aligned.
63+ " arrow column = indent + max_key_len + 1 (one space before =>)
64+ for l: entry in l: safe_lines
65+ let l: pad = l: max_key_len - len (l: entry .key )
66+ if l: pad < 0 | let l: pad = 0 | endif
67+
68+ let l: new_line = l: entry .indent . l: entry .key
69+ \ . repeat (' ' , l: pad ) . ' => ' . l: entry .value
70+
71+ call setline (l: entry .lnum, l: new_line )
5872 endfor
5973
6074 echo printf (' Aligned %d arrow(s)' , l: arrow_count )
6175endfunction
6276
63- " Helper: check if inside string/comment/heredoc (for safe searchpair)
64- function ! s: IsStringOrComment (lnum, col ) abort
77+ " Return byte column of first '=>' that is NOT inside string/comment/heredoc.
78+ " Returns -1 if no safe arrow on the line.
79+ function ! s: FindFirstSafeArrowCol (line , lnum) abort
80+ let l: col = 0
81+ while 1
82+ let l: col = match (a: line , ' =>' , l: col )
83+ if l: col < 0
84+ return -1
85+ endif
86+ " Check around the arrow (the '=' or the '>')
87+ if ! s: IsInStringOrComment (a: lnum , l: col + 1 )
88+ return l: col
89+ endif
90+ let l: col += 2
91+ endwhile
92+ endfunction
93+
94+ " Helper: is the given position inside a string, comment, heredoc or interpolation?
95+ " Matches the syntax groups defined in syntax/puppet.vim.
96+ function ! s: IsInStringOrComment (lnum, col ) abort
6597 let l: syn = synIDattr (synID (a: lnum , a: col , 1 ), ' name' )
66- return l: syn = ~? ' string\|comment\|heredoc\|puppetInterpolation'
98+ " Covers: puppetComment, puppetCComment, puppetSQString, puppetDQString,
99+ " puppetHeredoc*, puppetHeredocNI, puppetInterpolation, etc.
100+ return l: syn = ~? ' Comment\|String\|Heredoc\|Interpolation'
67101endfunction
68102
69- " Auto-align: align arrows in the current resource block
103+ " Auto-align: align arrows in the current resource (or hash) block.
104+ " Uses searchpair with string/comment skipping for finding the block.
70105function ! openvox#align#block () abort
71- " Find the enclosing { ... } block (skip strings/comments)
72106 let l: save_pos = getpos (' .' )
73107
74- let l: open = searchpair (' {' , ' ' , ' }' , ' bnW' , ' s:IsStringOrComment (line("."), col("."))' )
108+ let l: open = searchpair (' {' , ' ' , ' }' , ' bnW' , ' s:IsInStringOrComment (line("."), col("."))' )
75109 if l: open == 0
76- echo ' Not inside a resource block'
110+ echo ' Not inside a { ... } block'
77111 return
78112 endif
79113
80- let l: close = searchpair (' {' , ' ' , ' }' , ' nW' , ' s:IsStringOrComment (line("."), col("."))' )
114+ let l: close = searchpair (' {' , ' ' , ' }' , ' nW' , ' s:IsInStringOrComment (line("."), col("."))' )
81115 if l: close == 0
82116 echo ' Could not find closing brace'
83117 return
84118 endif
85119
86- " Align arrows in the block (skip the { and } lines )
120+ " Align only the interior lines (skip the opening { line and closing } line )
87121 execute (l: open + 1 ) . ' ,' . (l: close - 1 ) . ' call openvox#align#arrows()'
88122
89123 call setpos (' .' , l: save_pos )
0 commit comments