├── .gitignore
├── CONTRIBUTING.markdown
├── README.markdown
├── addon-info.json
├── autoload
└── flagship.vim
├── doc
└── flagship.txt
└── plugin
└── flagship.vim
/.gitignore:
--------------------------------------------------------------------------------
1 | /doc/tags
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.markdown:
--------------------------------------------------------------------------------
1 | See the [contribution guidelines for pathogen.vim](https://github.com/tpope/vim-pathogen/blob/master/CONTRIBUTING.markdown).
2 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # flagship.vim
2 |
3 | Flagship provides a Vim status line and tab line that are both easily
4 | customizable by the user and extensible by other plugins.
5 |
6 | ## Installation
7 |
8 | Copy and paste for [pathogen.vim](https://github.com/tpope/vim-pathogen):
9 |
10 | cd ~/.vim/bundle
11 | git clone https://github.com/tpope/vim-flagship.git
12 | vim -u NONE -c "helptags vim-flagship/doc" -c q
13 |
14 | While not strictly required, I highly recommend the following options:
15 |
16 | set laststatus=2
17 | set showtabline=2
18 | set guioptions-=e
19 |
20 | The first two force the status line and tab line to always display, and the
21 | third disables the GUI tab line in favor of the plain text version, enabling
22 | global flags and the tab prefix explained below.
23 |
24 | ## Extension
25 |
26 | Adding a flag from a plugin is a simple matter of calling `Hoist()` with a
27 | scope and function name from a `User Flags` autocommand. Here's an example
28 | from [fugitive.vim](https://github.com/tpope/vim-fugitive):
29 |
30 | autocmd User Flags call Hoist("buffer", "fugitive#statusline")
31 |
32 | You can also do this in your vimrc, for example if a plugin provides a
33 | statusline flag function but does not natively integrate with Flagship. If
34 | the function isn't defined (e.g., you temporarily disable or permanently
35 | remove the plugin), it will be skipped. Here's a couple of mine:
36 |
37 | autocmd User Flags call Hoist("window", "SyntasticStatuslineFlag")
38 | autocmd User Flags call Hoist("global", "%{&ignorecase ? '[IC]' : ''}")
39 |
40 | ## Customization
41 |
42 | The extension API is great for adding flags, but what if you want to change
43 | the core content? For the status line, Vim already provides a perfectly
44 | adequate `'statusline'` option, and Flagship will use it in constructing its
45 | own. Customizing your status line is exactly the same with and without
46 | Flagship.
47 |
48 | The tab line is another story. The usual technique (see
49 | `:help setting-tabline`) involves creating a function that cycles through each
50 | tab and assembles a giant format string. Furthermore, while you can use the
51 | same status line "%" items, they're expanded in the context of the active
52 | window only, rendering most of them worthless for any tab but the current.
53 | Rather than embrace this abomination, Flagship hides it, instead exposing
54 | a `g:tablabel` option which can be assigned to customize the format of a
55 | single tab. Additionally, you can set `g:tabprefix` to define content to be
56 | inserted before the first tab (assuming you disabled the GUI tab line as
57 | instructed above).
58 |
59 | The default tab label is nearly impossible to precisely reconstruct, and I
60 | never really found it useful, so I've taken it a different direction. Here's
61 | how it would look if you set `g:tablabel` yourself, using a few of the many
62 | helpers available:
63 |
64 | let g:tablabel =
65 | \ "%N%{flagship#tabmodified()} %{flagship#tabcwds('shorten',',')}"
66 |
67 | Here's a breakdown of what's included:
68 |
69 | * The tab number, so you never have to hesitate on `gt` invocation.
70 | * One `+` per modified window. Vim's default shows the status of the tab's
71 | current window only, which can be misleading.
72 | * A compact representation of the working directories of each window. For
73 | determining what project a tab is on, I find this far more useful than the
74 | filename.
75 |
76 | Additionally, I've chosen to prefix the tab line with the Vim GUI server name
77 | (see `:help v:servername`) if available, or the current host name if SSHed.
78 | This only takes a few characters, and I find it to be greatly helpful in
79 | reducing confusion when running multiple instances of Vim. (Assign
80 | `g:tabprefix` if you don't like it.)
81 |
82 | ## Self-Promotion
83 |
84 | Like flagship.vim? Follow the repository on
85 | [GitHub](https://github.com/tpope/vim-flagship) and vote for it on
86 | [vim.org](http://www.vim.org/scripts/script.php?script_id=5199). And if
87 | you're feeling especially charitable, follow [tpope](http://tpo.pe/) on
88 | [GitHub](https://github.com/tpope).
89 |
90 | ## License
91 |
92 | Copyright © Tim Pope. Distributed under the same terms as Vim itself.
93 | See `:help license`.
94 |
--------------------------------------------------------------------------------
/addon-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "vim_script_nr": 5199,
3 | "name": "flagship",
4 | "version": "1.1",
5 | "author": "Tim Pope ",
6 | "repository" : {
7 | "type": "git",
8 | "url": "git://github.com/tpope/vim-flagship.git"
9 | },
10 | "description": "Configurable and extensible tab line and status line"
11 | }
12 |
--------------------------------------------------------------------------------
/autoload/flagship.vim:
--------------------------------------------------------------------------------
1 | " Location: autoload/flagship.vim
2 | " Author: Tim Pope
3 |
4 | if exists('g:autoloaded_flagship')
5 | finish
6 | endif
7 | let g:autoloaded_flagship = 1
8 |
9 | " Section: General
10 |
11 | " Remove duplicates from a list. Different from uniq() in that the duplicates
12 | " do not have to be consecutive.
13 | function! flagship#uniq(list) abort
14 | let i = 0
15 | let seen = {}
16 | while i < len(a:list)
17 | let str = string(a:list[i])
18 | if has_key(seen, str)
19 | call remove(a:list, i)
20 | else
21 | let seen[str] = 1
22 | let i += 1
23 | endif
24 | endwhile
25 | return a:list
26 | endfunction
27 |
28 | " Double %'s, preventing them from being expanded.
29 | function! flagship#escape(expr) abort
30 | return substitute(a:expr, '%', '%%', 'g')
31 | endfunction
32 |
33 | " Surround the value in brackets, but return an empty string for empty input.
34 | " Give additional arguments to use characters other than brackets.
35 | function! flagship#surround(str, ...) abort
36 | if empty(a:str)
37 | return ''
38 | endif
39 | let match = {'[': ']', '{': '}', '(': ')', '<': '>', ',': ''}
40 | let open = a:0 ? a:1 : '['
41 | return open . a:str . (a:0 > 1 ? a:2 : get(match, open, open))
42 | endfunction
43 |
44 | " Remove surrounding brackets, whitespace, and commas.
45 | function! flagship#clean(str) abort
46 | return substitute(a:str, '^[[, ]\|[], ]$', '', 'g')
47 | endfunction
48 |
49 | " Call a function with arguments if it exists. Otherwise return ''. Useful
50 | " if you are not sure if a plugin is installed. Here's an example that I use
51 | " for Syntastic:
52 | "
53 | " %#ErrorMsg#%{flagship#try('SyntasticStatuslineFlag')}%*
54 | function! flagship#try(...) abort
55 | let args = copy(a:000)
56 | let dict = {}
57 | let default = ''
58 | if type(get(args, 0)) == type({})
59 | let dict = remove(args, 0)
60 | endif
61 | if type(get(args, 0)) == type([])
62 | let default = remove(args, 0)[0]
63 | endif
64 | if empty(args)
65 | return default
66 | endif
67 | let Func = remove(args, 0)
68 | if type(Func) == type(function('tr')) || exists('*'.Func)
69 | return call(Func, args, dict)
70 | else
71 | return default
72 | endif
73 | endfunction
74 |
75 | " Call a function with flagship#try() and normalize the result with
76 | " flagship#clean() and flagship#surround().
77 | function! flagship#call(...) abort
78 | let dict = {'host': 'flagship'}
79 | return flagship#surround(flagship#clean(call('flagship#try', [dict] + a:000)))
80 | endfunction
81 |
82 | " Create a dictionary from alternating keys and values. Useful because it's
83 | " impossible to nest a dictionary literal in %{} expressions.
84 | function! flagship#dict(...) abort
85 | let dict = {}
86 | for i in range(0, a:0-1, 2)
87 | let dict[a:000[i]] = dict[a:000[i+1]]
88 | endfor
89 | return dict
90 | endfunction
91 |
92 | " Currently logged in user.
93 | function! flagship#user() abort
94 | if has('win32')
95 | return $USERNAME
96 | elseif empty($LOGNAME) && has('unix')
97 | let me = system('whoami')[1:-1]
98 | let $LOGNAME = v:shell_error ? fnamemodify(expand('~'), ':t') : me
99 | endif
100 | return empty($LOGNAME) ? 'unknown' : $LOGNAME
101 | endfunction
102 |
103 | " Returns v:servername if present, or @hostname() when sshed. This is the
104 | " default tab prefix.
105 | function! flagship#id() abort
106 | let servername = v:servername
107 | if has('nvim')
108 | let servername = fnamemodify(servername, ':h:t')
109 | endif
110 | return servername . (empty($SSH_TTY) ? '': '@'.substitute(hostname(), '\..*', '', ''))
111 | endfunction
112 |
113 | " Returns "Help" for help buffers and the filetype otherwise.
114 | " flagship#surround(flagship#filetype()) essentially combines %h and %y.
115 | function! flagship#filetype(...) abort
116 | let buffer = bufnr((a:0 && a:1 isnot 0) ? a:1 : '%')
117 | if getbufvar(buffer, '&buftype') ==# 'help'
118 | return 'Help'
119 | else
120 | return getbufvar(buffer, '&filetype')
121 | endif
122 | endfunction
123 |
124 | " Section: Tab Page
125 |
126 | " All tab functions accept a tab number as this first optional argument,
127 | " with a default of v:lnum. Note that v:lnum is set to the tab number
128 | " automatically in a tab label.
129 |
130 | " Return the active buffer number for the tab.
131 | function! flagship#tabbufnr(...) abort
132 | let tab = a:0 ? a:1 : v:lnum
133 | return tab > 0 ? tabpagebuflist(v:lnum)[tabpagewinnr(v:lnum)-1] : 0
134 | endfunction
135 |
136 | " Returns a string consisting of one plus sign for each modified buffer and
137 | " one exclamation point for each terminal buffer in the given tab number.
138 | " If no tab number is given, use the tab number in v:lnum.
139 | function! flagship#tabmodified(...) abort
140 | let tab = a:0 ? a:1 : v:lnum
141 | let str = ''
142 | for tab in tab ? [tab] : range(1, tabpagenr('$'))
143 | for buf in tabpagebuflist(tab)
144 | if getbufvar(buf, '&buftype') ==# 'terminal'
145 | let str .= '!'
146 | elseif getbufvar(buf, '&modified')
147 | let str .= '+'
148 | endif
149 | endfor
150 | endfor
151 | return str
152 | endfunction
153 |
154 | " Return the number of times a given buffer variable is nonempty for the given
155 | " tab number.
156 | function! flagship#tabcountbufvar(var, ...) abort
157 | let tab = a:0 ? a:1 : v:lnum
158 | let cnt = 0
159 | for tab in tab ? [tab] : range(1, tabpagenr('$'))
160 | for buf in tabpagebuflist(tab)
161 | let cnt += !empty(getbufvar(buf, a:var))
162 | endfor
163 | endfor
164 | if !cnt
165 | return ''
166 | else
167 | return cnt
168 | endif
169 | endfunction
170 |
171 | " Return the number of times a given window variable is nonempty for the given
172 | " tab number.
173 | function! flagship#tabcountwinvar(var, ...) abort
174 | let tab = a:0 ? a:1 : v:lnum
175 | let cnt = 0
176 | for tab in tab ? [tab] : range(1, tabpagenr('$'))
177 | for win in range(1, tabpagewinnr(tab, '$'))
178 | let cnt += !empty(gettabwinvar(tab, win, a:var))
179 | endfor
180 | endfor
181 | if !cnt
182 | return ''
183 | else
184 | return cnt
185 | endif
186 | endfunction
187 |
188 | " Section: Current Working Directory
189 |
190 | " Returns the local working directory for a given tab and window number. Pass
191 | " zero as both arguments to get the global working directory (ignoring the
192 | " current window). Will return a path relative to 'cdpath' when possible;
193 | " pass 'raw' as an additional argument to disable this. Pass 'shorten' to
194 | " call a variant of pathshorten() on the result.
195 | function! flagship#cwd(...) abort
196 | call flagship#winleave()
197 | let args = copy(a:000)
198 | let gcwd = exists('*haslocaldir') ? get(g:, 'flagship_cwd', '') : getcwd()
199 | if a:0 > 1 && a:1 && a:2
200 | if !exists('g:flagship_no_getcwd_local') && has('patch-7.4.1126')
201 | let path = getcwd(a:2, a:1)
202 | else
203 | let path = gettabwinvar(a:1, a:2, 'flagship_cwd')
204 | endif
205 | let path = empty(path) ? gcwd : path
206 | let buf = bufname(tabpagebuflist(a:1)[a:2-1])
207 | elseif a:0 && a:1 is# 0
208 | let path = gcwd
209 | elseif type(get(args, 0, '')) !=# type(0)
210 | let path = getcwd()
211 | let buf = bufname('')
212 | else
213 | throw 'Invalid flagship#cwd arguments'
214 | endif
215 | while type(get(args, 0, '')) == type(0)
216 | call remove(args, 0)
217 | endwhile
218 | if index(args, 'raw') < 0
219 | let path = s:cwdpresent(path)
220 | endif
221 | if index(args, 'shorten') >= 0
222 | let path = matchstr(path, '^[^\/]*') . pathshorten(matchstr(path, '[\/].*'))
223 | endif
224 | return path
225 | endfunction
226 |
227 | " Return a unique list of all working directories for a given tab or v:lnum.
228 | " Accepts the 'raw' and 'shorten' flags from flagship#cwd().
229 | function! flagship#tabcwds(...) abort
230 | call flagship#winleave()
231 | let args = copy(a:000)
232 | let tabnr = type(get(args, 0, '')) == type(0) ? remove(args, 0) : v:lnum
233 | let gcwd = exists('*haslocaldir') ? get(g:, 'flagship_cwd', '') : getcwd()
234 | let path = []
235 | for t in tabnr ? [tabnr] : range(1, tabpagenr('$'))
236 | let types = map(tabpagebuflist(t), 'getbufvar(v:val, "&buftype")')
237 | let all_typed = empty(filter(copy(types), 'empty(v:val)'))
238 | for w in range(1, tabpagewinnr(t, '$'))
239 | if empty(types[w-1]) || all_typed
240 | call add(path, call('flagship#cwd', [t, w] + args))
241 | endif
242 | endfor
243 | endfor
244 | if index(args, 'raw') < 0
245 | call flagship#uniq(path)
246 | endif
247 | let join = get(filter(args, 'v:val =~# "[[:punct:][:space:]]"'), 0, '')
248 | return empty(join) ? path : join(path, join)
249 | endfunction
250 |
251 | " Section: Private Implementation
252 |
253 | function! s:slash() abort
254 | return has('+shellslash') && !&shellslash ? '\' : '/'
255 | endfunction
256 |
257 | function! s:locatepath(path, paths) abort
258 | let path = a:path
259 | let parent = ''
260 | for entry in a:paths
261 | if empty(entry)
262 | continue
263 | endif
264 | for dir in split(glob(entry), "\n")
265 | if dir !~# '\'.s:slash().'$'
266 | let dir .= s:slash()
267 | endif
268 | if strpart(a:path, 0, len(dir)) ==# dir && len(a:path) - len(dir) < len(path)
269 | let parent = dir
270 | let path = strpart(a:path, len(dir))
271 | endif
272 | endfor
273 | endfor
274 | return [parent, path]
275 | endfunction
276 |
277 | function! s:cwdpresent(dir) abort
278 | let parents = map(split(&cdpath, ','), 'expand(v:val)')
279 | let dir = a:dir
280 | call filter(parents, '!empty(v:val) && v:val !=# expand("~")')
281 | let dir = s:locatepath(dir, parents)[1]
282 | return substitute(dir, '^'.escape(expand('~'), '\'), '\~', '')
283 | endfunction
284 |
285 | function! s:cpath(path, ...) abort
286 | if exists('+fileignorecase') && &fileignorecase
287 | let path = tolower(a:path)
288 | else
289 | let path = a:path
290 | endif
291 | let path = tr(path, s:slash(), '/')
292 | return a:0 ? path ==# s:cpath(a:1) : path
293 | endfunction
294 |
295 | function! flagship#filename(...) abort
296 | if &buftype ==# 'quickfix'
297 | return '[Quickfix List]'
298 | elseif &buftype =~# '^\%(nofile\|acwrite\|terminal\)$'
299 | return empty(@%) ? '[Scratch]' : @%
300 | elseif empty(@%)
301 | return '[No Name]'
302 | elseif &buftype ==# 'help'
303 | return fnamemodify(@%, ':t')
304 | endif
305 | let f = @%
306 | let ns = substitute(matchstr(f, '^\a\a\+\ze:'), '^\a', '\u&', 'g')
307 | if len(ns) && exists('*' . ns . 'Real')
308 | try
309 | let f2 = {ns}Real(f)
310 | if !empty(f2)
311 | let f = f2
312 | endif
313 | catch
314 | endtry
315 | endif
316 | let cwd = getcwd()
317 | let home = expand('~')
318 | if s:cpath((f . '/')[0 : len(cwd)], cwd . '/')
319 | let f = f[len(cwd) + 1 : -1]
320 | let f = len(f) ? f : '.'
321 | elseif len(home) && s:cpath((f . '/')[0 : len(home)], home . '/')
322 | let f = '~' . f[len(home) : -1]
323 | endif
324 | return f
325 | endfunction
326 |
327 | unlet! s:did_setup
328 | function! flagship#enter() abort
329 | let s:mark = tabpagenr().'-'.winnr()
330 | if !exists('s:did_setup')
331 | call flagship#setup()
332 | endif
333 | endfunction
334 |
335 | function! flagship#winleave() abort
336 | let id = tabpagenr().'-'.winnr()
337 | if tabpagenr().'-'.winnr() !=# get(s:, 'mark', '')
338 | return
339 | elseif !exists('*haslocaldir') || haslocaldir()
340 | let w:flagship_cwd = getcwd()
341 | else
342 | unlet! w:flagship_cwd
343 | let g:flagship_cwd = getcwd()
344 | endif
345 | let cwds = g:flagship_cwd
346 | for t in range(1, tabpagenr('$'))
347 | for w in range(1, tabpagewinnr(t, '$'))
348 | let cwds .= "\n" . gettabwinvar(t, w, 'flagship_cwd')
349 | endfor
350 | endfor
351 | let g:FlagshipCwds = cwds
352 | endfunction
353 |
354 | function! flagship#session_load_post() abort
355 | if &sessionoptions =~ 'sesdir'
356 | let g:flagship_cwd = fnamemodify(v:this_session, ':h')
357 | endif
358 | if &sessionoptions =~# 'globals' && exists('g:FlagshipCwds')
359 | let cwds = split(g:FlagshipCwds, "\n", 1)
360 | let dir = remove(cwds, 0)
361 | let wins = 0
362 | for t in range(1, tabpagenr('$'))
363 | let wins += tabpagewinnr(t, '$')
364 | endfor
365 | if wins !=# len(cwds)
366 | return
367 | endif
368 | if &sessionoptions =~# 'curdir'
369 | let g:flagship_cwd = dir
370 | endif
371 | for t in range(1, tabpagenr('$'))
372 | for w in range(1, tabpagewinnr(t, '$'))
373 | let dir = remove(cwds, 0)
374 | if !empty(dir)
375 | call settabwinvar(t, w, 'flagship_cwd', dir)
376 | endif
377 | endfor
378 | endfor
379 | endif
380 | endfunction
381 |
382 | function! s:tabexpand(count, char, tab) abort
383 | let w = tabpagewinnr(a:tab)
384 | let b = tabpagebuflist(a:tab)[w-1]
385 | if a:char ==# 'N'
386 | let s = a:tab
387 | elseif a:char ==# 'f'
388 | let s = bufname(b)
389 | elseif a:char ==# 'F'
390 | let s = fnamemodify(bufname(b), ':p')
391 | elseif a:char ==# 'm'
392 | let s = getbufvar(b, '&modified') ? '[+]' : (getbufvar(b, '&modifiable') ? '' : '[-]')
393 | elseif a:char ==# 'M'
394 | let s = getbufvar(b, '&modified') ? ',+' : (getbufvar(b, '&modifiable') ? '' : ',-')
395 | else
396 | return '%' . a:count . a:char
397 | endif
398 | return '%'.a:count.'('.flagship#escape(s).'%)'
399 | endfunction
400 |
401 | function! s:tablabel(tab, fmt) abort
402 | if a:fmt =~# '^%!'
403 | let fmt = eval(a:fmt[2:-1])
404 | else
405 | let fmt = a:fmt
406 | endif
407 | return substitute(fmt, '%\(-\=\d*\%(\.\d*\)\=\)\([NFfMm%]\)', '\=s:tabexpand(submatch(1), submatch(2), a:tab)', 'g')
408 | endfunction
409 |
410 | function! flagship#in(...) abort
411 | let v:lnum = a:0 ? a:1 : tabpagenr()
412 | return ''
413 | endfunction
414 |
415 | function! s:in(...) abort
416 | return '%{flagship#in('.(a:0 ? a:1 : '').')}'
417 | endfunction
418 |
419 | function! s:tabfmtvar(var, ...) abort
420 | if get(g:, a:var, '') =~# '^%!'
421 | return eval(get(g:, a:var)[2:-1])
422 | else
423 | return get(g:, a:var, a:0 ? a:1 : '')
424 | endif
425 | endfunction
426 |
427 | function! flagship#tablabel() abort
428 | return s:tabfmtvar('tablabel', '%N') . s:flags('tabpage')
429 | endfunction
430 |
431 | function! s:hinorm(expr, highlight) abort
432 | return substitute(a:expr, '%\*', '%#'.a:highlight.'#', 'g')
433 | endfunction
434 |
435 | function! flagship#tablabels() abort
436 | let s = ''
437 |
438 | let lasttabpagenr = tabpagenr('$')
439 | for t in range(1, lasttabpagenr)
440 | let hi = t == tabpagenr() ? 'TabLineSel' : 'TabLine'
441 | let v:lnum = t
442 | let label = s:tablabel(t, flagship#tablabel())
443 | let s .= '%#'.hi.'#%'.t.'T'.s:in(t).' '.s:hinorm(label, hi).' '
444 | if t != lasttabpagenr
445 | let s .= '%#TabLineFill#%T'.g:tabinfix
446 | endif
447 | endfor
448 |
449 | return s . '%#TabLineFill#%T'.s:in()
450 | endfunction
451 |
452 | function! flagship#tabline(...) abort
453 | let hi = flagship#user() ==# 'root' ? 'ErrorMsg' : 'TabLineFill'
454 | let prefix = s:tabfmtvar('tabprefix')
455 | let suffix = s:tabfmtvar('tabsuffix')
456 | if prefix.suffix !~# '%='
457 | let suffix = '%=' . suffix
458 | endif
459 | if prefix.suffix !~# '%<'
460 | let suffix = '%<' . suffix
461 | endif
462 | let s = '%{flagship#in('.tabpagenr().')}'
463 | \ . '%#' . hi . '#'
464 | \ . s:hinorm(prefix . s:flags('global'), hi)
465 | \ . ' ' . call('flagship#tablabels', a:000)
466 | \ . s:hinorm(suffix, 'TabLineFill')
467 | return s:hinorm(s, 'TabLineFill')
468 | endfunction
469 |
470 | function! flagship#statusline(...) abort
471 | let s = a:0 ? a:1 : ''
472 | if s =~# '^%!'
473 | let s = eval(s[2:-1])
474 | endif
475 | if empty(s)
476 | let s = '%<%f %{flagship#surround(flagship#filetype())}%w%m%r'
477 | endif
478 | if s !~# '%='
479 | let rulerformat = (empty(&rulerformat) ? '%-14.(%l,%c%V%) %P' : &rulerformat)
480 | let s .= '%=' . (&ruler ? ' '.rulerformat : '')
481 | endif
482 | let s = s:in('winnr()=='.winnr().'?'.tabpagenr().':-'.winnr()).s.s:in(0)
483 | let s = substitute(s, '%-\=\d*\.\=\d*\zsf\(\s\)\=', '{flagship#filename()."\1"}', 'g')
484 | return substitute(s, '%=', '\=s:flags("file").s:flags("buffer")."%=".s:flags("window",-1)', '')
485 | endfunction
486 |
487 | function! flagship#_hoist(type, ...) abort
488 | if type(a:type) != type('') || a:type !~# '^[a-z]'
489 | return
490 | endif
491 | if !exists('s:new_flags')
492 | throw 'Hoist from User Flags autocommand only'
493 | endif
494 | let args = copy(a:000)
495 | if type(get(args, 0, '')) != type(0)
496 | call insert(args, 0)
497 | endif
498 | let args[0] = printf('%09d', args[0])
499 | if len(args) < 2
500 | return
501 | elseif len(args) == 2
502 | call add(args, {})
503 | elseif type(args[1]) == type({})
504 | let [args[1], args[2]] = [args[2], args[1]]
505 | endif
506 | if !has_key(s:new_flags, a:type)
507 | let s:new_flags[a:type] = []
508 | endif
509 | let flags = s:new_flags[a:type]
510 | let index = index(map(copy(flags), 'v:val[1]'), args[1])
511 | if index < 0
512 | call add(flags, args)
513 | else
514 | let flags[index][0] += args[0]
515 | call extend(flags[index][2], args[2], 'keep')
516 | endif
517 | endfunction
518 |
519 | function! flagship#flags_for(type) abort
520 | let flags = []
521 | for [F, opts; rest] in exists('s:flags') ? get(s:flags, a:type, []) : []
522 | let str = join([F])
523 | unlet! F Hl
524 | if !empty(get(g:, 'flagship_skip', '')) && str =~# g:flagship_skip
525 | let flag = ''
526 | elseif str =~# '^function('
527 | let flag = '%{flagship#call('.str.')}'
528 | elseif str =~# '^\d\+$'
529 | let flag = '%{flagship#call(function('.string(str).'))}'
530 | elseif str =~# '^\%(\h\|\)[[:alnum:]_#]*$' && exists('*'.str)
531 | let flag = '%{flagship#call('.string(str).')}'
532 | elseif str =~# '^%!'
533 | let flag = eval(str[2:-1])
534 | elseif str =~# '%'
535 | let flag = str
536 | else
537 | let flag = ''
538 | endif
539 | if empty(flag)
540 | continue
541 | endif
542 | let Hl = get(opts, 'hl', '')
543 | if type(Hl) == type('') && hlexists(substitute(Hl, '^\d$', 'User&', ''))
544 | if Hl =~# '^\d$'
545 | let flag = '%'.Hl.'*'.flag.'%*'
546 | elseif Hl ==? 'ignore'
547 | continue
548 | elseif !empty(Hl)
549 | let flag = '%#'.Hl.'#'.flag.'%*'
550 | endif
551 | endif
552 | call add(flags, flag)
553 | endfor
554 | return flags
555 | endfunction
556 |
557 | function! s:flags(type, ...) abort
558 | let flags = flagship#flags_for(a:type)
559 | if a:0 && a:1 is -1
560 | call reverse(flags)
561 | endif
562 | return join(flags, '')
563 | endfunction
564 |
565 | function! flagship#setup(...) abort
566 | if a:0 && a:1
567 | unlet! g:tablabel g:tabprefix
568 | if a:1 > 1
569 | setglobal statusline=
570 | endif
571 | endif
572 | if !exists('g:tablabel') && !exists('g:tabprefix')
573 | redir => blame
574 | silent verbose set showtabline?
575 | redir END
576 | if &showtabline == 1 && blame !~# "\t"
577 | set showtabline=2
578 | endif
579 | if exists('+guitablabel') && empty(&guitablabel)
580 | set guioptions-=e
581 | endif
582 | endif
583 | if !exists('g:tablabel')
584 | let g:tablabel =
585 | \ "%N%{flagship#tabmodified()} %{flagship#tabcwds('shorten',',')}"
586 | endif
587 | if !exists('g:tabprefix')
588 | let g:tabprefix = "%{flagship#id()}"
589 | endif
590 | if !exists('g:tabinfix')
591 | let g:tabinfix = ""
592 | endif
593 | if !empty(g:tablabel)
594 | set tabline=%!flagship#tabline()
595 | if exists('+guitablabel')
596 | set guitablabel=%!flagship#tablabel()
597 | endif
598 | endif
599 | if empty(&g:statusline)
600 | setglobal statusline=%!flagship#statusline()
601 | if &laststatus == 1
602 | set laststatus=2
603 | endif
604 | elseif &g:statusline !~# '^%!'
605 | let &g:statusline = '%!flagship#statusline('.string(&g:statusline).')'
606 | elseif &g:statusline !~# 'flagship#statusline'
607 | let &g:statusline = '%!flagship#statusline('.&g:statusline[2:-1].')'
608 | endif
609 | let s:new_flags = {}
610 | let modelines = &modelines
611 | try
612 | let g:Hoist = function('flagship#_hoist')
613 | function! Hoist(...) abort
614 | return call(g:Hoist, a:000)
615 | endfunction
616 | if exists('#User#Flags')
617 | if v:version >= 704 || (v:version == 703 && has('patch442'))
618 | doautocmd User Flags
619 | else
620 | let &modelines = 0
621 | doautocmd User Flags
622 | endif
623 | endif
624 | for [k, v] in items(s:new_flags)
625 | call map(sort(v), 'v:val[1:-1]')
626 | endfor
627 | unlockvar s:flags
628 | let s:flags = s:new_flags
629 | lockvar! s:flags
630 | finally
631 | if &modelines != modelines
632 | let &modelines = modelines
633 | endif
634 | unlet! s:new_flags g:Hoist
635 | if exists('*Hoist')
636 | delfunction Hoist
637 | endif
638 | endtry
639 | let s:did_setup = 1
640 | let &l:readonly = &l:readonly
641 | endfunction
642 |
643 | " vim:set et sw=2 foldmethod=expr foldexpr=getline(v\:lnum)=~'^\"\ Section\:'?'>1'\:getline(v\:lnum)=~#'^fu'?'a1'\:getline(v\:lnum)=~#'^endf'?'s1'\:'=':
644 |
--------------------------------------------------------------------------------
/doc/flagship.txt:
--------------------------------------------------------------------------------
1 | *flagship.txt* Configurable and extensible tab line and status line
2 |
3 | Author: Tim Pope
4 | Repo: https://github.com/tpope/vim-flagship
5 | License: Same terms as Vim itself (see |license|)
6 |
7 | SETUP *flagship*
8 |
9 | While not strictly required, the following options are highly recommended:
10 | >
11 | set laststatus=2
12 | set showtabline=2
13 | set guioptions-=e
14 | <
15 | *g:tablabel* *g:tabprefix* *g:tabsuffix*
16 | The default status line is a slightly tweaked version of Vim's default. To
17 | override it, :setglobal 'statusline' as usual. Vim's default tab label is
18 | difficult to reproduce (and in this humble plugin artist's opinion, not very
19 | useful), so the default is instead based around the tab's current working
20 | directories. To override it, assign |g:tablabel|. If you are not using the
21 | GUI tabline, you can also assign |g:tabprefix| and |g:tabsuffix| to control
22 | the content before and after the tabs themselves. The defaults are shown
23 | below:
24 | >
25 | let g:tabprefix = '%{flagship#id()}'
26 | let g:tablabel =
27 | \ "%N%{flagship#tabmodified()} %{flagship#tabcwds('shorten',',')}"
28 |
29 | If you adjust none of these configuration options, Flagship assumes you want
30 | the preferred setup, and will automatically set the 3 options listed at the
31 | top of this section.
32 |
33 | EXTENSION *flagship-Hoist()*
34 |
35 | The function Hoist() is may be called from from a User Flags autocommand to
36 | register a flag:
37 | >
38 | autocmd User Flags call Hoist({scope}, ..., {flag})
39 | <
40 | The exact arguments to Hoist are covered in order below.
41 |
42 | The first argument is the scope and must be one of the following:
43 |
44 | There are four supported scopes:
45 |
46 | Flag Default display position ~
47 | "buffer" left of the status line split
48 | "window" right of the status line split
49 | "tabpage" end of the tab label
50 | "global" end of the tab prefix
51 |
52 | Generally you will want to use "buffer" or "global". The "window" scope is
53 | for aspects of the window independent of the buffer itself, for example the
54 | cursor position or whether diff mode is enabled. Since these window
55 | properties typically have a visual presence, use of a flag is often redundant
56 | and unnecessary.
57 |
58 | Next comes the position argument, which defaults to 0 (zero) and can almost
59 | always be omitted. If you have a flag that is particularly volatile, try
60 | giving a positive number like 10 to sort it later. If on the other hand it
61 | very rarely changes, you might consider a negative number like -10 to sort it
62 | earlier.
63 |
64 | After that comes an optional dictionary of options. The only currently
65 | supported option is the experimental "hl", which names a highlight group to
66 | use for the flag. (Tip: re-add a plugin flag with a "hl" of "Ignore" in your
67 | vimrc to disable it.)
68 |
69 | Finally comes the flag itself, one of the following:
70 |
71 | - A function reference
72 | - A string naming a function
73 | - A format string containing one or more % statusline expressions
74 |
75 | For the first two, the function will be called from a %{} expression with zero
76 | arguments, but this is not guaranteed: use |...| for future compatibility.
77 | The result of the function will be wrapped in brackets if it isn't already.
78 | Future versions may allow customizing the wrapping characters.
79 |
80 | The % statusline format is not recommended for plugin use, but can be used for
81 | a quick and dirty flag definition in one's vimrc. For example, to show a
82 | global indicator when 'ignorecase' is set:
83 | >
84 | autocmd User Flags call Hoist("global", "%{&ic?'[ic]':''}")
85 | <
86 | If you are implementing your own statusline plugin, you may implement this
87 | same interface to tap into the existing ecosystem of flags. This is the basic
88 | technique:
89 | >
90 | try
91 | function! Hoist(...) abort
92 | " Your implementation
93 | endfunction
94 | doautocmd User Flags
95 | finally
96 | delfunction Hoist
97 | endtry
98 | <
99 | Make sure your implementation of Hoist() ignores unrecognized arguments and
100 | never throws an exception, for future compatibility.
101 |
102 | vim:tw=78:et:ft=help:norl:
103 |
--------------------------------------------------------------------------------
/plugin/flagship.vim:
--------------------------------------------------------------------------------
1 | " flagship.vim
2 | " Author: Tim Pope
3 |
4 | if exists('g:loaded_flagship')
5 | finish
6 | endif
7 | let g:loaded_flagship = 1
8 |
9 | if !exists('g:flagship_cwd')
10 | let g:flagship_cwd = getcwd()
11 | endif
12 |
13 | augroup flagship
14 | autocmd!
15 | autocmd WinEnter,VimEnter * call flagship#enter()
16 | autocmd SessionLoadPost * call flagship#session_load_post()
17 | augroup END
18 |
--------------------------------------------------------------------------------