Ryan Rueger

ryan@rueg.re / picture / key / home
aboutsummaryrefslogtreecommitdiff
path: root/plugin/statusryne.vim
blob: ba432fc260a0e735db776c492fa55e91621f8cc0 (plain) (blame)
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
" Vim Plugin

if exists("g:statusryne") || &cp
  finish
endif

let s:keepcpo = &cpo
set cpo&vim

" Statusline {{{
" Always show statusline.
set laststatus=2
" Don't additionally show mode underneath statusline.
set noshowmode

" Mode Dict {{{
let g:currentmode={
    \ "n"      : "NORMAL",
    \ "no"     : "N-PENDING",
    \ "v"      : "VISUAL",
    \ "V"      : "V-LINE",
    \ "\<C-V>" : "V-Block",
    \ "s"      : "SELECT",
    \ "S"      : "S-LINE",
    \ "\<C-S>" : "S-BLOCK",
    \ "i"      : "INSERT",
    \ "R"      : "REPLACE",
    \ "Rv"     : "V-REPLACE",
    \ "c"      : "COMMAND",
    \ "cv"     : "VIM-EX",
    \ "ce"     : "EX",
    \ "r"      : "PROMPT",
    \ "rm"     : "MORE",
    \ "r?"     : "CONFIRM",
    \ "!"      : "SHELL",
    \ "t"      : "TERMINAL"
    \}
" }}}
" (f) Git Info {{{
let b:git_info = ''

augroup GetGitBranch
  autocmd!
  autocmd BufEnter,BufWritePost * let b:git_info = GitInfo()
augroup END

function! GitInfo()

  let git_cmd = 'git -C ' . expand('%:p:h')

  " git puts strange characters on branch output.
  " Sanitise output, remove non-printable characters bar space.
  let branch_cmd = git_cmd . ' rev-parse --abbrev-ref HEAD 2> /dev/null' . " | tr -dc '[:graph:] '"
  let branch = system(branch_cmd)

  " Get more information if in a git repo.
  if branch != ''
    let branch = '  (' . branch . ')'

    " Could be a new file in a git repo with no name yet.
    if expand('%:p') != ''
      " git puts strange characters on branch output.
      " Sanitise output, remove non-printable characters bar space.
      " Need to escape backslashes in sed expresssion
      let stats_cmd = git_cmd . ' diff --numstat ' . expand('%:p' ) . " 2> /dev/null | sed 's/^\\([0-9]*\\)\\s*\\([0-9]*\\).*$/\\1(+) \\2(-)/' | tr -dc '[:graph:] '"
      let stats = system(stats_cmd)

      if stats == ''
        " Could be a non-tracked file. Then there are no stats.
        return  branch . ' (Not tracked)'
      else
        return branch . ' ' . stats
      endif

    endif
  else
    return ''
  endif
endfunction
" }}}
" (f) Colours {{{
" 'bg' is text colour, 'fg' is the bar colour.
"
hi StatusLine ctermbg=010
hi StatusLine ctermfg=007

hi StatusLineExtra ctermbg=014
hi StatusLineExtra ctermfg=007

hi StatusLineMode ctermfg=015

