322 lines
9.2 KiB
Lua
322 lines
9.2 KiB
Lua
local M = {}
|
|
|
|
local timers = {}
|
|
local references = {}
|
|
local paused_bufs = {}
|
|
|
|
-- returns r1 < r2 based on start of range
|
|
local function before_by_start(r1, r2)
|
|
if r1['start'].line < r2['start'].line then return true end
|
|
if r2['start'].line < r1['start'].line then return false end
|
|
if r1['start'].character < r2['start'].character then return true end
|
|
return false
|
|
end
|
|
|
|
-- returns r1 < r2 base on start and if they are disjoint
|
|
local function before_disjoint(r1, r2)
|
|
if r1['end'].line < r2['start'].line then return true end
|
|
if r2['start'].line < r1['end'].line then return false end
|
|
if r1['end'].character < r2['start'].character then return true end
|
|
return false
|
|
end
|
|
|
|
-- check for cursor row in [start,end]
|
|
-- check for cursor col in [start,end]
|
|
-- While the end is technically exclusive based on the highlighting, we treat it as inclusive to match the server.
|
|
local function point_in_range(point, range)
|
|
if point.row == range['start']['line'] and point.col < range['start']['character'] then
|
|
return false
|
|
end
|
|
if point.row == range['end']['line'] and point.col > range['end']['character'] then
|
|
return false
|
|
end
|
|
return point.row >= range['start']['line'] and point.row <= range['end']['line']
|
|
end
|
|
|
|
local function cursor_in_references(bufnr)
|
|
if not references[bufnr] then
|
|
return false
|
|
end
|
|
if vim.api.nvim_win_get_buf(0) ~= bufnr then
|
|
return false
|
|
end
|
|
local crow, ccol = unpack(vim.api.nvim_win_get_cursor(0))
|
|
crow = crow - 1 -- reference ranges are (0,0)-indexed for (row,col)
|
|
for _, reference in pairs(references[bufnr]) do
|
|
local range = reference.range
|
|
if point_in_range({ row = crow, col = ccol }, range) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function handle_document_highlight(result, bufnr, client_id)
|
|
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then return end
|
|
local btimer = timers[bufnr]
|
|
if btimer then
|
|
vim.loop.timer_stop(btimer)
|
|
-- vim.loop.close(btimer)
|
|
end
|
|
if type(result) ~= 'table' then
|
|
vim.lsp.util.buf_clear_references(bufnr)
|
|
return
|
|
end
|
|
timers[bufnr] = vim.defer_fn(function()
|
|
if not bufnr or not vim.api.nvim_buf_is_loaded(bufnr) then return end
|
|
vim.lsp.util.buf_clear_references(bufnr)
|
|
if cursor_in_references(bufnr) then
|
|
local client = vim.lsp.get_client_by_id(client_id)
|
|
if client then
|
|
vim.lsp.util.buf_highlight_references(bufnr, result, client.offset_encoding)
|
|
end
|
|
end
|
|
end, vim.g.Illuminate_delay or 17)
|
|
table.sort(result, function(a, b)
|
|
return before_by_start(a.range, b.range)
|
|
end)
|
|
references[bufnr] = result
|
|
end
|
|
|
|
local function valid(bufnr, range)
|
|
return range
|
|
and range.start.line < vim.api.nvim_buf_line_count(bufnr)
|
|
and range.start.character < #vim.fn.getline(range.start.line + 1)
|
|
end
|
|
|
|
local function augroup(bufnr, autocmds)
|
|
vim.cmd('augroup vim_illuminate_lsp' .. bufnr)
|
|
vim.cmd('autocmd!')
|
|
if autocmds then
|
|
vim.b.illuminate_lsp_enabled = true
|
|
autocmds()
|
|
else
|
|
vim.b.illuminate_lsp_enabled = false
|
|
end
|
|
vim.cmd('augroup END')
|
|
end
|
|
|
|
local function autocmd(bufnr)
|
|
vim.cmd(string.format('autocmd CursorMoved,CursorMovedI <buffer=%d> lua require"illuminate".on_cursor_moved(%d)',
|
|
bufnr, bufnr))
|
|
end
|
|
|
|
local function move_cursor(row, col)
|
|
if not paused_bufs[vim.api.nvim_get_current_buf()] then
|
|
augroup(vim.api.nvim_get_current_buf(), function()
|
|
vim.api.nvim_win_set_cursor(0, { row, col })
|
|
autocmd(vim.api.nvim_get_current_buf())
|
|
end)
|
|
else
|
|
vim.api.nvim_win_set_cursor(0, { row, col })
|
|
end
|
|
end
|
|
|
|
function M.on_attach(client)
|
|
M.stop_buf()
|
|
if client and not client.supports_method('textDocument/documentHighlight') then
|
|
return
|
|
end
|
|
pcall(vim.api.nvim_command, 'IlluminationDisable!')
|
|
augroup(vim.api.nvim_get_current_buf(), function()
|
|
autocmd(vim.api.nvim_get_current_buf())
|
|
end)
|
|
vim.lsp.handlers['textDocument/documentHighlight'] = function(...)
|
|
if vim.fn.has('nvim-0.5.1') == 1 then
|
|
handle_document_highlight(select(2, ...), select(3, ...).bufnr, select(3, ...).client_id)
|
|
else
|
|
handle_document_highlight(select(3, ...), select(5, ...), nil)
|
|
end
|
|
end
|
|
vim.lsp.buf.document_highlight()
|
|
end
|
|
|
|
function M.on_cursor_moved(bufnr)
|
|
if not cursor_in_references(bufnr) then
|
|
vim.lsp.util.buf_clear_references(bufnr)
|
|
end
|
|
|
|
-- Best-effort check if any client support textDocument/documentHighlight
|
|
local supported = nil
|
|
-- For nvim 0.10+
|
|
if vim.lsp.get_clients then
|
|
supported = false
|
|
for _, client in ipairs(vim.lsp.get_clients({bufnr = bufnr})) do
|
|
if client and client.supports_method('textDocument/documentHighlight') then
|
|
supported = true
|
|
end
|
|
end
|
|
-- For older versions
|
|
elseif vim.lsp.for_each_buffer_client then
|
|
supported = false
|
|
vim.lsp.for_each_buffer_client(bufnr, function(client)
|
|
if client.supports_method('textDocument/documentHighlight') then
|
|
supported = true
|
|
end
|
|
end)
|
|
end
|
|
|
|
if supported == nil or supported then
|
|
vim.lsp.buf.document_highlight()
|
|
else
|
|
augroup(vim.api.nvim_get_current_buf(), function()
|
|
end)
|
|
end
|
|
end
|
|
|
|
function M.get_document_highlights(bufnr)
|
|
return references[bufnr]
|
|
end
|
|
|
|
function M.next_reference(opt)
|
|
opt = vim.tbl_extend('force', { reverse = false, wrap = false, range_ordering = 'start', silent = false }, opt or {})
|
|
|
|
local before
|
|
if opt.range_ordering == 'start' then
|
|
before = before_by_start
|
|
else
|
|
before = before_disjoint
|
|
end
|
|
local bufnr = vim.api.nvim_get_current_buf()
|
|
local refs = M.get_document_highlights(bufnr)
|
|
if not refs or #refs == 0 then return nil end
|
|
|
|
local next = nil
|
|
local nexti = nil
|
|
local crow, ccol = unpack(vim.api.nvim_win_get_cursor(0))
|
|
local crange = { start = { line = crow - 1, character = ccol } }
|
|
|
|
for i, ref in ipairs(refs) do
|
|
local range = ref.range
|
|
if valid(bufnr, range) then
|
|
if opt.reverse then
|
|
if before(range, crange) and (not next or before(next, range)) then
|
|
next = range
|
|
nexti = i
|
|
end
|
|
else
|
|
if before(crange, range) and (not next or before(range, next)) then
|
|
next = range
|
|
nexti = i
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if not next and opt.wrap then
|
|
nexti = opt.reverse and #refs or 1
|
|
next = refs[nexti].range
|
|
end
|
|
if next then
|
|
move_cursor(next.start.line + 1, next.start.character)
|
|
if not opt.silent then
|
|
print('[' .. nexti .. '/' .. #refs .. ']')
|
|
end
|
|
end
|
|
return next
|
|
end
|
|
|
|
function M.toggle_pause()
|
|
if paused_bufs[vim.api.nvim_get_current_buf()] then
|
|
paused_bufs[vim.api.nvim_get_current_buf()] = false
|
|
augroup(vim.api.nvim_get_current_buf(), function()
|
|
autocmd(vim.api.nvim_get_current_buf())
|
|
end)
|
|
M.on_cursor_moved(vim.api.nvim_get_current_buf())
|
|
else
|
|
paused_bufs[vim.api.nvim_get_current_buf()] = true
|
|
augroup(vim.api.nvim_get_current_buf(), nil)
|
|
end
|
|
end
|
|
|
|
function M.configure(config)
|
|
require('illuminate.config').set(config)
|
|
end
|
|
|
|
function M.pause()
|
|
require('illuminate.engine').pause()
|
|
end
|
|
|
|
function M.resume()
|
|
require('illuminate.engine').resume()
|
|
end
|
|
|
|
function M.toggle()
|
|
require('illuminate.engine').toggle()
|
|
end
|
|
|
|
function M.toggle_buf()
|
|
require('illuminate.engine').toggle_buf()
|
|
end
|
|
|
|
function M.pause_buf()
|
|
require('illuminate.engine').pause_buf()
|
|
end
|
|
|
|
function M.stop_buf()
|
|
require('illuminate.engine').stop_buf()
|
|
end
|
|
|
|
function M.resume_buf()
|
|
require('illuminate.engine').resume_buf()
|
|
end
|
|
|
|
function M.freeze_buf()
|
|
require('illuminate.engine').freeze_buf()
|
|
end
|
|
|
|
function M.unfreeze_buf()
|
|
require('illuminate.engine').unfreeze_buf()
|
|
end
|
|
|
|
function M.toggle_freeze_buf()
|
|
require('illuminate.engine').toggle_freeze_buf()
|
|
end
|
|
|
|
function M.invisible_buf()
|
|
require('illuminate.engine').invisible_buf()
|
|
end
|
|
|
|
function M.visible_buf()
|
|
require('illuminate.engine').visible_buf()
|
|
end
|
|
|
|
function M.toggle_visibility_buf()
|
|
require('illuminate.engine').toggle_visibility_buf()
|
|
end
|
|
|
|
function M.goto_next_reference(wrap)
|
|
if wrap == nil then
|
|
wrap = vim.o.wrapscan
|
|
end
|
|
require('illuminate.goto').goto_next_reference(wrap)
|
|
end
|
|
|
|
function M.goto_prev_reference(wrap)
|
|
if wrap == nil then
|
|
wrap = vim.o.wrapscan
|
|
end
|
|
require('illuminate.goto').goto_prev_reference(wrap)
|
|
end
|
|
|
|
function M.textobj_select()
|
|
require('illuminate.textobj').select()
|
|
end
|
|
|
|
function M.debug()
|
|
require('illuminate.engine').debug()
|
|
end
|
|
|
|
function M.is_paused()
|
|
return require('illuminate.engine').is_paused()
|
|
end
|
|
|
|
function M.set_highlight_defaults()
|
|
vim.cmd [[
|
|
hi def IlluminatedWordText guifg=none guibg=none gui=underline
|
|
hi def IlluminatedWordRead guifg=none guibg=none gui=underline
|
|
hi def IlluminatedWordWrite guifg=none guibg=none gui=underline
|
|
]]
|
|
end
|
|
|
|
return M
|