Como listar buffers abertos simplificado

Sep 07 2020

Eu gostaria de usar o lscomando para mostrar buffers abertos.

Eu gostaria de tornar a saída desse comando o mais simples possível. Gostaria de ver apenas os nomes, números e status dos buffers (#,%, +, ..).

Não preciso ver a primeira linha no resultado que mostra o caminho completo do arquivo atual e também não preciso ver os caminhos dos buffers e o número da linha atual em cada buffer. Mostra o caminho apenas se dois buffers tiverem o mesmo nome e vierem de diretórios diferentes.

Minha ideia por trás disso é que eu gostaria de conseguir isso usando os recursos integrados do vim. Existe uma maneira de tornar a lssaída tão simples?

Respostas

6 user938271 Sep 07 2020 at 21:36

Experimente isto:

command -bar -bang Ls call s:ls(<bang>0)

function s:ls(bang) abort
    let bufnrs = range(1, bufnr('$')) call filter(bufnrs, a:bang ? {_, v -> bufexists(v)} : {_, v -> buflisted(v)}) let bufnames = copy(bufnrs) \ ->map({_, v -> bufname(v)->fnamemodify(':t')}) let uniq_flags = copy(bufnames)->map({_, v -> count(bufnames, v) == 1}) let items = map(bufnrs, {i, v -> #{ \ bufnr: v, \ text: s:gettext(v, uniq_flags[i]), \ }}) call setloclist(0, [], ' ', #{ \ items: items, \ title: 'ls' .. (a:bang ? '!' : ''), \ quickfixtextfunc: 's:quickfixtextfunc', \ }) lopen nmap <buffer><nowait><expr><silent> <cr> <sid>Cr() endfunction function s:Cr() if w:quickfix_title =~# '^ls!\=$'
        let locid = win_getid()
        return "\<c-w>\<cr>\<plug>(close-location-window)" .. locid .. "\<cr>\<plug>(verticalize)"
    else
        return "\<c-w>\<cr>\<plug>(verticalize)"
    endif
endfunction
nnoremap <plug>(close-location-window) :<c-u>call <sid>CloseLocationWindow()<cr>
nnoremap <plug>(verticalize) :<c-u>wincmd L<cr>
function s:CloseLocationWindow()
    let locid = input('')->str2nr()
    call win_execute(locid, 'close')
endfunction

function s:gettext(v, is_uniq) abort
    let format = ' %*d%s%s%s%s%s %s'
    let bufnr = [bufnr('$')->len(), a:v]
    let buflisted = !buflisted(a:v) ? 'u': ' '
    let cur_or_alt = a:v == bufnr('%') ? '%' : a:v == bufnr('#') ? '#' : ' '
    let active_or_hidden = win_findbuf(a:v)->empty() ? 'h' : 'a'
    let modifiable = getbufvar(a:v, '&ma', 0) ? ' ' : '-'
    let modified = getbufvar(a:v, '&mod', 0) ? '+' : ' '
    let bufname = bufname(a:v)->empty()
        \ ?  '[No Name]'
        \ :   bufname(a:v)->fnamemodify(a:is_uniq ? ':t' : ':p')
    return call('printf', [format]
        \ + bufnr
        \ + [buflisted, cur_or_alt, active_or_hidden, modifiable, modified, bufname])
endfunction

function s:quickfixtextfunc(info) abort
    let items = getloclist(a:info.winid, #{id : a:info.id, items : 1}).items
    let l = []
    for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
        call add(l, items[idx].text)
    endfor
    return l
endfunction

Ele instala um :Lscomando que deve exibir uma lista de buffer simplificada na janela de localização. Pressione Enter em uma entrada para pular para o buffer.

Assim como com o embutido :ls, acrescente um bang ( :Ls!) para incluir os buffers não listados.

Isso requer uma versão recente do Vim que inclui estes patches:

  • 8.2.0869 não é possível personalizar o conteúdo da janela de correção rápida
  • 8.2.0933 'quickfixtextfunc' não obtém o ID da janela da lista de locais
  • 8.2.0959 usando 'quickfixtextfunc' é um pouco lento

Para ganhar eficiência, legibilidade e confiabilidade, você pode reescrever o código no script Vim9:

vim9

command -bar -bang Ls Ls(<bang>0)

def Ls(bang: any)
    var bufnrs = range(1, bufnr('$'))
    filter(bufnrs, bang ? (_, v) => bufexists(v) : (_, v) => buflisted(v))
    var bufnames = copy(bufnrs)
        ->mapnew((_, v) => bufname(v)->fnamemodify(':t'))
    var uniq_flags = copy(bufnames)
        ->mapnew((_, v) => count(bufnames, v) == 1)
    var items = mapnew(bufnrs, (i, v) => ({
        bufnr: v,
        text: Gettext(v, uniq_flags[i]),
        }))
    setloclist(0, [], ' ', {
        items: items,
        title: 'ls' .. (bang ? '!' : ''),
        quickfixtextfunc: 'Quickfixtextfunc',
        })
    lopen
    nmap <buffer><nowait><expr><silent> <cr> <sid>Cr()
enddef

def Cr(): string
    if w:quickfix_title =~ '^ls!\=$' var locid = win_getid() return "\<c-w>\<cr>\<plug>(close-location-window)" .. locid .. "\<cr>\<plug>(verticalize)" else return "\<c-w>\<cr>\<plug>(verticalize)" endif enddef nnoremap <plug>(close-location-window) :<c-u>call <sid>CloseLocationWindow()<cr> nnoremap <plug>(verticalize) :<c-u>wincmd L<cr> def CloseLocationWindow() var locid = input('')->str2nr() win_execute(locid, 'close') enddef def Gettext(v: number, is_uniq: bool): string var format = ' %*d%s%s%s%s%s %s' var bufnr = [bufnr('$')->len(), v]
    var buflisted = !buflisted(v) ? 'u' : ' '
    var cur_or_alt = v == bufnr('%') ? '%' : v == bufnr('#') ? '#' : ' '
    var active_or_hidden = win_findbuf(v)->empty() ? 'h' : 'a'
    var modifiable = getbufvar(v, '&ma', 0) ? ' ' : '-'
    var modified = getbufvar(v, '&mod', 0) ? '+' : ' '
    var bufname = bufname(v)->empty()
        ?  '[No Name]'
        :   bufname(v)->fnamemodify(is_uniq ? ':t' : ':p')
    return call('printf', [format]
        + bufnr
        + [buflisted, cur_or_alt, active_or_hidden,
           modifiable, modified, bufname])
enddef

def Quickfixtextfunc(info: dict<number>): list<any>
    var items = getloclist(info.winid, {id: info.id, items: 1}).items
    var l = []
    for idx in range(info.start_idx - 1, info.end_idx - 1)
        add(l, items[idx].text)
    endfor
    return l
enddef

Isso requer uma versão ainda mais recente do Vim. Ele funciona em 8.2.2332.

Você não precisa escrever o código em seu vimrc, apenas coloque-o ~/.vim/plugin/myls.vim. Teste-o; guarde-o pelo tempo que você achar útil; remova-o assim que encontrar algo melhor.


Para obter mais informações, consulte:

  • : h 'quickfixtextfunc'
  • : h CTRL-W_
  • : h: com
  • : h: command-bang
  • : h: barra de comando
  • : h: função
  • : h: func-abort
  • : h: def
  • : h: lopen
  • : h: wincmd
  • : h add ()
  • : h bufexists ()
  • : h buflisted ()
  • : h bufname ()
  • : h bufnr ()
  • : h: ligar
  • : h call ()
  • : cópia h ()
  • : contagem de h ()
  • : h vazio ()
  • : filtro h ()
  • : h fnamemodify ()
  • : h getbufvar ()
  • : h getloclist ()
  • : h setloclist ()
  • : entrada h ()
  • : h len ()
  • : h map ()
  • : h printf ()
  • : intervalo h ()
  • : h str2nr ()
  • : h win_execute ()
  • : h win_findbuf ()
4 LucHermitte Sep 07 2020 at 18:12

Em meu plug-in de biblioteca , tenho um :Project lsrecurso que lista apenas os arquivos que pertencem ao projeto atual, como os :lsteria listado.

O código relacionado (GPL com exceção) é o seguinte:

function! s:As_ls(bid) abort " {{{2
  let name = bufname(a:bid)
  if empty(name)
    let name = 'Used to be known as: '.lh#project#__buffer(a:bid)
  endif
  return printf('%3d%s %s'
        \ , a:bid
        \ , (buflisted(a:bid) ? ' ' : 'u')
        \ . (bufnr('%') == a:bid ? '%' : bufnr('#') == a:bid ? '#' : ' ')
        \ . (! bufloaded(a:bid) ? ' ' : bufwinnr(a:bid)<0 ? 'h' : 'a')
        \ . (! getbufvar(a:bid, "&modifiable") ? '-' : getbufvar(a:bid, "&readonly") ? '=' : ' ')
        \ . (getbufvar(a:bid, "&modified") ? '+' : ' ')
        \ , '"'.name.'"')
endfunction

function! s:ls_project(prj) abort " {{{2
  if lh#option#is_unset(a:prj)
    echo '(no project specified!)'
  endif
  let lines = map(copy(a:prj.buffers), 's:As_ls(v:val)')
  echo "Buffer list of ".get(a:prj, 'name', '(unnamed)')." project:"
  echo join(lines, "\n")
endfunction

Em vez de a:prj.buffers, você teria a lista de buffers em range(1, bufnr('$')). Ele então precisaria ser filtrado para manter apenas os buffers existentes ( filter(range(1, bufnr('$')), 'bufexists(v:val)')).

Em seguida, você pode encerrar a chamada para bufname()com fnamemodify()para exibir exatamente o que você precisa.

IOW, a s:ls_projectfunção deve se tornar algo assim (não testado).

function! s:ls() abort " {{{2
  let buffers = filter(range(1, bufnr('$')), 'bufexists(v:val)')
  let lines = map(copy(buffers), 's:As_ls(v:val)')
  echo "Buffer list:"
  echo join(lines, "\n")
endfunction

command! -nargs=0 LS call s:ls()
" or with a mapping
nnoremap <leader>r :<c-u>call <sid>ls()<cr>

E então você terá que brincar com o s:As_ls()código para fazê-lo produzir o que você deseja ver. a:bidé o parâmetro formal que contém o id do buffer, e para saber o que cada função faz, veja por exemplo :h buflisted(), e assim por diante.

1 filbranden Sep 07 2020 at 13:54

Existe uma maneira de tornar a lssaída tão simples?

Não nativamente ... O Vim realmente não oferece suporte a opções para personalizar ou controlar a saída do :lscomando.

Dito isso, você pode escrever seu próprio comando definido pelo usuário que implementa o que você deseja.

Você pode usar execute('ls')para capturar a saída da string do comando original e, em seguida, pós-processá-la (possivelmente usando expressões regulares e a substitute()função) ou pode consultar os buffers individuais um por um para reunir as informações sobre eles você mesmo. Você pode usar bufnr('$')para encontrar o maior número de buffer (para saber onde parar de procurá-los).

Um comando do usuário precisa começar com maiúsculas, portanto, você só pode fazer :Lsou :LS. Mas uma coisa que você pode fazer é usar um :cnoremappara capturar o uso de :lsna linha de comando Ex e substituí-lo por sua implementação alternativa. Mas tome cuidado, pois :cnoremapnormalmente é muito amplo, ele se aplica a qualquer lugar na linha de comando e também afeta a pesquisa com /, avisando o usuário com input()etc. Portanto, normalmente você deseja usar um <expr>mapeamento que executa várias verificações antes de fazer uma substituição real. Uma boa técnica é fazer um :cnoremap <expr> <CR>que só acione quando você estiver prestes a enviar um comando, então você pode inspecionar todo o comando e fazer uma substituição, se necessário.

Então, sim, é factível ... Mas se você quer minha opinião, é muito trabalhoso. YMMV.