aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2025-06-08 11:09:30 -0500
committerFlorian Fischer <florian.fischer@muhq.space>2025-06-08 15:35:07 -0500
commit59ce465f6d51f1d3b8ae18a3346264006e99aab5 (patch)
treeebabd52d1adece59d9521b040ca844f13b0190d9
parentcef8b428cff4cfb3fa07f30eab644cba1540b4b2 (diff)
downloadvis-lspc-59ce465f6d51f1d3b8ae18a3346264006e99aab5.tar.gz
vis-lspc-59ce465f6d51f1d3b8ae18a3346264006e99aab5.zip
fix the line number detection with tabs
If a line includes tabs add the visually added space for each tab stop to the line length in order to detect a wrapped line.
-rw-r--r--init.lua11
-rw-r--r--util.lua25
-rwxr-xr-xutil_test.lua15
3 files changed, 48 insertions, 3 deletions
diff --git a/init.lua b/init.lua
index fd8c0b4..498887d 100644
--- a/init.lua
+++ b/init.lua
@@ -566,9 +566,14 @@ local function file_lineno_to_viewport_lineno(win, file_lineno)
local view_lineno = 0
for n = win.viewport.lines.start, file_lineno do
view_lineno = view_lineno + 1
- -- Wrapped line this shifts our displayed line down
- if #win.file.lines[n] > win.viewport.width then
- view_lineno = view_lineno + math.floor(#win.file.lines[n] / win.viewport.width)
+ -- Wrapped lines shift our displayed line down
+ local line_len = #win.file.lines[n]
+ if not win.options.expandtab then
+ line_len = util.visual_chars_in_line(win, win.file.lines[n], line_len)
+ end
+
+ if line_len >= win.viewport.width then
+ view_lineno = view_lineno + math.floor(line_len / win.viewport.width)
end
end
return view_lineno
diff --git a/util.lua b/util.lua
index 04855fb..2b090b2 100644
--- a/util.lua
+++ b/util.lua
@@ -279,4 +279,29 @@ function util.vis_pos_to_sel(win, pos)
return sel
end
+--- Count the visual characters in a line
+-- This is useful to detect wrapped lines including tabs which may add a
+-- unspecified amount of white space to a line depending on their position and
+-- the used tabwidth.
+-- @param win the window containing the line
+-- @param line the line to count
+-- @param nchars the number of characters in the line
+-- @return the number of visual characters
+util.visual_chars_in_line = function(win, line, nchars)
+ -- fast string iteration inspired by:
+ -- https://stackoverflow.com/a/49222705
+ local l = {string.byte(line, 1, nchars)}
+ local line_len = 0
+ for i = 1, nchars do
+ local c = l[i] -- Note: produces char codes instead of chars.
+ if c == 9 then -- '\t'
+ local chars_to_tab_stop = win.options.tabwidth - (line_len % win.options.tabwidth)
+ line_len = line_len + chars_to_tab_stop
+ else
+ line_len = line_len + 1
+ end
+ end
+ return line_len
+end
+
return util
diff --git a/util_test.lua b/util_test.lua
index fd6570d..a0f4218 100755
--- a/util_test.lua
+++ b/util_test.lua
@@ -15,4 +15,19 @@ function test_dirname() -- luacheck: ignore 111
lunatest.assert_equal('/', util.dirname('/'))
end
+function test_visual_chars_in_line() -- luacheck: ignore 111
+ local win = {options = {tabwidth = 4}} -- win mock
+ local s = '\tfo' -- visual chars == 6
+ lunatest.assert_equal(util.visual_chars_in_line(win, s, #s), 6)
+
+ s = 'f\tfo' -- visual chars == 6
+ lunatest.assert_equal(util.visual_chars_in_line(win, s, #s), 6)
+
+ s = 'fo\tfo' -- visual chars == 6
+ lunatest.assert_equal(util.visual_chars_in_line(win, s, #s), 6)
+
+ s = 'foo\tfo' -- visual chars == 6
+ lunatest.assert_equal(util.visual_chars_in_line(win, s, #s), 6)
+end
+
lunatest.run()