fix(tables): don't split a cell on a | inside a link/code/escape
CI / editor tests (push) Successful in 46s
CI / editor tests (push) Successful in 46s
Table auto-alignment scanned for cell separators with a naive find('|'),
so a cell containing a vimwiki link `[[url|title]]` was split at the link's
internal pipe — mangling both the link (`[[url` / `title]]`) and the table
(a phantom extra column). Same bug in both the Lua (Neovim) and VimL (Vim)
table code, in both table_cell_starts and parse_table_row.
Add a shared cell-separator scanner (cell_bar_positions) that treats `|` as
literal — not a separator — when it's inside `[[…]]` wikilinks, `{{…}}`
transclusions, `` `…` `` inline code, or backslash-escaped (`\|`), matching
vimwiki. Both scan functions in each client now use it.
Regression tests added on both clients (cr.table_link_cell_pipe_not_a_separator);
full harness green (Neovim 308, Vim 302).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+35
-15
@@ -821,20 +821,44 @@ end
|
||||
|
||||
local is_table_row = require('nuwiki.util').is_table_row
|
||||
|
||||
-- 1-based byte positions of the cell-separator `|` characters in `s`. Pipes
|
||||
-- that are literal cell content are skipped: those inside `[[…]]` wikilinks,
|
||||
-- `{{…}}` transclusions, `` `…` `` inline code, and backslash-escaped `\|`.
|
||||
-- This keeps a `[[url|title]]` link in one cell instead of splitting on its
|
||||
-- internal pipe.
|
||||
local function cell_bar_positions(s)
|
||||
local out = {}
|
||||
local i = 1
|
||||
local n = #s
|
||||
while i <= n do
|
||||
local c = s:sub(i, i)
|
||||
if c == '\\' then
|
||||
i = i + 2 -- skip the escaped char (e.g. `\|`)
|
||||
elseif c == '`' then
|
||||
local close = s:find('`', i + 1, true)
|
||||
i = (close or n) + 1
|
||||
elseif s:sub(i, i + 1) == '[[' then
|
||||
local close = s:find(']]', i + 2, true)
|
||||
i = close and close + 2 or n + 1
|
||||
elseif s:sub(i, i + 1) == '{{' then
|
||||
local close = s:find('}}', i + 2, true)
|
||||
i = close and close + 2 or n + 1
|
||||
elseif c == '|' then
|
||||
table.insert(out, i)
|
||||
i = i + 1
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function table_cell_starts(line)
|
||||
-- Returns 1-based byte positions of each `|` separator. Cell N spans
|
||||
-- (starts[N], starts[N+1]); placing the cursor at 0-based byte column
|
||||
-- `starts[i]` lands one position past the i-th `|` (the first space
|
||||
-- of the cell that the `|` opens).
|
||||
local out = {}
|
||||
local pos = 0
|
||||
while true do
|
||||
local bar = line:find('|', pos + 1, true)
|
||||
if not bar then break end
|
||||
table.insert(out, bar)
|
||||
pos = bar
|
||||
end
|
||||
return out
|
||||
return cell_bar_positions(line)
|
||||
end
|
||||
|
||||
local function find_table_range(row)
|
||||
@@ -856,15 +880,11 @@ local function parse_table_row(line)
|
||||
local body = line:sub(#indent + 1)
|
||||
local segments = {}
|
||||
local pos = 1
|
||||
while true do
|
||||
local bar = body:find('|', pos, true)
|
||||
if not bar then
|
||||
table.insert(segments, body:sub(pos))
|
||||
break
|
||||
end
|
||||
for _, bar in ipairs(cell_bar_positions(body)) do
|
||||
table.insert(segments, body:sub(pos, bar - 1))
|
||||
pos = bar + 1
|
||||
end
|
||||
table.insert(segments, body:sub(pos))
|
||||
-- Drop the empty piece before the opening `|` and the trailing piece
|
||||
-- after the closing `|`.
|
||||
if #segments > 0 and segments[1] == '' then table.remove(segments, 1) end
|
||||
|
||||
Reference in New Issue
Block a user