summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJINNOUCHI Yasushi <me@delphinus.dev>2021-01-31 18:55:17 +0900
committerGitHub <noreply@github.com>2021-01-31 10:55:17 +0100
commit1ca1e7ccba203752f7b7f89c2bd3aca13586d460 (patch)
tree18dca4487c1a34759b3e7a26a6e8e2c93a1a767f
parent4e0dfa2e705cd34b315315ed4740683988ef5403 (diff)
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 https://github.com/neovim/neovim/blob/a1ed941a7881122fda2fd48e71e890ed55e4d08e/src/nvim/eval/funcs.c#L9845-L9858 Co-authored-by: Simon Hauser <Simon-Hauser@outlook.de>
-rw-r--r--lua/telescope/make_entry.lua3
-rw-r--r--lua/telescope/pickers/entry_display.lua30
-rw-r--r--lua/telescope/utils.lua82
-rw-r--r--lua/tests/automated/entry_display_spec.lua32
4 files changed, 138 insertions, 9 deletions
diff --git a/lua/telescope/make_entry.lua b/lua/telescope/make_entry.lua
index bd1bf73..d23dcb7 100644
--- a/lua/telescope/make_entry.lua
+++ b/lua/telescope/make_entry.lua
@@ -463,7 +463,8 @@ function make_entry.gen_from_buffer(opts)
local icon_width = 0
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
local displayer = entry_display.create {
diff --git a/lua/telescope/pickers/entry_display.lua b/lua/telescope/pickers/entry_display.lua
index c7a51ac..7e38ac8 100644
--- a/lua/telescope/pickers/entry_display.lua
+++ b/lua/telescope/pickers/entry_display.lua
@@ -1,13 +1,27 @@
+local utils = require('telescope.utils')
+
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
- -- TODO: This doesn't handle multi byte chars...
- if vim.fn.strdisplaywidth(str) > len then
- str = str:sub(1, len - 1)
- str = str .. "…"
+ if utils.strdisplaywidth(str) <= len then
+ return str
+ end
+ 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 str
+ return result
end
entry_display.create = function(configuration)
@@ -18,9 +32,9 @@ entry_display.create = function(configuration)
local format_str = "%" .. justify .. v.width .. "s"
table.insert(generator, function(item)
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
- return string.format(format_str, truncate(item, v.width))
+ return string.format(format_str, entry_display.truncate(item, v.width))
end
end)
else
diff --git a/lua/telescope/utils.lua b/lua/telescope/utils.lua
index 913c9a2..9cfc23c 100644
--- a/lua/telescope/utils.lua
+++ b/lua/telescope/utils.lua
@@ -202,4 +202,86 @@ function utils.get_os_command_output(cmd)
return Job:new({ command = command, args = cmd }):sync()
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
diff --git a/lua/tests/automated/entry_display_spec.lua b/lua/tests/automated/entry_display_spec.lua
new file mode 100644
index 0000000..fff78cd
--- /dev/null
+++ b/lua/tests/automated/entry_display_spec.lua
@@ -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)