-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocation.lua
More file actions
321 lines (291 loc) · 7.81 KB
/
location.lua
File metadata and controls
321 lines (291 loc) · 7.81 KB
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
local fs = require("peekstack.util.fs")
local str = require("peekstack.util.str")
local M = {}
local REALPATH_CACHE_MAX = 512
---@type table<string, string>
local realpath_cache = {}
---@type table<string, { prev: string?, next: string? }>
local realpath_cache_nodes = {}
---@type string?
local realpath_cache_head = nil
---@type string?
local realpath_cache_tail = nil
local realpath_cache_size = 0
---@param key string
local function cache_detach(key)
local node = realpath_cache_nodes[key]
if not node then
return
end
local prev = node.prev
local next = node.next
if prev then
realpath_cache_nodes[prev].next = next
else
realpath_cache_head = next
end
if next then
realpath_cache_nodes[next].prev = prev
else
realpath_cache_tail = prev
end
node.prev = nil
node.next = nil
end
---@param key string
local function cache_append_tail(key)
if not realpath_cache_nodes[key] then
realpath_cache_nodes[key] = {}
end
local node = realpath_cache_nodes[key]
node.prev = realpath_cache_tail
node.next = nil
if realpath_cache_tail then
realpath_cache_nodes[realpath_cache_tail].next = key
else
realpath_cache_head = key
end
realpath_cache_tail = key
end
---@param key string
---@param is_new boolean
local function cache_touch(key, is_new)
if not is_new and realpath_cache_tail == key then
return
end
if not is_new then
cache_detach(key)
else
realpath_cache_size = realpath_cache_size + 1
end
cache_append_tail(key)
end
local function cache_evict_if_needed()
while realpath_cache_size > REALPATH_CACHE_MAX do
local evict_key = realpath_cache_head
if not evict_key then
break
end
cache_detach(evict_key)
realpath_cache_nodes[evict_key] = nil
realpath_cache[evict_key] = nil
realpath_cache_size = realpath_cache_size - 1
end
end
local function cache_clear()
realpath_cache = {}
realpath_cache_nodes = {}
realpath_cache_head = nil
realpath_cache_tail = nil
realpath_cache_size = 0
end
---@param fname string
---@param cache? table<string, string>
---@return string
local function resolve_realpath(fname, cache)
if cache then
if cache[fname] then
return cache[fname]
end
local resolved = vim.uv.fs_realpath(fname) or fname
cache[fname] = resolved
return resolved
end
if realpath_cache[fname] then
cache_touch(fname, false)
return realpath_cache[fname]
end
local resolved = vim.uv.fs_realpath(fname) or fname
realpath_cache[fname] = resolved
cache_touch(fname, true)
cache_evict_if_needed()
return resolved
end
---@param range? PeekstackRange
---@return PeekstackRange
local function normalize_range(range)
if not range then
return { start = { line = 0, character = 0 }, ["end"] = { line = 0, character = 0 } }
end
return range
end
---@param loc table
---@param provider? string
---@return PeekstackLocation?
local function from_lsp_location(loc, provider)
if not loc then
return nil
end
local uri = loc.uri or loc.targetUri
local range = loc.range or loc.targetRange
if not uri or not range then
return nil
end
return {
uri = uri,
range = normalize_range(range),
text = loc.text,
kind = loc.kind,
provider = provider,
origin = loc.originSelectionRange,
}
end
---@param loc table
---@param provider? string
---@return PeekstackLocation?
function M.normalize(loc, provider)
if not loc then
return nil
end
if loc.uri or loc.targetUri then
return from_lsp_location(loc, provider)
end
if loc.filename or loc.bufnr then
local uri = loc.uri or fs.fname_to_uri(loc.filename or vim.api.nvim_buf_get_name(loc.bufnr))
local line = (loc.lnum or 1) - 1
local col = (loc.col or 1) - 1
return {
uri = uri,
range = {
start = { line = line, character = col },
["end"] = { line = line, character = col },
},
text = loc.text,
kind = loc.kind,
provider = provider,
}
end
return nil
end
---@param result table|table[]?
---@param provider? string
---@return PeekstackLocation[]
function M.list_from_lsp(result, provider)
if not result then
return {}
end
local items = {}
if vim.islist(result) then
for _, loc in ipairs(result) do
local norm = from_lsp_location(loc, provider)
if norm then
table.insert(items, norm)
end
end
else
local norm = from_lsp_location(result, provider)
if norm then
table.insert(items, norm)
end
end
return items
end
---@param diags table[]?
---@param provider? string
---@return PeekstackLocation[]
function M.from_diagnostics(diags, provider)
local items = {}
for _, diag in ipairs(diags or {}) do
local uri = fs.fname_to_uri(vim.api.nvim_buf_get_name(diag.bufnr or 0))
table.insert(items, {
uri = uri,
range = {
start = { line = diag.lnum, character = diag.col or 0 },
["end"] = { line = diag.end_lnum or diag.lnum, character = diag.end_col or diag.col or 0 },
},
text = diag.message,
kind = diag.severity,
provider = provider,
})
end
return items
end
---@param location PeekstackLocation
---@param preview_lines? integer
---@param opts? PeekstackDisplayTextOpts
---@return string
function M.display_text(location, preview_lines, opts)
opts = opts or {}
local raw_path = fs.uri_to_fname(location.uri) or ""
local path = raw_path
if opts.path_base then
path = str.relative_path(raw_path, opts.path_base, opts)
else
path = str.shorten_path(raw_path)
end
local line = (location.range.start.line or 0) + 1
local col = (location.range.start.character or 0) + 1
local text = location.text or ""
if preview_lines and preview_lines > 0 and text ~= "" then
text = text:gsub("\n", " ")
else
text = ""
end
local suffix = text ~= "" and (" " .. text) or ""
local suffix_text = string.format(":%d:%d%s", line, col, suffix)
if opts.max_width and opts.max_width > 0 then
local suffix_width = vim.fn.strdisplaywidth(suffix_text)
local available = math.max(opts.max_width - suffix_width, 0)
path = str.truncate_middle(path, available)
end
local label = path .. suffix_text
if opts.max_width and opts.max_width > 0 and vim.fn.strdisplaywidth(label) > opts.max_width then
label = str.truncate_middle(label, opts.max_width)
end
return label
end
---@param location PeekstackLocation
---@param uri string
---@param line integer
---@param character integer
---@param opts? { realpath_cache?: table<string, string> }
---@return boolean
function M.is_same_position(location, uri, line, character, opts)
if not location or not location.uri or not location.range or not location.range.start then
return false
end
opts = opts or {}
local path_cache = opts.realpath_cache
local loc_uri = location.uri
local loc_fname = fs.uri_to_fname(loc_uri)
local cur_fname = fs.uri_to_fname(uri)
if loc_fname then
loc_fname = resolve_realpath(loc_fname, path_cache)
end
if cur_fname then
cur_fname = resolve_realpath(cur_fname, path_cache)
end
if loc_fname and cur_fname then
if loc_fname ~= cur_fname then
return false
end
elseif loc_uri ~= uri then
return false
end
local range = location.range
local start = range.start or { line = 0, character = 0 }
local finish = range["end"] or start
local pos = { line = line or 0, character = character or 0 }
if pos.line < (start.line or 0) then
return false
end
if pos.line == (start.line or 0) and pos.character < (start.character or 0) then
return false
end
if pos.line > (finish.line or 0) then
return false
end
if pos.line == (finish.line or 0) and pos.character > (finish.character or 0) then
return false
end
return true
end
---Reset internal caches (for testing).
function M._reset()
cache_clear()
end
---@return integer
function M._realpath_cache_limit()
return REALPATH_CACHE_MAX
end
return M