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
|