mirror of
https://github.com/nvim-treesitter/nvim-treesitter-context
synced 2024-09-16 14:14:03 +02:00
implement highlights for partial nodes
This commit is contained in:
parent
ee02ba1da7
commit
81474aff59
2 changed files with 99 additions and 49 deletions
|
@ -1,6 +1,8 @@
|
||||||
|
|
||||||
local vim = vim
|
local vim = vim
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
|
local ts = vim.treesitter
|
||||||
|
local Highlighter = ts.highlighter
|
||||||
local ts_utils = require'nvim-treesitter.ts_utils'
|
local ts_utils = require'nvim-treesitter.ts_utils'
|
||||||
local parsers = require'nvim-treesitter.parsers'
|
local parsers = require'nvim-treesitter.parsers'
|
||||||
|
|
||||||
|
@ -8,27 +10,30 @@ local parsers = require'nvim-treesitter.parsers'
|
||||||
|
|
||||||
local winid = nil
|
local winid = nil
|
||||||
local bufnr = api.nvim_create_buf(false, true)
|
local bufnr = api.nvim_create_buf(false, true)
|
||||||
local label = nil
|
local ns = api.nvim_create_namespace('nvim-treesitter-context')
|
||||||
|
local current_node = nil
|
||||||
|
|
||||||
|
|
||||||
-- Helper functions
|
-- Helper functions
|
||||||
|
|
||||||
local get_line_for_node = function(node, type_patterns, transform_fn)
|
local is_valid = function(node, type_patterns)
|
||||||
local node_type = node:type()
|
local node_type = node:type()
|
||||||
local is_valid = false
|
|
||||||
for _, rgx in ipairs(type_patterns) do
|
for _, rgx in ipairs(type_patterns) do
|
||||||
if node_type:find(rgx) then
|
if node_type:find(rgx) then
|
||||||
is_valid = true
|
return true
|
||||||
break
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
if not is_valid then return '' end
|
local get_text_for_node = function(node)
|
||||||
|
return ts_utils.get_node_text(node)[1]
|
||||||
|
end
|
||||||
|
|
||||||
-- local node_text = transform_fn(ts_utils.get_node_text(node)[1] or '')
|
local get_lines_for_node = function(node)
|
||||||
local line = api.nvim_buf_get_lines(0, node:start(), node:start() + 1, false)[1]
|
local start_row = node:start()
|
||||||
|
local end_row = node:end_()
|
||||||
return line
|
return api.nvim_buf_get_lines(0, start_row, end_row + 1, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Trim spaces and opening brackets from end
|
-- Trim spaces and opening brackets from end
|
||||||
|
@ -36,34 +41,6 @@ local transform_line = function(line)
|
||||||
return line:gsub('%s*[%[%(%{]*%s*$', ''):gsub('\n', '')
|
return line:gsub('%s*[%[%(%{]*%s*$', ''):gsub('\n', '')
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_context = function(opts)
|
|
||||||
if not parsers.has_parser() then return nil end
|
|
||||||
local options = opts or {}
|
|
||||||
local type_patterns = options.type_patterns or {'class', 'function', 'method'}
|
|
||||||
local transform_fn = options.transform_fn or transform_line
|
|
||||||
local separator = options.separator or ' -> '
|
|
||||||
|
|
||||||
local current_node = ts_utils.get_node_at_cursor()
|
|
||||||
if not current_node then return nil end
|
|
||||||
|
|
||||||
local matches = {}
|
|
||||||
local expr = current_node
|
|
||||||
|
|
||||||
while expr do
|
|
||||||
local line = get_line_for_node(expr, type_patterns, transform_fn)
|
|
||||||
if line ~= '' and not vim.tbl_contains(matches, line) then
|
|
||||||
table.insert(matches, 1, { expr:start(), line })
|
|
||||||
end
|
|
||||||
expr = expr:parent()
|
|
||||||
end
|
|
||||||
|
|
||||||
if #matches == 0 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return matches
|
|
||||||
end
|
|
||||||
|
|
||||||
local get_gutter_width = function()
|
local get_gutter_width = function()
|
||||||
local old_col = api.nvim_call_function('col', { '.' })
|
local old_col = api.nvim_call_function('col', { '.' })
|
||||||
api.nvim_call_function('cursor', { 0, 1 })
|
api.nvim_call_function('cursor', { 0, 1 })
|
||||||
|
@ -76,41 +53,71 @@ local nvim_augroup = function(group_name, definitions)
|
||||||
api.nvim_command('augroup ' .. group_name)
|
api.nvim_command('augroup ' .. group_name)
|
||||||
api.nvim_command('autocmd!')
|
api.nvim_command('autocmd!')
|
||||||
for _, def in ipairs(definitions) do
|
for _, def in ipairs(definitions) do
|
||||||
local command = table.concat(vim.tbl_flatten{'autocmd', def}, ' ')
|
local command = table.concat({'autocmd', unpack(def)}, ' ')
|
||||||
api.nvim_command(command)
|
if api.nvim_call_function('exists', {'#' .. def[1]}) then
|
||||||
|
api.nvim_command(command)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
api.nvim_command('augroup END')
|
api.nvim_command('augroup END')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Exports
|
-- Exports
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
function M.get_context(opts)
|
||||||
|
if not parsers.has_parser() then return nil end
|
||||||
|
local options = opts or {}
|
||||||
|
local type_patterns = options.type_patterns or {'class', 'function', 'method'}
|
||||||
|
local transform_fn = options.transform_fn or transform_line
|
||||||
|
local separator = options.separator or ' -> '
|
||||||
|
|
||||||
|
local current_node = ts_utils.get_node_at_cursor()
|
||||||
|
if not current_node then return nil end
|
||||||
|
|
||||||
|
local matches = {}
|
||||||
|
local expr = current_node
|
||||||
|
|
||||||
|
while expr do
|
||||||
|
if is_valid(expr, type_patterns) then
|
||||||
|
table.insert(matches, 1, expr)
|
||||||
|
end
|
||||||
|
expr = expr:parent()
|
||||||
|
end
|
||||||
|
|
||||||
|
if #matches == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return matches
|
||||||
|
end
|
||||||
|
|
||||||
function M.update_context()
|
function M.update_context()
|
||||||
if api.nvim_get_option('buftype') ~= '' then
|
if api.nvim_get_option('buftype') ~= '' then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local context = get_context()
|
local context = M.get_context()
|
||||||
|
|
||||||
label = nil
|
current_node = nil
|
||||||
|
|
||||||
if context then
|
if context then
|
||||||
local first_visible_line = api.nvim_call_function('line', { 'w0' })
|
local first_visible_line = api.nvim_call_function('line', { 'w0' })
|
||||||
|
|
||||||
for i = #context, 1, -1 do
|
for i = #context, 1, -1 do
|
||||||
local match = context[i]
|
local node = context[i]
|
||||||
local line_number, text = unpack(match)
|
local row = node:start()
|
||||||
|
|
||||||
if line_number < (first_visible_line - 1) then
|
if row < (first_visible_line - 1) then
|
||||||
label = text
|
current_node = node
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if label then
|
if current_node then
|
||||||
M.open()
|
M.open()
|
||||||
else
|
else
|
||||||
M.close()
|
M.close()
|
||||||
|
@ -132,6 +139,10 @@ function M.close()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.open()
|
function M.open()
|
||||||
|
local saved_bufnr = api.nvim_get_current_buf()
|
||||||
|
local start_row = current_node:start()
|
||||||
|
local end_row = current_node:end_()
|
||||||
|
|
||||||
if winid == nil or not api.nvim_win_is_valid(winid) then
|
if winid == nil or not api.nvim_win_is_valid(winid) then
|
||||||
local gutter_width = get_gutter_width()
|
local gutter_width = get_gutter_width()
|
||||||
local win_width = api.nvim_win_get_width(0) - gutter_width
|
local win_width = api.nvim_win_get_width(0) - gutter_width
|
||||||
|
@ -147,11 +158,50 @@ function M.open()
|
||||||
})
|
})
|
||||||
-- else
|
-- else
|
||||||
-- api.nvim_win_set_config(winid, {
|
-- api.nvim_win_set_config(winid, {
|
||||||
-- width = #label,
|
-- width = #current_node,
|
||||||
-- })
|
-- })
|
||||||
end
|
end
|
||||||
|
|
||||||
api.nvim_buf_set_lines(bufnr, 0, -1, false, { label })
|
local start_row, start_col = current_node:start()
|
||||||
|
local lines =
|
||||||
|
start_col == 0
|
||||||
|
and vim.split(get_text_for_node(current_node), '\n')
|
||||||
|
or get_lines_for_node(current_node)
|
||||||
|
local target_node =
|
||||||
|
start_col == 0
|
||||||
|
and current_node
|
||||||
|
or current_node:parent()
|
||||||
|
|
||||||
|
api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
|
||||||
|
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
|
|
||||||
|
local start_row_absolute = current_node:start()
|
||||||
|
|
||||||
|
for _, highlighter in pairs(Highlighter.active[saved_bufnr] or {}) do
|
||||||
|
local iter = highlighter.query:iter_captures(target_node, saved_bufnr, start_row, end_row)
|
||||||
|
for capture, node in iter do
|
||||||
|
|
||||||
|
local start_row, start_col, end_row, end_col = node:range()
|
||||||
|
local hl = highlighter.hl_cache[capture]
|
||||||
|
|
||||||
|
if start_row >= start_row_absolute then
|
||||||
|
|
||||||
|
start_row = start_row - start_row_absolute
|
||||||
|
end_row = end_row - start_row_absolute
|
||||||
|
|
||||||
|
-- Sometimes there is an error :/
|
||||||
|
-- but we ignore it :)
|
||||||
|
-- Yay?
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col,
|
||||||
|
{ end_line = end_row, end_col = end_col,
|
||||||
|
hl_group = hl,
|
||||||
|
-- ephemeral = true
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.enable()
|
function M.enable()
|
||||||
|
|
BIN
static/demo.gif
BIN
static/demo.gif
Binary file not shown.
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 995 KiB |
Loading…
Reference in a new issue