Skip to content

Commit

Permalink
fix: multibyte chars in text lines (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
tris203 authored May 31, 2024
1 parent b40c353 commit f893367
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 32 deletions.
18 changes: 8 additions & 10 deletions lua/precognition/horizontal_motions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,26 @@ end

---@param str string
---@param cursorcol integer
---@param _linelen integer
---@param linelen integer
---@param big_word boolean
---@return Precognition.PlaceLoc
function M.next_word_boundary(str, cursorcol, _linelen, big_word)
function M.next_word_boundary(str, cursorcol, linelen, big_word)
local offset = cursorcol
local len = vim.fn.strcharlen(str)
local char = vim.fn.strcharpart(str, offset - 1, 1)
local c_class = utils.char_class(char, big_word)

if c_class ~= cc.whitespace then
while utils.char_class(char, big_word) == c_class and offset <= len do
while utils.char_class(char, big_word) == c_class and offset <= linelen do
offset = offset + 1
char = vim.fn.strcharpart(str, offset - 1, 1)
end
end

while utils.char_class(char, big_word) == cc.whitespace and offset <= len do
while utils.char_class(char, big_word) == cc.whitespace and offset <= linelen do
offset = offset + 1
char = vim.fn.strcharpart(str, offset - 1, 1)
end
if offset > len then
if offset > linelen then
return 0
end

Expand Down Expand Up @@ -114,11 +113,10 @@ end

---@param str string
---@param cursorcol integer
---@param _linelen integer
---@param linelen integer
---@param big_word boolean
---@return Precognition.PlaceLoc
function M.prev_word_boundary(str, cursorcol, _linelen, big_word)
local len = vim.fn.strcharlen(str)
function M.prev_word_boundary(str, cursorcol, linelen, big_word)
local offset = cursorcol - 1
local char = vim.fn.strcharpart(str, offset - 1, 1)
local c_class = utils.char_class(char, big_word)
Expand All @@ -141,7 +139,7 @@ function M.prev_word_boundary(str, cursorcol, _linelen, big_word)
end
end

if offset == nil or offset > len or offset < 0 then
if offset == nil or offset > linelen or offset < 0 then
return 0
end
return offset + 1
Expand Down
30 changes: 23 additions & 7 deletions lua/precognition/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ local M = {}
---@field PrevParagraph Precognition.PlaceLoc
---@field NextParagraph Precognition.PlaceLoc

---@class Precognition.ExtraPadding
---@field start integer
---@field length integer

---@type Precognition.HintConfig
local defaultHintConfig = {
Caret = { text = "^", prio = 2 },
Expand Down Expand Up @@ -106,8 +110,9 @@ local gutter_group = "precognition_gutter"

---@param marks Precognition.VirtLine
---@param line_len integer
---@param extra_padding Precognition.ExtraPadding
---@return table
local function build_virt_line(marks, line_len)
local function build_virt_line(marks, line_len, extra_padding)
if not marks then
return {}
end
Expand All @@ -127,20 +132,26 @@ local function build_virt_line(marks, line_len)
if existing == " " and existing ~= hint then
line_table[col] = hint
else -- if the character is not a space, then we need to check the prio
local existingKey
local existing_key
for key, value in pairs(config.hints) do
if value.text == existing then
existingKey = key
existing_key = key
break
end
end
if existing ~= " " and config.hints[mark].prio > config.hints[existingKey].prio then
if existing ~= " " and config.hints[mark].prio > config.hints[existing_key].prio then
line_table[col] = hint
end
end
end
end

if #extra_padding > 0 then
for _, padding in ipairs(extra_padding) do
line_table[padding.start] = line_table[padding.start] .. string.rep(" ", padding.length)
end
end

local line = table.concat(line_table)
if line:match("^%s+$") then
return {}
Expand Down Expand Up @@ -201,15 +212,17 @@ local function display_marks()
if utils.is_blacklisted_buffer(bufnr) then
return
end
local cursorline, cursorcol = unpack(vim.api.nvim_win_get_cursor(0))
cursorcol = cursorcol + 1
local cursorline = vim.fn.line(".")
local cursorcol = vim.fn.charcol(".")
if extmark and not dirty then
return
end

local tab_width = vim.bo.expandtab and vim.bo.shiftwidth or vim.bo.tabstop
local cur_line = vim.api.nvim_get_current_line():gsub("\t", string.rep(" ", tab_width))
local line_len = vim.fn.strcharlen(cur_line)
---@type Precognition.ExtraPadding[]
local extra_padding = {}
-- local after_cursor = vim.fn.strcharpart(cur_line, cursorcol + 1)
-- local before_cursor = vim.fn.strcharpart(cur_line, 0, cursorcol - 1)
-- local before_cursor_rev = string.reverse(before_cursor)
Expand All @@ -232,7 +245,10 @@ local function display_marks()
Zero = 1,
}

local virt_line = build_virt_line(virtual_line_marks, line_len)
--multicharacter padding
utils.add_multibyte_padding(cur_line, extra_padding, line_len)

local virt_line = build_virt_line(virtual_line_marks, line_len, extra_padding)

-- TODO: can we add indent lines to the virt line to match indent-blankline or similar (if installed)?

Expand Down
14 changes: 14 additions & 0 deletions lua/precognition/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,18 @@ function M.create_pad_array(len, str)
return pad_array
end

---Add extra padding for multi byte character characters
---@param cur_line string
---@param extra_padding Precognition.ExtraPadding[]
---@param line_len integer
function M.add_multibyte_padding(cur_line, extra_padding, line_len)
for i = 1, line_len do
local char = vim.fn.strcharpart(cur_line, i - 1, 1)
local width = vim.fn.strdisplaywidth(char)
if width > 1 then
table.insert(extra_padding, { start = i, length = width - 1 })
end
end
end

return M
10 changes: 10 additions & 0 deletions tests/precognition/char_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ describe("big_word classing", function()
eq(utils.char_class("@", true), 1)
eq(utils.char_class(".", true), 1)
end)

it("can class emoji characters", function()
eq(utils.char_class("🐱", false), 1)
eq(utils.char_class("😸", false), 1)
end)

it("can class nerdfont characters", function()
eq(utils.char_class("", false), 1)
eq(utils.char_class("", false), 1)
end)
end)

describe("pad arrays", function()
Expand Down
9 changes: 9 additions & 0 deletions tests/precognition/horizontal_motions_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,13 @@ describe("edge case", function()
eq(16, hm.end_of_word(str, 15, #str, false))
eq(22, hm.end_of_word(str, 21, #str, false))
end)

it("multibyte characters", function()
local str = "# 💭👀precognition.nvim"
local len = vim.fn.strcharlen(str)
eq(3, hm.next_word_boundary(str, 1, len, false))
eq(17, hm.prev_word_boundary(str, 18, len, false))
eq(3, hm.prev_word_boundary(str, 18, len, true))
eq(3, hm.prev_word_boundary(str, 5, len, false))
end)
end)
Loading

0 comments on commit f893367

Please sign in to comment.