summaryrefslogtreecommitdiff
path: root/files/vim/plugin/qname.1.2.vim
blob: 4e813182801a9efd95d07d7345badb52315f7f54 (plain)
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
" =============================================================================
" QuickName:	The most convenient way navigating opened buffers,
"				incremental search by name
" Last Change:	2013-01-03
" Maintainer:	Pal Nart <palnart@gmail.com>
" Version:		1.2
" License:		Vim License
" =============================================================================

" https://www.vim.org/scripts/script.php?script_id=2317
"
" Change Log
" EDIT:
"  2025-06-24:
"	Remove redraw on update
"	Don't wrap around in the list
"	ls! -> ls
"	<Tab> -> <C-j/k>
"	Take out built-in hotkey
"  2025-08-05:
"	Use path relative to current dir
"	Match against file path
"  2025-08-13:
"	Sort buffers by most recently used
"	Add bind to "flatten" the list
"	Replace fmatch() with built-in matchfuzzy() (speedup)
"	Fix logic to not include buffers with modifiable off
"  2025-10-30:
"   Treat <S-BS> the same as <BS>
"  2025-11-04:
"   Open selected buffer in the current window, even if the same buffer is
"   already open in another window.
"
" 1.2	2013-01-03
" ADD: <Tab> and <S-Tab> moves selection like <Up> and <Down>
" ADD: Use <silent> mapping to keep cmdline clear
" FIX: Catch interrupt (<C-C> or <Esc> keypress) and restore cmd line
" FIX: Use case-sensitive string comparisons to afford immunity from value
"	   of 'ignorecase'
" FIX: Do not clobber contents of @y register
"
" 1.1	2009-07-19
" ADD: Key Shift+Enter open buffer in a new horizontally split window. Thanks
" to contribution by Salman Halim
" ADD: Key Ctrl+U reset input line and show all buffers
" FIX: If a buffer is already associated with a window, navigate to that
" window instead of opening in current window
"
" 1.0	2008-07-30
"	Initial upload

if v:version < 700
	finish
endif

if exists("g:qname_loaded") && g:qname_loaded
	finish
endif
let g:qname_loaded = 1

function! QNameRun()
	try
		call s:colPrinter.print()
		echo "\rMatch" len(s:n)."/".len(s:ls) "names:" s:inp
		call inputsave()
		let _key = getchar()
		if !type(_key)
			let _key = nr2char(_key)
		endif

		" use case-sensitive equality operator, otherwise <BS> looks like
		" <S-Tab> if 'ignorecase' is set
		if _key ==# "\<BS>" || _key ==# "\<S-BS>"
			let s:inp = s:inp[:-2]
		elseif _key ==# "\<C-U>"
			let s:inp = ""
		elseif strlen(_key) == 1 && char2nr(_key) > 31
			let s:inp = s:inp._key
		endif

		if _key ==# "\<ESC>" || _key ==# "\<CR>" || _key ==# "\<S-CR>"
			let _sel = s:colPrinter.sel
			if _key != "\<ESC>" && _sel < len(s:n) && _sel >= 0
				if _key ==# "\<S-CR>"
					:split
				endif
				call s:swb(str2nr(matchstr(s:s[_sel], '<\zs\d\+\ze>')))
			endif
			call QNameInit(0)
		elseif _key ==# "\<Up>" || _key ==# "\<C-k>"
			call s:colPrinter.vert(-1)
		elseif _key ==# "\<Down>" ||  _key ==# "\<C-j>"
			call s:colPrinter.vert(1)
		elseif _key ==# "\<Left>"
			call s:colPrinter.horz(-1)
		elseif _key ==# "\<Right>"
			call s:colPrinter.horz(1)
		elseif _key ==# "\<C-f>"
			let s:flat = 1 - s:flat
			let prev_sel = s:colPrinter.sel
			call s:build()
			let s:colPrinter.sel = prev_sel
		else
			call s:build()
		endif
	catch /^Vim:.*$/
		" restore cmdheight if search is interrupted
		cal QNameInit(0)
	finally
		" clean up screen after dismissing search window
		call inputrestore()
	endtry
endfunc

function! QNameInit(start)
	if a:start
		cmap <silent> ~ cal QNameRun()<CR>:~
		let s:cmdh = &cmdheight
		if a:start != -1
			let s:inp = ""
			let s:flat = 0
		endif
		call s:baselist()
		call s:build()
		exe "set cmdheight=".(s:colPrinter.trow+1)
	else
		cmap <silent> ~ exe "cunmap \x7E"<CR>
		exe "set cmdheight=".s:cmdh
	 endif
endfunc

