fix: Multi byte truncate for displayer (#464)
This is needed for calling strdisplaywidth() from Lua loop.
See https://github.com/nvim-telescope/telescope.nvim/issues/414
See a1ed941a78/src/nvim/eval/funcs.c (L9845-L9858)
Co-authored-by: Simon Hauser <Simon-Hauser@outlook.de>
This commit is contained in:
committed by
GitHub
parent
4e0dfa2e70
commit
1ca1e7ccba
@@ -463,7 +463,8 @@ function make_entry.gen_from_buffer(opts)
|
|||||||
|
|
||||||
local icon_width = 0
|
local icon_width = 0
|
||||||
if not disable_devicons then
|
if not disable_devicons then
|
||||||
icon_width = vim.fn.strdisplaywidth(get_devicons('fname', disable_devicons))
|
local icon, _ = get_devicons('fname', disable_devicons)
|
||||||
|
icon_width = utils.strdisplaywidth(icon)
|
||||||
end
|
end
|
||||||
|
|
||||||
local displayer = entry_display.create {
|
local displayer = entry_display.create {
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
|
local utils = require('telescope.utils')
|
||||||
|
|
||||||
local entry_display = {}
|
local entry_display = {}
|
||||||
|
|
||||||
local function truncate(str, len)
|
entry_display.truncate = function(str, len)
|
||||||
str = tostring(str) -- We need to make sure its an actually a string and not a number
|
str = tostring(str) -- We need to make sure its an actually a string and not a number
|
||||||
-- TODO: This doesn't handle multi byte chars...
|
if utils.strdisplaywidth(str) <= len then
|
||||||
if vim.fn.strdisplaywidth(str) > len then
|
return str
|
||||||
str = str:sub(1, len - 1)
|
|
||||||
str = str .. "…"
|
|
||||||
end
|
end
|
||||||
return str
|
local charlen = 0
|
||||||
|
local cur_len = 0
|
||||||
|
local result = ''
|
||||||
|
local len_of_dots = utils.strdisplaywidth('…')
|
||||||
|
while true do
|
||||||
|
local part = utils.strcharpart(str, charlen, 1)
|
||||||
|
cur_len = cur_len + utils.strdisplaywidth(part)
|
||||||
|
if (cur_len + len_of_dots) > len then
|
||||||
|
result = result .. '…'
|
||||||
|
break
|
||||||
|
end
|
||||||
|
result = result .. part
|
||||||
|
charlen = charlen + 1
|
||||||
|
end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
entry_display.create = function(configuration)
|
entry_display.create = function(configuration)
|
||||||
@@ -18,9 +32,9 @@ entry_display.create = function(configuration)
|
|||||||
local format_str = "%" .. justify .. v.width .. "s"
|
local format_str = "%" .. justify .. v.width .. "s"
|
||||||
table.insert(generator, function(item)
|
table.insert(generator, function(item)
|
||||||
if type(item) == 'table' then
|
if type(item) == 'table' then
|
||||||
return string.format(format_str, truncate(item[1], v.width)), item[2]
|
return string.format(format_str, entry_display.truncate(item[1], v.width)), item[2]
|
||||||
else
|
else
|
||||||
return string.format(format_str, truncate(item, v.width))
|
return string.format(format_str, entry_display.truncate(item, v.width))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -202,4 +202,86 @@ function utils.get_os_command_output(cmd)
|
|||||||
return Job:new({ command = command, args = cmd }):sync()
|
return Job:new({ command = command, args = cmd }):sync()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
utils.strdisplaywidth = (function()
|
||||||
|
if jit then
|
||||||
|
local ffi = require('ffi')
|
||||||
|
ffi.cdef[[
|
||||||
|
typedef unsigned char char_u;
|
||||||
|
int linetabsize_col(int startcol, char_u *s);
|
||||||
|
]]
|
||||||
|
|
||||||
|
return function(str, col)
|
||||||
|
local startcol = col or 0
|
||||||
|
local s = ffi.new('char[?]', #str + 1)
|
||||||
|
ffi.copy(s, str)
|
||||||
|
return ffi.C.linetabsize_col(startcol, s) - startcol
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return function(str, col)
|
||||||
|
return #str - (col or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)()
|
||||||
|
|
||||||
|
utils.utf_ptr2len = (function()
|
||||||
|
if jit then
|
||||||
|
local ffi = require('ffi')
|
||||||
|
ffi.cdef[[
|
||||||
|
typedef unsigned char char_u;
|
||||||
|
int utf_ptr2len(const char_u *const p);
|
||||||
|
]]
|
||||||
|
|
||||||
|
return function(str)
|
||||||
|
local c_str = ffi.new('char[?]', #str + 1)
|
||||||
|
ffi.copy(c_str, str)
|
||||||
|
return ffi.C.utf_ptr2len(c_str)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return function(str)
|
||||||
|
return str == '' and 0 or 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)()
|
||||||
|
|
||||||
|
function utils.strcharpart(str, nchar, charlen)
|
||||||
|
local nbyte = 0
|
||||||
|
if nchar > 0 then
|
||||||
|
while nchar > 0 and nbyte < #str do
|
||||||
|
nbyte = nbyte + utils.utf_ptr2len(str:sub(nbyte + 1))
|
||||||
|
nchar = nchar - 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
nbyte = nchar
|
||||||
|
end
|
||||||
|
|
||||||
|
local len = 0
|
||||||
|
if charlen then
|
||||||
|
while charlen > 0 and nbyte + len < #str do
|
||||||
|
local off = nbyte + len
|
||||||
|
if off < 0 then
|
||||||
|
len = len + 1
|
||||||
|
else
|
||||||
|
len = len + utils.utf_ptr2len(str:sub(off + 1))
|
||||||
|
end
|
||||||
|
charlen = charlen - 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
len = #str - nbyte
|
||||||
|
end
|
||||||
|
|
||||||
|
if nbyte < 0 then
|
||||||
|
len = len + nbyte
|
||||||
|
nbyte = 0
|
||||||
|
elseif nbyte > #str then
|
||||||
|
nbyte = #str
|
||||||
|
end
|
||||||
|
if len < 0 then
|
||||||
|
len = 0
|
||||||
|
elseif nbyte + len > #str then
|
||||||
|
len = #str - nbyte
|
||||||
|
end
|
||||||
|
|
||||||
|
return str:sub(nbyte + 1, nbyte + len)
|
||||||
|
end
|
||||||
|
|
||||||
return utils
|
return utils
|
||||||
|
|||||||
32
lua/tests/automated/entry_display_spec.lua
Normal file
32
lua/tests/automated/entry_display_spec.lua
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
local entry_display = require('telescope.pickers.entry_display')
|
||||||
|
|
||||||
|
describe('truncate', function()
|
||||||
|
for _, ambiwidth in ipairs{'single', 'double'} do
|
||||||
|
for _, case in ipairs{
|
||||||
|
{args = {'abcde', 6}, expected = {single = 'abcde', double = 'abcde'}},
|
||||||
|
{args = {'abcde', 5}, expected = {single = 'abcde', double = 'abcde'}},
|
||||||
|
{args = {'abcde', 4}, expected = {single = 'abc…', double = 'ab…'}},
|
||||||
|
{args = {'アイウエオ', 11}, expected = {single = 'アイウエオ', double = 'アイウエオ'}},
|
||||||
|
{args = {'アイウエオ', 10}, expected = {single = 'アイウエオ', double = 'アイウエオ'}},
|
||||||
|
{args = {'アイウエオ', 9}, expected = {single = 'アイウエ…', double = 'アイウ…'}},
|
||||||
|
{args = {'アイウエオ', 8}, expected = {single = 'アイウ…', double = 'アイウ…'}},
|
||||||
|
{args = {'├─┤', 7}, expected = {single = '├─┤', double = '├─┤'}},
|
||||||
|
{args = {'├─┤', 6}, expected = {single = '├─┤', double = '├─┤'}},
|
||||||
|
{args = {'├─┤', 5}, expected = {single = '├─┤', double = '├…'}},
|
||||||
|
{args = {'├─┤', 4}, expected = {single = '├─┤', double = '├…'}},
|
||||||
|
{args = {'├─┤', 3}, expected = {single = '├─┤', double = '…'}},
|
||||||
|
{args = {'├─┤', 2}, expected = {single = '├…', double = '…'}},
|
||||||
|
} do
|
||||||
|
local msg = ('can truncate: ambiwidth = %s, [%s, %d] -> %s'):format(ambiwidth, case.args[1], case.args[2], case.expected[ambiwidth])
|
||||||
|
it(msg, function()
|
||||||
|
local original = vim.o.ambiwidth
|
||||||
|
vim.o.ambiwidth = ambiwidth
|
||||||
|
assert.are.same(
|
||||||
|
case.expected[ambiwidth],
|
||||||
|
entry_display.truncate(case.args[1], case.args[2])
|
||||||
|
)
|
||||||
|
vim.o.ambiwidth = original
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user