├── .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 | --------------------------------------------------------------------------------