" Automatically change the statusline color depending on mode.
function! ChangeStatuslineColor()
  if (mode() =~# '\v(n|no)')
    " Normal Mode.
    exe 'hi! StatusLineMode ctermbg=010'
  elseif (mode() =~# '\v(v|V)')
    " Visual Mode.
    exe 'hi! StatusLineMode ctermbg=005'
  elseif (mode() ==# 'i')
    " Insert Mode.
    exe 'hi! StatusLineMode ctermbg=004'
  elseif (mode() ==# 'R')
    " Replace Mode.
    exe 'hi! StatusLineMode ctermbg=001'
  else
    " Other Mode.
    exe 'hi! StatusLineMode ctermbg=010'
  endif

  return ''
endfunction
" }}}
" (f) Buffer Statistics {{{
let b:buf_stats = ''

augroup GetFileStats
  autocmd!
  autocmd BufEnter,BufWritePost * let b:buf_stats = FileStats()
augroup END

function! FileStats()

  " If buffer hasn't been written yet, don't get size.
  if filereadable(expand('%:p'))

    " Buffer size.
    let bytes = str2float(getfsize(expand('%:p')))

    for suffix in ['B', 'K', 'M', 'G']
      if (abs(bytes) < 1000)
        let size = string(float2nr(round(bytes))) . suffix
        break
      endif
      let bytes = bytes / 1000
    endfor

    " Word count
    let old_status = v:statusmsg
    exe ":silent normal g\<c-g>"
    if v:statusmsg == '--No lines in buffer--'
      let word_count = '0(w)'
      let char_count = '0(c)'
    else
      let word_count = str2nr(split(v:statusmsg)[11]) . '(w)'
      let char_count = str2nr(split(v:statusmsg)[15]) . '(c)'
    endif
    let v:statusmsg = old_status

    return word_count . ' ' . char_count . ' ' . size

  else
    return ''

  endif

endfunction
" }}}
" (f) Read Only {{{
let b:readonly_flag = ""

augroup ReadOnlyStatus
  autocmd!
  autocmd BufEnter,WinEnter * let b:readonly_flag = ReadOnly()
augroup END

function! ReadOnly()

  if &readonly || !&modifiable
    return "[READ ONLY]"
  else
    return ""

endfunction
" }}}
" (f) File Name {{{
" Change how much of the filename is displayed based on available terminal.
" space.

let b:filename = ""

augroup FileName
  autocmd!
  autocmd BufEnter,VimResized * let b:filename = FileName()
augroup END

function! FileName()

    let fullname = expand("%:p")

    if (fullname == "")
        return ' [New]'
    endif

    " See how much space is being occupied by the rest of the statusline
    " elements.
    let remainder =
          \ 25
          \ + len(&filetype)
          \ + len(&fenc)
          \ + len(g:currentmode[mode()])
          \ + len(get(b:, 'git_info', ''))
          \ + len(get(b:, 'readonly_flag', ''))
          \ + len(get(b:, 'buf_stats', ''))

    " If the full name doesn't fit, then use a shorter one.
    " Cases:
    " 1) Show full path.
    " 2) Show only first character of every directory in path.
    " 3) Show only basename.

    if (winwidth(0) > len(fullname) + remainder)

      let shortpath = substitute(fullname, $HOME, '~', "")
      return ' ' . shortpath

    elseif (winwidth(0) > len(pathshorten(fullname)) + remainder)

      let home = pathshorten($HOME . '/')
      let shortpath = substitute(pathshorten(fullname), home, '~/', "")
      return ' ' . shortpath

    else
        return ' ' . expand("%:t")
    endif

endfunction
" }}}

" Status Line Format String.
set statusline=
set statusline+=%{ChangeStatuslineColor()}
set statusline+=%#StatusLineMode#                               " Set colour.
set statusline+=\ %{g:currentmode[mode()]}                      " Get Mode.
set statusline+=\ %*                                            " Default colour.
set statusline+=%{get(b:,'filename','')}                        " Filename.
set statusline+=%{get(b:,'git_info','')}                        " Git branch.
set statusline+=%{get(b:,'readonly_flag','')}                   " Readonly flag.
set statusline+=\ %*                                            " Default colour.

set statusline+=\ %=                                            " Right Side.
set statusline+=\ %3(%{get(b:,'buf_stats','')}%)                " File size.
set statusline+=\ %#StatusLineExtra#                            " Set colour.
set statusline+=\ %<[%{substitute(&spelllang,'_..','','g')}] " Spell Language.
set statusline+=%{(&filetype!=''?'\ ['.&filetype.']':'')}       " FileType.
set statusline+=\ %#StatusLineMode#                             " Set colour.
set statusline+=\ %3p%%                                         " Percentage.
set statusline+=\ %3l/%-3L:%-3c\                                " Line/Column Numbers.
" }}}
" Tab Line {{{
" Only show tabline if there are two or more tabs.
set showtabline=1
set tabpagemax=10
set tabline=%!TabLine()

" Colours and Configurations {{{
let g:padding = 2
let g:mintablabellen = 5

hi TabLineSel ctermfg=007 cterm=None
hi TabLineSel ctermbg=010 cterm=None

hi TabLineNum ctermfg=015 cterm=None
hi TabLineNum ctermbg=014 cterm=None

hi TabLineBuffer ctermbg=014
hi TabLineBuffer ctermfg=007

hi TabLine cterm=None
hi TabLineFill cterm=None
" }}}
" (f) LabelName {{{
function! LabelName(n)
  " Decide label name for n'th buffer.

  let b:buftype = getbufvar(a:n, "&buftype")

  if b:buftype ==# 'help'
    " Show help page name and strip trailing '.txt'.
    let label = '[H] ' . fnamemodify(bufname(a:n), ':t:s/.txt$//')

  elseif b:buftype ==# 'quickfix'
    let label = '[Q]'

  elseif bufname(a:n) != ''
    " Only show basename (with extension) for regular files.
    let label = " " . fnamemodify(bufname(a:n), ':t')

  else
    let label = "[New]"

  endif

  return label

endfunction
" }}}
" (f) Maxlen {{{
function! MaxLen(iterable)
  let maxlen = 0
  for item in a:iterable
    if len(item) > maxlen
      let maxlen = len(item)
    endif
  endfor
  return maxlen
endfunction
" }}}
" (f) TabLine {{{
function! TabLine()
  " Set the format string for the tabline.

  " Explainer {{{
  " The main complexity in the following code arises due to the requirement to
  " have equally spaced tab labels. It means that we need to find what will be
  " visually displayed first (the chars printed to screen with spacing) to
  " find out how long each tab label will be and then go back over the buffer
  " list to add formatting strings (i.e. colour). If we did not do this, the
  " formatting strings would be included in the calculation of the length of
  " the tab labels, even though they don't take up any visual space.
  "
  " Example:
  " The format string
  "
  " '%1T%#TabLineSel#%#TabLineSel#    %#TabLineSel# vimrc %#TabLineBuffer# .tmux.conf     %2T%#TabLine#         [H] eval          %#TabLineFill#%T'
  "
  " takes up 141 chars, but will only display
  "
  " '     vimrc  .tmux.conf              [H] eval          '
  "
  " taking up 54 chars.
  " }}}
  " Get maximum length tab label {{{

  " Get all labels.
  let g:tablabels = []
  for t in range(tabpagenr('$'))
    let g:tablabel = ""
    for bufnum in tabpagebuflist(t + 1)
      let g:tablabel .= LabelName(bufnum)
    endfor
    call add(g:tablabels, g:tablabel)
  endfor

  " Find maximal length.
  " For equal with tabs, fitted to longest tab label.
  let g:maxlabellen = max([g:mintablabellen, MaxLen(g:tablabels)])
  " For full screen width equal width tabs.
  " let g:maxlabellen = winwidth(0) / tabpagenr('$')

  " }}}

  let tablfmt = ''

  " Iter over tabs.
  " Here we set the format strings, for correct colouring, we need to do it in
  " the following order:
  " '<colour><padding><buflabel>[[ <colour> <buflabel>]]*<padding>'

  for t in range(tabpagenr('$'))

    " New tab label begins here.
    let tablfmt .= '%' . (t+1) . 'T'

    " Set highlight.
    " #TabLineSel for selected tab, #TabLine otherwise.
    let tablfmt .= (t+1 == tabpagenr() ? '%#TabLineSel#' : '%#TabLine#')

    " Get buffer names and statuses.
    " Buffer names with formatting strings, colours etc.
    let t:labelfmt = ''
    " Number of buffers in tab.
    let t:bcount = len(tabpagebuflist(t+1))
    " Total amount of whitespace to fill, after considering curent tab label.
    let t:remainder = g:maxlabellen - len(g:tablabels[t])
    let t:pad = t:remainder /2 + g:padding

    " Iter over buffers in tab.
    for bnum in tabpagebuflist(t+1)

      " Set colour.
      if t+1 == tabpagenr()
        " If on current buffer in current tab, set bg colour dark.
        let bcolour = (bnum == bufnr("%") ? '%#TabLineSel#' : '%#TabLineBuffer#')
      else
        " If not on current tab, leave default bg colour.
        let bcolour = ''
      endif

      " Put padding before first buffer name in tab.
      if bnum == tabpagebuflist(t+1)[0]
        let t:labelfmt .= bcolour . repeat(" ", t:pad)
      endif

      let t:labelfmt .= bcolour . LabelName(bnum)

      " Don't add final space to buffer name.
      if t:bcount > 1
        let t:labelfmt .= ' '
      endif
      let t:bcount -= 1

    endfor

    let t:labelfmt .= repeat(" ", t:pad)
    let tablfmt .= t:labelfmt

  endfor

  " Fill to end.
  let tablfmt .= '%#TabLineFill#%T'

  return tablfmt
endfunction
" }}}
" }}}

let &cpo = s:keepcpo
unlet s:keepcpo