any-jump.vim
690 строк · 18.2 Кб
1" ----------------------------------------------
2" Internal buffer prototype definition
3" ----------------------------------------------
4" represents ui internal structure
5
6" abstract structure of internal buffer representation:
7"
8" buffer = [ array of lines ]
9"
10" line = [ list of items
11" { type, strat_col, finish_col, text, hl_group },
12" { ... },
13" ...
14" ]
15
16let s:nvim = has('nvim')
17
18let s:InternalBuffer = {}
19
20let s:InternalBuffer.MethodsList = [
21\'RenderLine',
22\'AddLine',
23\'AddLineAt',
24\'CreateItem',
25\'len',
26\'GetItemByPos',
27\'GetItemLineNumber',
28\'GetItemLineNumberByData',
29\'GetFirstItemOfType',
30\'TryFindOriginalLinkFromPos',
31\'TryRestoreCursorForItem',
32\'RenderUiUsagesList',
33\'RenderUi',
34\'HelpSection',
35\'StartUiTransaction',
36\'EndUiTransaction',
37\'GrepResultToItems',
38\'GrepResultToGroupedItems',
39\'RemoveGarbagedLines',
40\'JumpToFirstOfType',
41\'ClearBuffer',
42\'BufferLnum',
43\'RestorePopupCursor',
44\]
45
46" Produce new Render Buffer
47fu! s:InternalBuffer.New() abort
48let object = {
49\"items": [],
50\"current_page": 0,
51\"gc": v:false,
52\"preview_opened": v:false,
53\"usages_opened": v:false,
54\"grouping_enabled": v:false,
55\"overmaxed_results_hidden": v:true,
56\"definitions_grep_results": [],
57\"usages_grep_results": [],
58\"vim_bufnr": 0,
59\"popup_winid": 0,
60\"previous_bufnr": 0,
61\}
62
63for method in self.MethodsList
64let object[method] = s:InternalBuffer[method]
65endfor
66
67return object
68endfu
69
70fu! s:InternalBuffer.len() dict abort
71return len(self.items)
72endfu
73
74fu! s:InternalBuffer.RenderLine(items, line) dict abort
75let text = s:nvim ? " " : ""
76let idx = 0
77let next_start_col = 1
78
79" calculate & assign items start & end columns
80for item in a:items
81" separate items in line with 1 space
82if idx == 0
83let text = text . item.text
84else
85if has_key(item.data, 'no_padding')
86let text = text . item.text
87else
88let text = text . ' ' . item.text
89let next_start_col = next_start_col + 1
90endif
91endif
92
93let item.start_col = next_start_col
94let item.end_col = next_start_col + len(item.text)
95
96let next_start_col = item.end_col
97let idx += 1
98endfor
99
100" filter out empty whitespaces
101if text =~ '^\s\+$'
102let text = ''
103endif
104
105" write final text to buffer
106call appendbufline(self.vim_bufnr, a:line - 1, text)
107
108" colorize
109for item in a:items
110if s:nvim
111call nvim_buf_add_highlight(
112\self.vim_bufnr,
113\-1,
114\item.hl_group,
115\a:line - 1,
116\item.start_col,
117\item.end_col)
118else
119call prop_add(a:line, item.start_col, {
120\'length': item.len,
121\'type': item.hl_group,
122\'bufnr': self.vim_bufnr})
123endif
124endfor
125endfu
126
127fu! s:InternalBuffer.AddLine(items) dict abort
128if type(a:items) == v:t_list
129call self.RenderLine(a:items, self.len() + 1)
130call add(self.items, a:items)
131
132return v:true
133else
134echoe "array required, got invalid type: " . string(a:items)
135
136return v:false
137endif
138endfu
139
140fu! s:InternalBuffer.AddLineAt(items, line_number) dict abort
141if type(a:items) == v:t_list
142call self.RenderLine(a:items, a:line_number)
143call insert(self.items, a:items, a:line_number - 1)
144
145return v:true
146else
147echoe "array required, got invalid type: " . string(a:items)
148
149return v:false
150endif
151endfu
152
153" type:
154" 'text' / 'link' / 'button' / 'preview_text'
155fu! s:InternalBuffer.CreateItem(type, text, hl_group, ...) dict abort
156let data = {}
157
158if a:0
159let data = a:1
160endif
161
162let item = {
163\"type": a:type,
164\"text": a:text,
165\"len": len(a:text),
166\"start_col": 0,
167\"end_col": 0,
168\"hl_group": a:hl_group,
169\"gc": v:false,
170\"data": data
171\}
172
173" TODO: optimize this part for rednering perfomance
174if !s:nvim
175if prop_type_get(item.hl_group, {'bufnr': self.vim_bufnr}) == {}
176call prop_type_add(item.hl_group, {
177\'highlight': item.hl_group,
178\'bufnr': self.vim_bufnr
179\})
180endif
181endif
182
183return item
184endfu
185
186
187fu! s:InternalBuffer.GetItemByPos() dict abort
188if s:nvim
189let idx = getbufinfo(self.vim_bufnr)[0]['lnum']
190else
191" vim popup buffer doesn't have current line info inside getbufinfo()
192" so extract line nr from win
193let l:popup_pos = 0
194call win_execute(self.popup_winid, 'let l:popup_pos = getcurpos()')
195let idx = l:popup_pos[1]
196end
197
198if idx > len(self.items)
199return 0
200endif
201
202if s:nvim
203let column = col('.')
204else
205let column = 1
206end
207
208let line = self.items[idx - 1]
209
210for item in line
211if item.start_col <= column && (item.end_col >= column || item.end_col == -1 )
212return item
213endif
214endfor
215
216return 0
217endfu
218
219" not optimal, but ok for current ui with around ~100/200 lines
220" COMPLEXITY: O(1)
221fu! s:InternalBuffer.GetItemLineNumber(item) dict abort
222let i = 0
223let found = 0
224
225for line in self.items
226let i += 1
227
228for item in line
229if item == a:item
230let found = i
231break
232endif
233endfor
234
235if found > 0
236break
237endif
238endfor
239
240return found
241endfu
242
243" not optimal, but ok for current ui with around ~100/200 lines
244" COMPLEXITY: O(1)
245fu! s:InternalBuffer.GetItemLineNumberByData(data) dict abort
246let i = 0
247let found = 0
248
249for line in self.items
250let i += 1
251
252for item in line
253if item.data == a:data
254let found = i
255break
256endif
257endfor
258
259if found > 0
260break
261endif
262endfor
263
264return found
265endfu
266
267fu! s:InternalBuffer.GetFirstItemOfType(type, ...) dict abort
268let result = 0
269let layer = 0
270
271if a:0 == 1
272let layer = a:1
273endif
274
275for line in self.items
276if type(result) == v:t_dict
277break
278endif
279
280for item in line
281let type_is_ok = item.type == a:type
282let layer_is_ok = v:true
283
284if type(layer) == v:t_string
285let layer_is_ok = item.data.layer == layer
286endif
287
288if type_is_ok && layer_is_ok
289let result = item
290break
291endif
292endfor
293endfor
294
295return result
296endfu
297
298fu! s:InternalBuffer.TryFindOriginalLinkFromPos() dict abort
299let cursor_item = self.GetItemByPos()
300
301" try to find original link
302if type(cursor_item) == v:t_dict && type(cursor_item.data) == v:t_dict
303\ && cursor_item.type == 'link'
304\ && !has_key(cursor_item, 'original_link')
305let ln = self.GetItemLineNumber(cursor_item)
306let line = self.items[ln - 1]
307
308for item in line
309if type(item.data) == v:t_dict && has_key(item.data, 'original_link')
310let cursor_item = item
311break
312endif
313endfor
314endif
315
316return cursor_item
317endfu
318
319fu! s:InternalBuffer.TryRestoreCursorForItem(item,...) dict abort
320let opts = {}
321if a:0 == 1 && type(a:1) == v:t_dict
322let opts = a:1
323endif
324
325if type(a:item) == v:t_dict
326\ && a:item.type == "link"
327\ && !has_key(a:item.data, 'group_header')
328
329let new_ln = self.GetItemLineNumberByData(a:item.data)
330
331" item removed
332if new_ln == 0
333call self.JumpToFirstOfType('link')
334else
335call cursor(new_ln, 2)
336endif
337else
338if has_key(opts, 'last_ln_nr')
339if opts.last_ln_nr > self.len()
340call self.JumpToFirstOfType('link')
341else
342call cursor(opts.last_ln_nr, 2)
343call cursor(opts.last_ln_nr, 2)
344endif
345else
346call self.JumpToFirstOfType('link')
347endif
348endif
349endfu
350
351fu! s:InternalBuffer.JumpToFirstOfType(type, ...) dict abort
352let item = self.GetFirstItemOfType(a:type, a:000)
353
354if type(item) == v:t_dict
355let ln = self.GetItemLineNumber(item)
356call cursor(ln, 2)
357endif
358endfu
359
360fu! s:InternalBuffer.ClearBuffer(buf) dict abort
361call deletebufline(a:buf, 1, self.len() + 1)
362endfu
363
364fu! s:InternalBuffer.BufferLnum() dict abort
365return getbufinfo(self.vim_bufnr)[0]['lnum']
366endfu
367
368fu! s:InternalBuffer.RestorePopupCursor() dict abort
369if !s:nvim
370call popup_filter_menu(self.popup_winid, 'j')
371endif
372endfu
373
374fu! s:InternalBuffer.StartUiTransaction() dict abort
375if !s:nvim
376return
377endif
378
379call setbufvar(self.vim_bufnr, '&modifiable', 1)
380endfu
381
382fu! s:InternalBuffer.EndUiTransaction() dict abort
383if !s:nvim
384return
385endif
386
387call setbufvar(self.vim_bufnr, '&modifiable', 0)
388endfu
389
390fu! s:InternalBuffer.GrepResultToItems(gr, current_idx, layer) dict abort
391let gr = a:gr
392let items = []
393
394let options =
395\{ "path": gr.path, "line_number": gr.line_number, "layer": a:layer }
396let original_link_options =
397\{ "path": gr.path, "line_number": gr.line_number,
398\"layer": a:layer, "original_link": v:true }
399
400if g:any_jump_list_numbers
401let prefix_text = a:current_idx + 1
402let prefix = self.CreateItem("link", prefix_text, g:AnyJumpGetColor('result_line_number'), options)
403
404call add(items, prefix)
405endif
406
407if g:any_jump_results_ui_style == 'filename_first'
408let path_text = gr.path . ":" . gr.line_number
409let matched_text = self.CreateItem("link", "" . gr.text, g:AnyJumpGetColor('result_text'), original_link_options)
410let file_path = self.CreateItem("link", path_text, g:AnyJumpGetColor('result_path'), options)
411
412call add(items, file_path)
413call add(items, matched_text)
414elseif g:any_jump_results_ui_style == 'filename_last'
415let path_text = '' . gr.path . ":" . gr.line_number
416let matched_text = self.CreateItem("link", gr.text, g:AnyJumpGetColor('result_text'), original_link_options)
417let file_path = self.CreateItem("link", path_text, g:AnyJumpGetColor('result_path'), options)
418
419call add(items, matched_text)
420call add(items, file_path)
421endif
422
423return items
424endfu
425
426fu! s:InternalBuffer.GrepResultToGroupedItems(gr, current_idx, layer) dict abort
427let gr = a:gr
428let items = []
429
430let options =
431\{ "path": gr.path, "line_number": gr.line_number, "layer": a:layer }
432let original_link_options =
433\{ "path": gr.path, "line_number": gr.line_number,
434\"layer": a:layer, "original_link": v:true }
435
436let prefix_text = gr.line_number
437let prefix = self.CreateItem("link", prefix_text, g:AnyJumpGetColor('result_line_number'), options)
438
439call add(items, prefix)
440
441let matched_text = self.CreateItem("link", gr.text, g:AnyJumpGetColor('result_text'), original_link_options)
442
443call add(items, matched_text)
444
445return items
446endfu
447
448fu! s:InternalBuffer.RenderUiUsagesList(grep_results, start_ln) dict abort
449let start_ln = a:start_ln
450let hidden_count = 0
451
452" TODO: move to method
453if type(g:any_jump_max_search_results) == v:t_number
454\ && g:any_jump_max_search_results > 0
455\ && self.overmaxed_results_hidden == v:true
456
457let cp = self.current_page ? self.current_page : 1
458let to = (cp * g:any_jump_max_search_results) - 1
459
460let collection = self.usages_grep_results[0 : to]
461let hidden_count = len(self.usages_grep_results[to : -1])
462else
463let collection = self.usages_grep_results
464endif
465
466call self.AddLineAt([
467\self.CreateItem("text", ">", g:AnyJumpGetColor('heading_text'), {'layer': 'usages'}),
468\self.CreateItem("text", self.keyword, g:AnyJumpGetColor('heading_keyword'), {'layer': 'usages'}),
469\self.CreateItem("text", len(self.usages_grep_results) . " references", g:AnyJumpGetColor('heading_text'), {'layer': 'usages'}),
470\], start_ln)
471
472
473let start_ln += 1
474
475call self.AddLineAt([ self.CreateItem("text", "", "Comment", {"layer": "usages"}) ], start_ln)
476
477let start_ln += 1
478
479let idx = 0
480if self.grouping_enabled
481" group by file name rendering
482let render_map = {}
483
484for gr in collection
485if !has_key(render_map, gr.path)
486let render_map[gr.path] = []
487endif
488
489call add(render_map[gr.path], gr)
490endfor
491
492let path_idx = 0
493for path in keys(render_map)
494let first_gr = render_map[path][0]
495let opts = {
496\"path": path,
497\"line_number": first_gr.line_number,
498\"layer": "usages",
499\"group_header": v:true,
500\}
501
502let prefix = self.CreateItem("link", ">", g:AnyJumpGetColor('group_text'), opts)
503let group_name = self.CreateItem("link", path, g:AnyJumpGetColor('group_name'), opts)
504let line = [ prefix, group_name ]
505
506call self.AddLineAt(line, start_ln)
507let start_ln += 1
508
509for gr in render_map[path]
510let items = self.GrepResultToGroupedItems(gr, idx, "usages")
511call self.AddLineAt(items, start_ln)
512
513let start_ln += 1
514let idx += 1
515endfor
516
517if path_idx != len(keys(render_map)) - 1
518call self.AddLineAt([ self.CreateItem("text", "", "Comment", {"layer": "usages"}) ], start_ln)
519
520let start_ln += 1
521endif
522
523let path_idx += 1
524endfor
525else
526for gr in collection
527let items = self.GrepResultToItems(gr, idx, "usages")
528call self.AddLineAt(items, start_ln)
529
530let idx += 1
531let start_ln += 1
532endfor
533endif
534
535if hidden_count > 0
536call self.AddLineAt([ self.CreateItem("text", "", "Comment", {"layer": "usages"}) ], start_ln)
537let start_ln += 1
538
539call self.AddLineAt([
540\self.CreateItem("more_button", '[ ' . hidden_count . ' more ]', g:AnyJumpGetColor('more_button'), {"layer": "usages"}),
541\self.CreateItem("more_button", '— [a] load more results [A] load all', g:AnyJumpGetColor('more_explain'), {"layer": "usages"}),
542\], start_ln)
543let start_ln += 1
544endif
545
546call self.AddLineAt([ self.CreateItem("text", " ", "Comment", {"layer": "usages"}) ], start_ln)
547
548return v:true
549endfu
550
551fu! s:InternalBuffer.RenderUi() dict abort
552" clear items before render
553let self.items = []
554
555call self.AddLine([ self.CreateItem("text", "", "Comment") ])
556
557call self.AddLine([
558\self.CreateItem("text", ">", g:AnyJumpGetColor('heading_text')),
559\self.CreateItem("text", self.keyword, g:AnyJumpGetColor('heading_keyword')),
560\self.CreateItem("text", len(self.definitions_grep_results) . " definitions", g:AnyJumpGetColor('heading_text')),
561\])
562
563call self.AddLine([ self.CreateItem("text", "", "Comment") ])
564
565" draw grep results
566let idx = 0
567let hidden_count = 0
568
569" TODO: move to method
570if type(g:any_jump_max_search_results) == v:t_number
571\ && g:any_jump_max_search_results > 0
572\ && self.overmaxed_results_hidden == v:true
573
574let cp = self.current_page ? self.current_page : 1
575let to = (cp * g:any_jump_max_search_results) - 1
576
577let collection = self.definitions_grep_results[0 : to]
578let hidden_count = len(self.definitions_grep_results[to : -1])
579else
580let collection = self.definitions_grep_results
581endif
582
583if self.grouping_enabled
584" group by file name rendering
585let render_map = {}
586
587for gr in collection
588if !has_key(render_map, gr.path)
589let render_map[gr.path] = []
590endif
591
592call add(render_map[gr.path], gr)
593endfor
594
595let path_idx = 0
596
597for path in keys(render_map)
598let first_gr = render_map[path][0]
599let opts = {
600\"path": path,
601\"line_number": first_gr.line_number,
602\"layer": "definitions",
603\"group_header": v:true,
604\}
605
606let prefix = self.CreateItem("link", ">", g:AnyJumpGetColor('group_text'), opts)
607let group_name = self.CreateItem("link", path, g:AnyJumpGetColor('group_name'), opts)
608let line = [ prefix, group_name ]
609
610call self.AddLine(line)
611
612for gr in render_map[path]
613let items = self.GrepResultToGroupedItems(gr, idx, "definitions")
614call self.AddLine(items)
615
616let idx += 1
617endfor
618
619if path_idx != len(keys(render_map)) - 1
620call self.AddLine([ self.CreateItem("text", "", "Comment") ])
621endif
622
623let path_idx += 1
624endfor
625
626call self.AddLine([ self.CreateItem("text", "", "Comment") ])
627else
628if len(collection)
629for gr in collection
630let items = self.GrepResultToItems(gr, idx, "definitions")
631call self.AddLine(items)
632
633let idx += 1
634endfor
635else
636call self.AddLine([ self.CreateItem("text", "No definitions results", g:AnyJumpGetColor('plain_text')) ])
637endif
638
639call self.AddLine([ self.CreateItem("text", "", "Comment") ])
640endif
641
642if hidden_count > 0
643call self.AddLine([
644\self.CreateItem("more_button", '[ + ' . hidden_count . ' more ]', g:AnyJumpGetColor('more_button')),
645\self.CreateItem("more_button", '— [a] load more results [A] load all', g:AnyJumpGetColor('more_explain')),
646\])
647call self.AddLine([ self.CreateItem("text", "", "Comment") ])
648endif
649
650if self.usages_opened && len(self.usages_grep_results) > 0
651call self.RenderUiUsagesList(self.usages_grep_results, self.len() + 1)
652endif
653
654call self.HelpSection()
655endfu
656
657fu! s:InternalBuffer.HelpSection() abort
658if g:any_jump_show_help_section
659call self.AddLine([ self.CreateItem("help_link", "> Help", g:AnyJumpGetColor('heading_text')) ])
660
661let color = g:AnyJumpGetColor('help')
662
663call self.AddLine([ self.CreateItem("help_text", "", color) ])
664call self.AddLine([ self.CreateItem("help_text", "[o] open [t] open in tab [s] open in split [v] open in vsplit", color) ])
665call self.AddLine([ self.CreateItem("help_text", "[p/tab] preview file [b] scroll to first result", color) ])
666call self.AddLine([ self.CreateItem("help_text", "[a] load more results [A] load all results", color) ])
667call self.AddLine([ self.CreateItem("help_text", "[r] show references [T] group by file", color) ])
668call self.AddLine([ self.CreateItem("help_text", "[L] toggle search [esc/q] exit", color) ])
669call self.AddLine([ self.CreateItem("help_text", " results ui style", color) ])
670endif
671endfu
672
673fu! s:InternalBuffer.RemoveGarbagedLines() dict abort
674" remove marked for garbage collection lines
675let new_items = []
676
677for line in self.items
678if has_key(line[0], 'gc') == v:false || line[0].gc == v:false
679call add(new_items, line)
680endif
681endfor
682
683let self.items = new_items
684endfu
685
686
687" Public api
688fu! internal_buffer#GetClass() abort
689return s:InternalBuffer
690endfu
691