Cómo listar búferes abiertos simplificado

Sep 07 2020

Me gustaría usar el lscomando para mostrar los búferes abiertos.

Me gustaría que la salida de este comando sea lo más simple posible. Me gustaría ver solo los nombres, números y estado de los búferes (#,%, +, ..).

No necesito ver la primera línea en el resultado que muestra la ruta completa del archivo actual y tampoco necesito ver los parches de los búferes y el número de línea actual en cada búfer. Solo muestra la ruta si dos búferes tienen el mismo nombre y provienen de directorios diferentes.

Mi idea detrás de esto es que me gustaría lograrlo usando las funciones integradas de vim. ¿Hay alguna manera de hacer lsque la salida sea tan simple?

Respuestas

6 user938271 Sep 07 2020 at 21:36

Prueba esto:

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

Instala un :Lscomando que debería mostrar una lista de búfer simplificada en la ventana de ubicación. Presione Enter en una entrada para saltar al búfer.

Al igual que con el builtin :ls, agregue un bang ( :Ls!) para incluir los búferes no listados.

Esto requiere una versión reciente de Vim que incluya estos parches:

  • 8.2.0869 no es posible personalizar el contenido de la ventana de corrección rápida
  • 8.2.0933 'quickfixtextfunc' no obtiene el ID de ventana de la lista de ubicaciones
  • 8.2.0959 usar 'quickfixtextfunc' es un poco lento

Para ganar en eficiencia, legibilidad y confiabilidad, puede reescribir el código en el 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

Esto requiere una versión de Vim aún más reciente. Funciona en 8.2.2332.

No tiene que escribir el código en su vimrc, simplemente introdúzcalo ~/.vim/plugin/myls.vim. Pruébalo; guárdelo mientras lo encuentre útil; retírelo una vez que encuentre algo mejor.


Para obtener más información, consulte:

  • : h 'quickfixtextfunc'
  • : h CTRL-W_
  • : h: com
  • : h: comando-bang
  • : h: barra de comandos
  • : h: función
  • : h: función-abortar
  • : h: def
  • : h: lopen
  • : h: wincmd
  • : h agregar ()
  • : h bufexistas ()
  • : h en búfer ()
  • : h nombre bufón ()
  • : h bufnr ()
  • : h: llamar
  • : h llamada ()
  • : h copiar ()
  • : h cuenta ()
  • : h vacío ()
  • : filtro h ()
  • : h fnamemodify ()
  • : h getbufvar ()
  • : h getloclist ()
  • : h setloclist ()
  • : h entrada ()
  • : h len ()
  • : h mapa ()
  • : h printf ()
  • : rango h ()
  • : h str2nr ()
  • : h win_execute ()
  • : h win_findbuf ()
4 LucHermitte Sep 07 2020 at 18:12

En el complemento de mi biblioteca , tengo una :Project lsfunción que enumera solo los archivos que pertenecen al proyecto actual como los :lshabría enumerado.

El código relacionado (GPL con excepción) es el siguiente:

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

En lugar de a:prj.buffers, tendría la lista de búferes en formato range(1, bufnr('$')). Luego, debería filtrarse para mantener solo los búferes existentes ( filter(range(1, bufnr('$')), 'bufexists(v:val)')).

Posteriormente, se podría envolver la llamada a bufname()la fnamemodify()de mostrar exactamente lo que necesita.

IOW, la s:ls_projectfunción se convertirá en algo como esto (sin probar).

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>

Y luego solo tendrás que jugar con el s:As_ls()código para que produzca lo que deseas ver. a:bides el parámetro formal que contiene la identificación del búfer, y para saber qué hace cada función, consulte, por ejemplo :h buflisted(), y así sucesivamente.

1 filbranden Sep 07 2020 at 13:54

¿Hay alguna manera de hacer lsque la salida sea tan simple?

No de forma nativa ... Vim realmente no admite opciones para personalizar o controlar la salida del :lscomando.

Dicho esto, puede escribir su propio comando definido por el usuario que implemente lo que desee.

Puede usar execute('ls')para capturar la salida de la cadena del comando original y luego procesarlo (posiblemente usando expresiones regulares y la substitute()función), o puede consultar los búferes individuales uno por uno para ensamblar la información sobre ellos usted mismo. Puede usar bufnr('$')para encontrar el número de búfer más alto (para que sepa dónde dejar de buscarlos).

Sin embargo, un comando de usuario debe comenzar con mayúsculas, por lo que solo puede hacer :Lso :LS. Pero una cosa que puede hacer es usar a :cnoremappara capturar el uso de :lsen la línea de comandos Ex y reemplazarlo con su implementación alternativa. Pero tenga cuidado de que :cnoremapnormalmente es demasiado amplio, se aplica en cualquier parte de la línea de comandos y también afecta la búsqueda con /, preguntar al usuario con input(), etc. Por lo general, desea utilizar un <expr>mapeo que realice varias comprobaciones antes de realizar un reemplazo real. Una buena técnica es hacer una :cnoremap <expr> <CR>que solo se active cuando está a punto de enviar un comando, luego puede inspeccionar el comando completo y hacer un reemplazo si es necesario.

Entonces, sí, es factible ... Pero si quieres mi opinión, es demasiado trabajo. YMMV.