* fix(help_tags): show help tags on windows (#3126) On Windows, `builtin.help_tags` picker does not show any help tags. To fix this, the following changes are needed: 1. `util.path_tail` checks unix separator `/` on Windows and leave the original implementation intact on unix systems. 2. Line endings should be taken carefully on Windows. `vim.split` with only newline `\n` character as separator may result in unexpected crash when parsing large files. When splits on lines are needed, call it with `\r?\n`, or even set up a wrapper function in utils is more prefered. Fixes #3126 * fix: handle cross platform line splits
245 lines
6.5 KiB
Lua
245 lines
6.5 KiB
Lua
local ts_utils = require "telescope.utils"
|
||
local strings = require "plenary.strings"
|
||
local conf = require("telescope.config").values
|
||
|
||
local Job = require "plenary.job"
|
||
local Path = require "plenary.path"
|
||
|
||
local telescope_utils = require "telescope.utils"
|
||
|
||
local utils = {}
|
||
|
||
local detect_from_shebang = function(p)
|
||
local s = p:readbyterange(0, 256)
|
||
if s then
|
||
local lines = telescope_utils.split_lines(s)
|
||
return vim.filetype.match { contents = lines }
|
||
end
|
||
end
|
||
|
||
local parse_modeline = function(tail)
|
||
if tail:find "vim:" then
|
||
return tail:match ".*:ft=([^: ]*):.*$" or ""
|
||
end
|
||
end
|
||
|
||
local detect_from_modeline = function(p)
|
||
local s = p:readbyterange(-256, 256)
|
||
if s then
|
||
local lines = telescope_utils.split_lines(s)
|
||
local idx = lines[#lines] ~= "" and #lines or #lines - 1
|
||
if idx >= 1 then
|
||
return parse_modeline(lines[idx])
|
||
end
|
||
end
|
||
end
|
||
|
||
utils.filetype_detect = function(filepath)
|
||
if type(filepath) ~= string then
|
||
filepath = tostring(filepath)
|
||
end
|
||
|
||
local match = vim.filetype.match { filename = filepath }
|
||
if match and match ~= "" then
|
||
return match
|
||
end
|
||
|
||
local p = Path:new(filepath)
|
||
if p and p:is_file() then
|
||
match = detect_from_shebang(p)
|
||
if match and match ~= "" then
|
||
return match
|
||
end
|
||
|
||
match = detect_from_modeline(p)
|
||
if match and match ~= "" then
|
||
return match
|
||
end
|
||
end
|
||
end
|
||
|
||
-- API helper functions for buffer previewer
|
||
--- Job maker for buffer previewer
|
||
utils.job_maker = function(cmd, bufnr, opts)
|
||
opts = opts or {}
|
||
opts.mode = opts.mode or "insert"
|
||
-- bufname and value are optional
|
||
-- if passed, they will be use as the cache key
|
||
-- if any of them are missing, cache will be skipped
|
||
if opts.bufname ~= opts.value or not opts.bufname or not opts.value then
|
||
local command = table.remove(cmd, 1)
|
||
local writer = (function()
|
||
if opts.writer ~= nil then
|
||
local wcommand = table.remove(opts.writer, 1)
|
||
return Job:new {
|
||
command = wcommand,
|
||
args = opts.writer,
|
||
env = opts.env,
|
||
cwd = opts.cwd,
|
||
}
|
||
end
|
||
end)()
|
||
|
||
Job:new({
|
||
command = command,
|
||
args = cmd,
|
||
env = opts.env,
|
||
cwd = opts.cwd,
|
||
writer = writer,
|
||
on_exit = vim.schedule_wrap(function(j)
|
||
if not vim.api.nvim_buf_is_valid(bufnr) then
|
||
return
|
||
end
|
||
if opts.mode == "append" then
|
||
vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, j:result())
|
||
elseif opts.mode == "insert" then
|
||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, j:result())
|
||
end
|
||
if opts.callback then
|
||
opts.callback(bufnr, j:result())
|
||
end
|
||
end),
|
||
}):start()
|
||
else
|
||
if opts.callback then
|
||
opts.callback(bufnr)
|
||
end
|
||
end
|
||
end
|
||
|
||
local function has_filetype(ft)
|
||
return ft and ft ~= ""
|
||
end
|
||
|
||
--- Attach default highlighter which will choose between regex and ts
|
||
utils.highlighter = function(bufnr, ft, opts)
|
||
opts = vim.F.if_nil(opts, {})
|
||
opts.preview = vim.F.if_nil(opts.preview, {})
|
||
opts.preview.treesitter = (function()
|
||
if type(opts.preview) == "table" and opts.preview.treesitter then
|
||
return opts.preview.treesitter
|
||
end
|
||
if type(conf.preview) == "table" and conf.preview.treesitter then
|
||
return conf.preview.treesitter
|
||
end
|
||
if type(conf.preview) == "boolean" then
|
||
return conf.preview
|
||
end
|
||
-- We should never get here
|
||
return false
|
||
end)()
|
||
|
||
if type(opts.preview.treesitter) == "boolean" then
|
||
local temp = { enable = opts.preview.treesitter }
|
||
opts.preview.treesitter = temp
|
||
end
|
||
|
||
local ts_highlighting = (function()
|
||
if type(opts.preview.treesitter.enable) == "table" then
|
||
if vim.tbl_contains(opts.preview.treesitter.enable, ft) then
|
||
return true
|
||
end
|
||
return false
|
||
end
|
||
|
||
if vim.tbl_contains(vim.F.if_nil(opts.preview.treesitter.disable, {}), ft) then
|
||
return false
|
||
end
|
||
|
||
return opts.preview.treesitter.enable == nil or opts.preview.treesitter.enable == true
|
||
end)()
|
||
|
||
local ts_success
|
||
if ts_highlighting then
|
||
ts_success = utils.ts_highlighter(bufnr, ft)
|
||
end
|
||
if not ts_highlighting or ts_success == false then
|
||
utils.regex_highlighter(bufnr, ft)
|
||
end
|
||
end
|
||
|
||
--- Attach regex highlighter
|
||
utils.regex_highlighter = function(bufnr, ft)
|
||
if has_filetype(ft) then
|
||
return pcall(vim.api.nvim_buf_set_option, bufnr, "syntax", ft)
|
||
end
|
||
return false
|
||
end
|
||
|
||
-- Attach ts highlighter
|
||
utils.ts_highlighter = function(bufnr, ft)
|
||
if has_filetype(ft) then
|
||
local lang = vim.treesitter.language.get_lang(ft) or ft
|
||
if lang and ts_utils.has_ts_parser(lang) then
|
||
return vim.treesitter.start(bufnr, lang)
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
utils.set_preview_message = function(bufnr, winid, message, fillchar)
|
||
fillchar = vim.F.if_nil(fillchar, "╱")
|
||
local height = vim.api.nvim_win_get_height(winid)
|
||
local width = vim.api.nvim_win_get_width(winid)
|
||
vim.api.nvim_buf_set_lines(
|
||
bufnr,
|
||
0,
|
||
-1,
|
||
false,
|
||
ts_utils.repeated_table(height, table.concat(ts_utils.repeated_table(width, fillchar), ""))
|
||
)
|
||
local anon_ns = vim.api.nvim_create_namespace ""
|
||
local padding = table.concat(ts_utils.repeated_table(#message + 4, " "), "")
|
||
local formatted_message = " " .. message .. " "
|
||
-- Populate lines table based on height
|
||
local lines = {}
|
||
if height == 1 then
|
||
lines[1] = formatted_message
|
||
else
|
||
for i = 1, math.min(height, 3), 1 do
|
||
if i % 2 == 0 then
|
||
lines[i] = formatted_message
|
||
else
|
||
lines[i] = padding
|
||
end
|
||
end
|
||
end
|
||
vim.api.nvim_buf_set_extmark(
|
||
bufnr,
|
||
anon_ns,
|
||
0,
|
||
0,
|
||
{ end_line = height, hl_group = "TelescopePreviewMessageFillchar" }
|
||
)
|
||
local col = math.floor((width - strings.strdisplaywidth(formatted_message)) / 2)
|
||
for i, line in ipairs(lines) do
|
||
local line_pos = math.floor(height / 2) - 2 + i
|
||
vim.api.nvim_buf_set_extmark(
|
||
bufnr,
|
||
anon_ns,
|
||
math.max(line_pos, 0),
|
||
0,
|
||
{ virt_text = { { line, "TelescopePreviewMessage" } }, virt_text_pos = "overlay", virt_text_win_col = col }
|
||
)
|
||
end
|
||
end
|
||
|
||
--- Check if mime type is binary.
|
||
--- NOT an exhaustive check, may get false negatives. Ideally should check
|
||
--- filetype with `vim.filetype.match` or `filetype_detect` first for filetype
|
||
--- info.
|
||
---@param mime_type string
|
||
---@return boolean
|
||
utils.binary_mime_type = function(mime_type)
|
||
local type_, subtype = unpack(vim.split(mime_type, "/"))
|
||
if vim.tbl_contains({ "text", "inode" }, type_) then
|
||
return false
|
||
end
|
||
if vim.tbl_contains({ "json", "javascript" }, subtype) then
|
||
return false
|
||
end
|
||
return true
|
||
end
|
||
|
||
return utils
|