-
Notifications
You must be signed in to change notification settings - Fork 219
/
components.lua
558 lines (508 loc) · 15.7 KB
/
components.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
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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
-- This file contains the built-in components. Each componment is a function
-- that takes the following arguments:
-- config: A table containing the configuration provided by the user
-- when declaring this component in their renderer config.
-- node: A NuiNode object for the currently focused node.
-- state: The current state of the source providing the items.
--
-- The function should return either a table, or a list of tables, each of which
-- contains the following keys:
-- text: The text to display for this item.
-- highlight: The highlight group to apply to this text.
local highlights = require("neo-tree.ui.highlights")
local utils = require("neo-tree.utils")
local file_nesting = require("neo-tree.sources.common.file-nesting")
local container = require("neo-tree.sources.common.container")
local log = require("neo-tree.log")
local M = {}
local make_two_char = function(symbol)
if vim.fn.strchars(symbol) == 1 then
return symbol .. " "
else
return symbol
end
end
-- only works in the buffers component, but it's here so we don't have to defined
-- multple renderers.
M.bufnr = function(config, node, state)
local highlight = config.highlight or highlights.BUFFER_NUMBER
local bufnr = node.extra and node.extra.bufnr
if not bufnr then
return {}
end
return {
text = string.format("#%s", bufnr),
highlight = highlight,
}
end
M.clipboard = function(config, node, state)
local clipboard = state.clipboard or {}
local clipboard_state = clipboard[node:get_id()]
if not clipboard_state then
return {}
end
return {
text = " (" .. clipboard_state.action .. ")",
highlight = config.highlight or highlights.DIM_TEXT,
}
end
M.container = container.render
M.current_filter = function(config, node, state)
local filter = node.search_pattern or ""
if filter == "" then
return {}
end
return {
{
text = "Find",
highlight = highlights.DIM_TEXT,
},
{
text = string.format('"%s"', filter),
highlight = config.highlight or highlights.FILTER_TERM,
},
{
text = "in",
highlight = highlights.DIM_TEXT,
},
}
end
---`sign_getdefined` based wrapper with compatibility
---@param severity string
---@return vim.fn.sign_getdefined.ret.item
local function get_defined_sign(severity)
local defined
if vim.fn.has("nvim-0.10") > 0 then
local signs_config = vim.diagnostic.config().signs
if type(signs_config) == "table" then
local identifier = severity:sub(1, 1)
if identifier == "H" then
identifier = "N"
end
defined = {
text = (signs_config.text or {})[vim.diagnostic.severity[identifier]],
texthl = "DiagnosticSign" .. severity,
}
end
else -- before 0.10
defined = vim.fn.sign_getdefined("DiagnosticSign" .. severity)
if vim.tbl_isempty(defined) then
-- backwards compatibility...
local old_severity = severity
if severity == "Warning" then
old_severity = "Warn"
elseif severity == "Information" then
old_severity = "Info"
end
defined = vim.fn.sign_getdefined("LspDiagnosticsSign" .. old_severity)
end
defined = defined and defined[1]
end
if type(defined) ~= "table" then
defined = {}
end
return defined
end
M.diagnostics = function(config, node, state)
local diag = state.diagnostics_lookup or {}
local diag_state = diag[node:get_id()]
if config.hide_when_expanded and node.type == "directory" and node:is_expanded() then
return {}
end
if not diag_state then
return {}
end
if config.errors_only and diag_state.severity_number > 1 then
return {}
end
local severity = diag_state.severity_string
local defined = get_defined_sign(severity)
-- check for overrides in the component config
local severity_lower = severity:lower()
if config.symbols and config.symbols[severity_lower] then
defined.texthl = defined.texthl or ("Diagnostic" .. severity)
defined.text = config.symbols[severity_lower]
end
if config.highlights and config.highlights[severity_lower] then
defined.text = defined.text or severity:sub(1, 1)
defined.texthl = config.highlights[severity_lower]
end
if defined.text and defined.texthl then
return {
text = make_two_char(defined.text),
highlight = defined.texthl,
}
else
return {
text = severity:sub(1, 1),
highlight = "Diagnostic" .. severity,
}
end
end
M.git_status = function(config, node, state)
local git_status_lookup = state.git_status_lookup
if config.hide_when_expanded and node.type == "directory" and node:is_expanded() then
return {}
end
if not git_status_lookup then
return {}
end
local git_status = git_status_lookup[node.path]
if not git_status then
if node.filtered_by and node.filtered_by.gitignored then
git_status = "!!"
else
return {}
end
end
local symbols = config.symbols or {}
local change_symbol
local change_highlt = highlights.FILE_NAME
local status_symbol = symbols.staged
local status_highlt = highlights.GIT_STAGED
if node.type == "directory" and git_status:len() == 1 then
status_symbol = nil
end
if git_status:sub(1, 1) == " " then
status_symbol = symbols.unstaged
status_highlt = highlights.GIT_UNSTAGED
end
if git_status:match("?$") then
status_symbol = nil
status_highlt = highlights.GIT_UNTRACKED
change_symbol = symbols.untracked
change_highlt = highlights.GIT_UNTRACKED
-- all variations of merge conflicts
elseif git_status == "DD" then
status_symbol = symbols.conflict
status_highlt = highlights.GIT_CONFLICT
change_symbol = symbols.deleted
change_highlt = highlights.GIT_CONFLICT
elseif git_status == "UU" then
status_symbol = symbols.conflict
status_highlt = highlights.GIT_CONFLICT
change_symbol = symbols.modified
change_highlt = highlights.GIT_CONFLICT
elseif git_status == "AA" then
status_symbol = symbols.conflict
status_highlt = highlights.GIT_CONFLICT
change_symbol = symbols.added
change_highlt = highlights.GIT_CONFLICT
elseif git_status:match("U") then
status_symbol = symbols.conflict
status_highlt = highlights.GIT_CONFLICT
if git_status:match("A") then
change_symbol = symbols.added
elseif git_status:match("D") then
change_symbol = symbols.deleted
end
change_highlt = highlights.GIT_CONFLICT
-- end merge conflict section
elseif git_status:match("M") then
change_symbol = symbols.modified
change_highlt = highlights.GIT_MODIFIED
elseif git_status:match("R") then
change_symbol = symbols.renamed
change_highlt = highlights.GIT_RENAMED
elseif git_status:match("[ACT]") then
change_symbol = symbols.added
change_highlt = highlights.GIT_ADDED
elseif git_status:match("!") then
status_symbol = nil
change_symbol = symbols.ignored
change_highlt = highlights.GIT_IGNORED
elseif git_status:match("D") then
change_symbol = symbols.deleted
change_highlt = highlights.GIT_DELETED
end
if change_symbol or status_symbol then
local components = {}
if type(change_symbol) == "string" and #change_symbol > 0 then
table.insert(components, {
text = make_two_char(change_symbol),
highlight = change_highlt,
})
end
if type(status_symbol) == "string" and #status_symbol > 0 then
table.insert(components, {
text = make_two_char(status_symbol),
highlight = status_highlt,
})
end
return components
else
return {
text = "[" .. git_status .. "]",
highlight = config.highlight or change_highlt,
}
end
end
M.filtered_by = function(config, node, state)
local result = {}
if type(node.filtered_by) == "table" then
local fby = node.filtered_by
if fby.name then
result = {
text = "(hide by name)",
highlight = highlights.HIDDEN_BY_NAME,
}
elseif fby.pattern then
result = {
text = "(hide by pattern)",
highlight = highlights.HIDDEN_BY_NAME,
}
elseif fby.gitignored then
result = {
text = "(gitignored)",
highlight = highlights.GIT_IGNORED,
}
elseif fby.dotfiles then
result = {
text = "(dotfile)",
highlight = highlights.DOTFILE,
}
elseif fby.hidden then
result = {
text = "(hidden)",
highlight = highlights.WINDOWS_HIDDEN,
}
end
fby = nil
end
return result
end
M.icon = function(config, node, state)
local icon = config.default or " "
local highlight = config.highlight or highlights.FILE_ICON
if node.type == "directory" then
highlight = highlights.DIRECTORY_ICON
if node.loaded and not node:has_children() then
icon = not node.empty_expanded and config.folder_empty or config.folder_empty_open
elseif node:is_expanded() then
icon = config.folder_open or "-"
else
icon = config.folder_closed or "+"
end
elseif node.type == "file" or node.type == "terminal" then
local success, web_devicons = pcall(require, "nvim-web-devicons")
local name = node.type == "terminal" and "terminal" or node.name
if success then
local devicon, hl = web_devicons.get_icon(name)
icon = devicon or icon
highlight = hl or highlight
end
end
local filtered_by = M.filtered_by(config, node, state)
return {
text = icon .. " ",
highlight = filtered_by.highlight or highlight,
}
end
M.modified = function(config, node, state)
local opened_buffers = state.opened_buffers or {}
local buf_info = opened_buffers[node.path]
if buf_info and buf_info.modified then
return {
text = (make_two_char(config.symbol) or "[+]"),
highlight = config.highlight or highlights.MODIFIED,
}
else
return {}
end
end
M.name = function(config, node, state)
local highlight = config.highlight or highlights.FILE_NAME
local text = node.name
if node.type == "directory" then
highlight = highlights.DIRECTORY_NAME
if config.trailing_slash and text ~= "/" then
text = text .. "/"
end
end
if node:get_depth() == 1 and node.type ~= "message" then
highlight = highlights.ROOT_NAME
else
local filtered_by = M.filtered_by(config, node, state)
highlight = filtered_by.highlight or highlight
if config.use_git_status_colors then
local git_status = state.components.git_status({}, node, state)
if git_status and git_status.highlight then
highlight = git_status.highlight
end
end
end
local hl_opened = config.highlight_opened_files
if hl_opened then
local opened_buffers = state.opened_buffers or {}
if
(hl_opened == "all" and opened_buffers[node.path])
or (opened_buffers[node.path] and opened_buffers[node.path].loaded)
then
highlight = highlights.FILE_NAME_OPENED
end
end
if type(config.right_padding) == "number" then
if config.right_padding > 0 then
text = text .. string.rep(" ", config.right_padding)
end
else
text = text
end
return {
text = text,
highlight = highlight,
}
end
M.indent = function(config, node, state)
if not state.skip_marker_at_level then
state.skip_marker_at_level = {}
end
local strlen = vim.fn.strdisplaywidth
local skip_marker = state.skip_marker_at_level
local indent_size = config.indent_size or 2
local padding = config.padding or 0
local level = node.level
local with_markers = config.with_markers
local with_expanders = config.with_expanders == nil and file_nesting.is_enabled()
or config.with_expanders
local marker_highlight = config.highlight or highlights.INDENT_MARKER
local expander_highlight = config.expander_highlight or config.highlight or highlights.EXPANDER
local function get_expander()
if with_expanders and utils.is_expandable(node) then
return node:is_expanded() and (config.expander_expanded or "")
or (config.expander_collapsed or "")
end
end
if indent_size == 0 or level < 2 or not with_markers then
local len = indent_size * level + padding
local expander = get_expander()
if level == 0 or not expander then
return {
text = string.rep(" ", len),
}
end
return {
text = string.rep(" ", len - strlen(expander) - 1) .. expander .. " ",
highlight = expander_highlight,
}
end
local indent_marker = config.indent_marker or "│"
local last_indent_marker = config.last_indent_marker or "└"
skip_marker[level] = node.is_last_child
local indent = {}
if padding > 0 then
table.insert(indent, { text = string.rep(" ", padding) })
end
for i = 1, level do
local char = ""
local spaces_count = indent_size
local highlight = nil
if i > 1 and not skip_marker[i] or i == level then
spaces_count = spaces_count - 1
char = indent_marker
highlight = marker_highlight
if i == level then
local expander = get_expander()
if expander then
char = expander
highlight = expander_highlight
elseif node.is_last_child then
char = last_indent_marker
spaces_count = spaces_count - (vim.api.nvim_strwidth(last_indent_marker) - 1)
end
end
end
table.insert(indent, {
text = char .. string.rep(" ", spaces_count),
highlight = highlight,
no_next_padding = true,
})
end
return indent
end
local get_header = function (state, label, size)
if state.sort and state.sort.label == label then
local icon = state.sort.direction == 1 and "▲" or "▼"
size = size - 2
return string.format("%" .. size .. "s %s ", label, icon)
end
return string.format("%" .. size .. "s ", label)
end
M.file_size = function (config, node, state)
-- Root node gets column labels
if node:get_depth() == 1 then
return {
text = get_header(state, "Size", 12),
highlight = highlights.FILE_STATS_HEADER
}
end
local text = "-"
if node.type == "file" then
local stat = utils.get_stat(node)
local size = stat and stat.size or nil
if size then
local success, human = pcall(utils.human_size, size)
if success then
text = human or text
end
end
end
return {
text = string.format("%12s ", text),
highlight = config.highlight or highlights.FILE_STATS
}
end
local file_time = function(config, node, state, stat_field)
-- Root node gets column labels
if node:get_depth() == 1 then
local label = stat_field
if stat_field == "mtime" then
label = "Last Modified"
elseif stat_field == "birthtime" then
label = "Created"
end
return {
text = get_header(state, label, 20),
highlight = highlights.FILE_STATS_HEADER
}
end
local stat = utils.get_stat(node)
local value = stat and stat[stat_field]
local seconds = value and value.sec or nil
local display = seconds and os.date("%Y-%m-%d %I:%M %p", seconds) or "-"
return {
text = string.format("%20s ", display),
highlight = config.highlight or highlights.FILE_STATS
}
end
M.last_modified = function(config, node, state)
return file_time(config, node, state, "mtime")
end
M.created = function(config, node, state)
return file_time(config, node, state, "birthtime")
end
M.symlink_target = function(config, node, state)
if node.is_link then
return {
text = string.format(" ➛ %s", node.link_to),
highlight = config.highlight or highlights.SYMBOLIC_LINK_TARGET,
}
else
return {}
end
end
M.type = function (config, node, state)
local text = node.ext or node.type
-- Root node gets column labels
if node:get_depth() == 1 then
return {
text = get_header(state, "Type", 10),
highlight = highlights.FILE_STATS_HEADER
}
end
return {
text = string.format("%10s ", text),
highlight = highlights.FILE_STATS
}
end
return M