Skip to content

Commit 74eb988

Browse files
committed
fix(align): harden hashrocket alignment with proper string/comment/heredoc safety
- Only consider => arrows that are in code context (skip those inside strings, comments, heredocs, and interpolations). - Improves reliability of :OpenvoxAlign, :OpenvoxAlignBlock and <LocalLeader>a. - Addresses cases that produced misaligned or corrupted output. - Bump version to 1.0.1 and update documentation.
1 parent 0382417 commit 74eb988

4 files changed

Lines changed: 79 additions & 45 deletions

File tree

DOCUMENTATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**Repository:** `cvquesty/vim-openvox`
88
**License:** Apache-2.0
9-
**Version:** 1.0.0
9+
**Version:** 1.0.1
1010

1111
---
1212

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**A comprehensive Vim plugin for OpenVox and Puppet 8+ development**
66

7-
[![Version](https://img.shields.io/badge/version-1.0.0--rc1-orange?style=for-the-badge)](https://github.com/cvquesty/vim-openvox/releases)
7+
[![Version](https://img.shields.io/badge/version-1.0.1-orange?style=for-the-badge)](https://github.com/cvquesty/vim-openvox/releases)
88
[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=for-the-badge)](LICENSE)
99
[![Vim](https://img.shields.io/badge/Vim-8.0+-019833?style=for-the-badge&logo=vim&logoColor=white)](https://www.vim.org)
1010
[![Neovim](https://img.shields.io/badge/Neovim-0.5+-57A143?style=for-the-badge&logo=neovim&logoColor=white)](https://neovim.io)
@@ -74,7 +74,7 @@ git clone https://github.com/cvquesty/vim-openvox.git
7474

7575
vim-openvox works out of the box for syntax, folding, linting, and basic navigation.
7676

77-
**Note on indentation & alignment**: Core features are functional. We are actively porting battle-tested logic from the vim-puppet gold standard to make auto-indent and block alignment rock-solid. Current behavior is good for most cases and improving rapidly.
77+
**Note on indentation & alignment**: Core features are functional and have improved safety for arrow alignment (strings, comments, and heredocs are now properly skipped). We continue hardening toward full vim-puppet parity.
7878

7979
### Key Mappings
8080

autoload/openvox/align.vim

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1319
function! 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)
6175
endfunction
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'
67101
endfunction
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.
70105
function! 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)

doc/openvox.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Author: xAI
44
License: Apache-2.0
5-
Version: 1.0.0
5+
Version: 1.0.1
66

77
==============================================================================
88
CONTENTS *openvox-contents*

0 commit comments

Comments
 (0)