Skip to content

Commit ec72152

Browse files
committed
fix(previewer): improve file_maker line splitting and timeouts
1 parent 5063384 commit ec72152

4 files changed

Lines changed: 104 additions & 31 deletions

File tree

lua/telescope/previewers/buffer_previewer.lua

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,6 @@ local function defaulter(f, default_opts)
6060
}
6161
end
6262

63-
-- modified vim.split to incorporate a timer
64-
local function split(s, sep, plain, opts)
65-
opts = opts or {}
66-
local t = {}
67-
for c in vim.gsplit(s, sep, plain) do
68-
local line = opts.file_encoding and vim.iconv(c, opts.file_encoding, "utf8") or c
69-
table.insert(t, line)
70-
if opts.preview.timeout then
71-
local diff_time = (vim.uv.hrtime() - opts.start_time) / 1e6
72-
if diff_time > opts.preview.timeout then
73-
return
74-
end
75-
end
76-
end
77-
if t[#t] == "" then
78-
t[#t] = nil
79-
end
80-
return t
81-
end
8263
local bytes_to_megabytes = math.pow(1024, 2)
8364

8465
local color_hash = {
@@ -205,7 +186,7 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
205186
if not api.nvim_buf_is_valid(bufnr) then
206187
return
207188
end
208-
local processed_data = split(data, "[\r]?\n", nil, opts)
189+
local processed_data = putils.timed_split_lines(data, opts)
209190

210191
if processed_data then
211192
local ok = pcall(api.nvim_buf_set_lines, bufnr, 0, -1, false, processed_data)

lua/telescope/previewers/utils.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,49 @@ utils.binary_mime_type = function(mime_type)
235235
return true
236236
end
237237

238+
local CHECK_TIME_INTERVAL = 200
239+
240+
--- Split a string into lines, checking every `CHECK_TIME_INTERVAL` characters
241+
--- whether to timeout.
242+
---
243+
--- Roughly 4-5x faster than using `vim.gsplit` and checking timeout between each line.
244+
--- The latter approach is also more prone to exceeding timeout if a file has huge lines.
245+
---@param s string file content to split into lines
246+
---@param opts {start_time: number, preview: { timeout: number }, file_encoding: string?}
247+
---@return string[]?
248+
function utils.timed_split_lines(s, opts)
249+
local lines = {}
250+
local line_start = 1
251+
252+
for i = 1, #s do
253+
local ch = s:byte(i)
254+
if ch == 10 then
255+
local line
256+
if s:byte(i - 1) ~= 13 then
257+
line = s:sub(line_start, i - 1)
258+
else
259+
line = s:sub(line_start, i - 2)
260+
end
261+
line_start = i + 1
262+
table.insert(lines, opts.file_encoding and vim.iconv(line, opts.file_encoding, "utf8") or line)
263+
end
264+
265+
if i % CHECK_TIME_INTERVAL == 0 then
266+
local diff_time = (vim.uv.hrtime() - opts.start_time) / 1e6
267+
if diff_time > opts.preview.timeout then
268+
return
269+
end
270+
end
271+
end
272+
273+
-- Only append the tail when it is non-empty.
274+
-- neovim treats \n and \r\n as "line terminators" instead of "line separator"
275+
local tail = s:sub(line_start)
276+
if tail ~= "" then
277+
table.insert(lines, opts.file_encoding and vim.iconv(tail, opts.file_encoding, "utf8") or tail)
278+
end
279+
280+
return lines
281+
end
282+
238283
return utils

lua/telescope/utils.lua

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -766,16 +766,8 @@ utils.reverse_table = function(input_table)
766766
return temp_table
767767
end
768768

769-
utils.split_lines = (function()
770-
if utils.iswin then
771-
return function(s, opts)
772-
return vim.split(s, "\r?\n", opts)
773-
end
774-
else
775-
return function(s, opts)
776-
return vim.split(s, "\n", opts)
777-
end
778-
end
779-
end)()
769+
utils.split_lines = function(s, opts)
770+
return vim.split(s, "\r?\n", opts)
771+
end
780772

781773
return utils
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
local putils = require "telescope.previewers.utils"
2+
local utils = require "telescope.utils"
3+
4+
describe("timed_split_lines", function()
5+
local expect = {
6+
"",
7+
"",
8+
"line3 of the file",
9+
"",
10+
"line5 of the file",
11+
"",
12+
"",
13+
"line8 of the file, last line of file",
14+
}
15+
16+
local split_lines = function(s)
17+
return putils.timed_split_lines(s, {
18+
start_time = vim.uv.hrtime(),
19+
preview = {
20+
timeout = 250, -- should be more than enough time
21+
},
22+
})
23+
end
24+
25+
if utils.iswin then
26+
describe("handles files on Windows", function()
27+
it("reads file ending with \\r\\n (standard Windows line terminator)", function()
28+
local file = table.concat(expect, "\r\n") .. "\r\n"
29+
assert.are.same(expect, split_lines(file))
30+
end)
31+
32+
it("reads file ending with \\n only", function()
33+
local file = table.concat(expect, "\n") .. "\n"
34+
assert.are.same(expect, split_lines(file))
35+
end)
36+
37+
it("reads file with no trailing newline", function()
38+
local file = table.concat(expect, "\r\n")
39+
assert.are.same(expect, split_lines(file))
40+
end)
41+
end)
42+
else
43+
describe("handles files on non Windows environment", function()
44+
it("reads file ending with \\n (standard Unix line terminator)", function()
45+
local file = table.concat(expect, "\n") .. "\n"
46+
assert.are.same(expect, split_lines(file))
47+
end)
48+
49+
it("reads file with no trailing newline", function()
50+
local file = table.concat(expect, "\n")
51+
assert.are.same(expect, split_lines(file))
52+
end)
53+
end)
54+
end
55+
end)

0 commit comments

Comments
 (0)