function! s:build()
	let s:s = []
	let s:n = []
	let s:sp = []
	let s:np = []
	let s:blen = 0
	let _cmp = tolower(tr(s:inp, '\', '/'))
	for _line in s:ls
		"				   Match everthing before |<xx>| Match everthing after space.
		let _match = matchlist(_line, '^.\{-}\ze \+<.*> \+\(.*\)')
		let _name = _match[0]
		let _path = _match[1]
		if strlen(_cmp) == 0 || s:fmatch(tolower(_name), _cmp)
			cal add(s:s, _line)
			cal add(s:n, _name)
		elseif s:fmatch(tolower(_path), _cmp)
			cal add(s:sp, _line)
			cal add(s:np, _name)
		endif
	endfor
	" Put filename matches before path matches.
	let s:n = s:n + s:np
	let s:s = s:s + s:sp
	if !s:flat && len(s:n) > s:colPrinter.trow
		cal s:colPrinter.put(s:n)
	else
		cal s:colPrinter.put(s:s)
	endif
endfunc

function! s:swb(bno)
	exe "hid b" a:bno
endfunc

function! s:fmatch(src,pat)
	return len(matchfuzzy([a:src], a:pat)) > 0
endfunc

function! s:baselist()
	let s:ls = []
	redir => l:bufs | silent ls t | redir END
	for _line in split(l:bufs, "\n")
		if _line[6] != '-'
			let _bno = matchstr(_line, '^ *\zs\d*')+0
			let _fname = substitute(expand("#"._bno.":p"), '\', '/', 'g')
			if _fname == ""
				let _fname = "|".matchstr(_line, '"\[\zs[^\]]*')."|"
			endif
			let _name = fnamemodify(_fname,":t")
			cal add(s:ls, _name." <"._bno."> ".fnamemodify(_fname,":."))
		endif
	endfor
	let _align = max(map(copy(s:ls),'stridx(v:val,">")'))
	call map(s:ls, 'substitute(v:val, " <", repeat(" ",_align-stridx(v:val,">"))." <", "")')
endfunc

let s:colPrinter = {"trow": 5}
function! s:colPrinter.put(its) dict
	let _cols = []
	let _trow = self.trow

	let _its = copy(a:its)
	let _len = len(_its)
	let _i = 0
	while _i < _len
		if _i+_trow <= _len
			cal add(_cols, remove(_its,0,_trow-1))
		else
			cal add(_cols, _its)
		endif
		let _i += _trow
	endwhile

	let _cpos = [0]
	let _cw = []
	let _t = 0
	for _li in _cols
		let _w = max(map(copy(_li),'strlen(v:val)'))+4
		let _t += _w
		cal add(_cpos,_t)
		cal add(_cw,_w)
	endfor

	let _rows = []
	for _i in range(_trow)
		let _row = []
		for _j in range(len(_cols))
			if _j*_trow+_i < _len
				cal add(_row,_cols[_j][_i])
			endif
		endfor
		cal add(_rows, _row)
	endfor

	let self.cols = _cols
	let self.cw = _cw
	let self.rows = _rows
	let self.cpos = _cpos
	let self.len = _len
	let self.lcol = 0
	let self.sel = 0
endfunc

function! s:colPrinter.horz(mv) dict
	let _t = self.sel + a:mv*self.trow
	if _t >= 0 && _t < self.len
		let self.sel = _t
	endif
endfunc

function! s:colPrinter.vert(mv) dict
	let _t = self.sel + a:mv
	let _len = self.len
	if _t >= 0 && _t < _len
		let self.sel = _t
	endif
endfunc

function! s:colPrinter.print() dict
	let _len = self.len
	let _trow = self.trow
	if !_len
		echo "	[...NO MATCH...]" repeat("\n",_trow)
		return
	endif
	let _sel = self.sel
	let _t = _sel/_trow
	let _cpos = self.cpos
	let _lcol = self.lcol
	let _tcol = s:flat ? 1 : &columns
	if _cpos[_lcol]+_tcol < _cpos[_t+1]
		let _rcol = _t
		let _pos = _cpos[_t+1]-_tcol-2
		while _cpos[_lcol] < _pos
			let _lcol += 1
		endwhile
		let _lcol -= _lcol > _t
	else
		if _t < _lcol
			let _lcol = _t
		endif
		let _rcol = len(_cpos)-1
		let _pos = _cpos[_lcol]+_tcol+2
		while _cpos[_rcol] > _pos
			let _rcol -= 1
		endwhile
		let _rcol -= _rcol > _lcol
	endif
	let _cw = self.cw
	let _pos = _cpos[_lcol]+_tcol
	let self.lcol = _lcol
	for _i in range(_trow)
		let _row = self.rows[_i]
		for _j in range(_lcol,_rcol)
			if _j*_trow+_i < _len
				let _txt = "  " . _row[_j]
				let _txt .= repeat(" ", _cw[_j] - strlen(_txt))
				let _txt = _txt[:_pos-_cpos[_j]-2]
				if _j*_trow + _i == _sel
					echoh Search|echon _txt|echoh None
				else
					echon _txt
				endif
			endif
		endfor
		echon "\n"
	endfor
endfunc