-
Notifications
You must be signed in to change notification settings - Fork 18
/
nvim-lsp.lua
233 lines (208 loc) · 6.63 KB
/
nvim-lsp.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
local cfg = require('outline.config')
local jsx = require('outline.providers.jsx')
local lsp_utils = require('outline.utils.lsp')
local l = vim.lsp
local M = {
name = 'lsp',
}
local request_timeout = 2000
---@param info table? Must be the table received from `supports_buffer`
function M.get_status(info)
if not info then
return { 'No clients' }
end
return { 'client: ' .. info.client.name }
end
---@param client lsp.client
---@param capability string
---@return boolean
local function _check_client(client, capability)
if cfg.is_client_blacklisted(client) then
return false
end
return client.server_capabilities[capability]
end
---@param bufnr integer
---@param capability string
---@return lsp.client?
local function get_appropriate_client(bufnr, capability)
local clients, use_client
if _G._outline_nvim_has[8] then
if _G._outline_nvim_has[10] then
clients = l.get_clients({ bufnr = bufnr })
else
clients = l.get_active_clients({ bufnr = bufnr })
end
for _, client in ipairs(clients) do
if _check_client(client, capability) then
use_client = client
break
end
end
else
-- Returns client_id:client pairs
---@diagnostic disable-next-line
clients = l.buf_get_clients(bufnr)
for _, client in pairs(clients) do
if _check_client(client, capability) then
use_client = client
break
end
end
end
return use_client
end
---@return boolean, table?
function M.supports_buffer(bufnr)
local client = get_appropriate_client(bufnr, 'documentSymbolProvider')
if not client then
return false
end
return true, { client = client }
end
---Include JSX symbols if applicable, and merge it with existing symbols
---@param symbols outline.ProviderSymbol[]
---@return outline.ProviderSymbol[]
local function postprocess_symbols(symbols)
local jsx_symbols = jsx.get_symbols()
if #jsx_symbols > 0 then
return lsp_utils.merge_symbols(symbols, jsx_symbols)
else
return symbols
end
end
-- XXX: Only one LSP client is supported here, to prevent checking blacklisting
-- over again
---@param on_symbols fun(symbols?:outline.ProviderSymbol[], opts?:table)
---@param opts table?
---@param info table? Must be the table received from `supports_buffer`
function M.request_symbols(on_symbols, opts, info)
if not info then
return on_symbols(nil, opts)
end
local params = {
textDocument = l.util.make_text_document_params(),
}
-- XXX: Is bufnr=0 ok here?
local status = info.client.request('textDocument/documentSymbol', params, function(err, response)
if err or not response then
on_symbols(response, opts)
else
response = postprocess_symbols(response)
on_symbols(response, opts)
end
end, 0)
if not status then
on_symbols(nil, opts)
end
end
-- No good way to update outline when LSP action complete for now
---@param sidebar outline.Sidebar
---@return boolean success
function M.code_actions(sidebar)
local client = get_appropriate_client(sidebar.code.buf, 'codeActionProvider')
if not client then
return false
end
-- NOTE: Unfortunately the code_action function provided by neovim does a
-- lot, yet it doesn't let us filter clients. Since handling of code_actions
-- is beyond the scope of outline.nvim itself, we will not respect
-- blacklist_clients for code actions for now. Code actions feature would not
-- actually be included if I were to write this plugin from scratch. However
-- we still keep it because many people are moving here from
-- symbols-outline.nvim, which happened to implement this feature.
sidebar:wrap_goto_location(function()
l.buf.code_action()
end)
return true
end
---@see rename_symbol
---@param sidebar outline.Sidebar
---@param client lsp.client
---@param node outline.FlatSymbol
---@return boolean success
local function legacy_rename(sidebar, client, node)
-- Using fn.input so it's synchronous
local new_name = vim.fn.input({ prompt = 'New Name: ', default = node.name })
if not new_name or new_name == '' or new_name == node.name then
return true
end
local params = {
textDocument = { uri = 'file://' .. vim.api.nvim_buf_get_name(sidebar.code.buf) },
position = { line = node.line, character = node.character },
bufnr = sidebar.code.buf,
newName = new_name,
}
local status, err =
client.request_sync('textDocument/rename', params, request_timeout, sidebar.code.buf)
if status == nil or status.err or err or status.result == nil then
return false
end
l.util.apply_workspace_edit(status.result, client.offset_encoding)
node.name = new_name
sidebar:_update_lines(false)
return true
end
---Synchronously request rename from LSP
---@param sidebar outline.Sidebar
---@return boolean success
function M.rename_symbol(sidebar)
local client = get_appropriate_client(sidebar.code.buf, 'renameProvider')
if not client then
return false
end
local node = sidebar:_current_node()
if not node then
return false
end
if _G._outline_nvim_has[8] then
sidebar:wrap_goto_location(function()
-- Options table with filter key only added in nvim-0.8
-- Use vim.lsp's function because it has better support.
l.buf.rename(nil, {
filter = function(cl)
return not cfg.is_client_blacklisted(cl)
end,
})
end)
return true
else
return legacy_rename(sidebar, client, node)
end
end
---Synchronously request and show hover info from LSP
---@param sidebar outline.Sidebar
---@return boolean success
function M.show_hover(sidebar)
local client = get_appropriate_client(sidebar.code.buf, 'hoverProvider')
if not client then
return false
end
local node = sidebar:_current_node()
if not node then
return false
end
local params = {
textDocument = { uri = vim.uri_from_bufnr(sidebar.code.buf) },
position = { line = node.line, character = node.character },
bufnr = sidebar.code.buf,
}
local status, err = client.request_sync('textDocument/hover', params, request_timeout)
if status == nil or status.err or err or not status.result or not status.result.contents then
return false
end
local md_lines = l.util.convert_input_to_markdown_lines(status.result.contents)
md_lines = l.util.trim_empty_lines(md_lines)
if vim.tbl_isempty(md_lines) then
-- Request was successful, but there is no hover content
return true
end
local code_width = vim.api.nvim_win_get_width(sidebar.code.win)
local bufnr, winnr = l.util.open_floating_preview(md_lines, 'markdown', {
border = cfg.o.preview_window.border,
width = code_width,
})
vim.api.nvim_win_set_option(winnr, 'winhighlight', cfg.o.preview_window.winhl)
return true
end
return M