Skip to content

Commit 18a8b8d

Browse files
committed
fix(previewer): improve file_maker line splitting and timeouts
1 parent 5972437 commit 18a8b8d

4 files changed

Lines changed: 95 additions & 28 deletions

File tree

lua/telescope/previewers/buffer_previewer.lua

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,6 @@ local function defaulter(f, default_opts)
5656
}
5757
end
5858

59-
-- modified vim.split to incorporate a timer
60-
local function split(s, sep, plain, opts)
61-
opts = opts or {}
62-
local t = {}
63-
for c in vim.gsplit(s, sep, plain) do
64-
local line = opts.file_encoding and vim.iconv(c, opts.file_encoding, "utf8") or c
65-
table.insert(t, line)
66-
if opts.preview.timeout then
67-
local diff_time = (vim.loop.hrtime() - opts.start_time) / 1e6
68-
if diff_time > opts.preview.timeout then
69-
return
70-
end
71-
end
72-
end
73-
return t
74-
end
7559
local bytes_to_megabytes = math.pow(1024, 2)
7660

7761
local color_hash = {
@@ -199,7 +183,7 @@ local handle_file_preview = function(filepath, bufnr, stat, opts)
199183
if not vim.api.nvim_buf_is_valid(bufnr) then
200184
return
201185
end
202-
local processed_data = split(data, "[\r]?\n", nil, opts)
186+
local processed_data = putils.timed_split_lines(data, opts)
203187

204188
if processed_data then
205189
local ok = pcall(vim.api.nvim_buf_set_lines, bufnr, 0, -1, false, processed_data)

lua/telescope/previewers/utils.lua

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,46 @@ utils.binary_mime_type = function(mime_type)
241241
return true
242242
end
243243

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

lua/telescope/utils.lua

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -776,16 +776,8 @@ utils.reverse_table = function(input_table)
776776
return temp_table
777777
end
778778

779-
utils.split_lines = (function()
780-
if utils.iswin then
781-
return function(s, opts)
782-
return vim.split(s, "\r?\n", opts)
783-
end
784-
else
785-
return function(s, opts)
786-
return vim.split(s, "\n", opts)
787-
end
788-
end
789-
end)()
779+
utils.split_lines = function(s, opts)
780+
return vim.split(s, "\r?\n", opts)
781+
end
790782

791783
return utils
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
17+
local function get_fake_file(line_ending)
18+
return table.concat(expect, line_ending)
19+
end
20+
21+
local newline_file = get_fake_file "\n"
22+
local carriage_newline_file = get_fake_file "\r\n"
23+
24+
local split_lines = function(s)
25+
return putils.timed_split_lines(s, {
26+
start_time = vim.loop.hrtime(),
27+
preview = {
28+
timeout = 250, -- should be more than enough time
29+
},
30+
})
31+
end
32+
33+
if utils.iswin then
34+
describe("handles files on Windows", function()
35+
it("reads file with newline only", function()
36+
assert.are.same(expect, split_lines(newline_file))
37+
end)
38+
it("reads file with carriage return and newline", function()
39+
assert.are.same(expect, split_lines(carriage_newline_file))
40+
end)
41+
end)
42+
else
43+
describe("handles files on non Windows environment", function()
44+
it("reads file with newline only", function()
45+
assert.are.same(expect, split_lines(newline_file))
46+
end)
47+
end)
48+
end
49+
end)

0 commit comments

Comments
 (0)