Como posso reformatar uma string de várias linhas no vim ao usar o tipo de arquivo Python?

23

Vamos supor que estou editando algum código Python no vim que se parece com:

myobj.myfunc("Some string parameter that goes on and on and on and on and sometimes doesn't"
             "split very"
             "neatly over different lines so that"
             "it is formatted attractively")

Eu preferiria reformatar isso para que reflita o que textwidtheu defini:

myobj.myfunc("Some string parameter that goes on and on and "
             "on and on and sometimes doesn't split very "
             "neatly over different lines so that it is "
             "formatted attractively")

Existe uma maneira fácil de fazer isso? Se esse fosse um parágrafo de texto convencional gqipou similar seria útil, mas isso não tratará das aspas usadas para delinear a string.

Nota : Estou perguntando especificamente sobre Python aqui, mas, idealmente, essa resposta seria relevante para muitos tipos de linguagem de programação que permitem a continuação de strings.

Andrew Ferrier
fonte
1
Pergunta semelhante aqui: vim.1045645.n5.nabble.com/…
Andrew Ferrier
3
Você também considerou reescrever o código como um """multi-line string"""?
200_success
1
@ 200_success, é uma ótima dica, obrigado. Eu não sabia nada sobre esse recurso do Python. Parece funcionar, principalmente - embora todos os parâmetros para a função sejam refletidos, o que não é o ideal. Acho que minha pergunta ainda é válida para o tipo de string que eu tenho, e certamente para outros idiomas.
Andrew Ferrier
2
Observe que uma string de várias linhas adicionará caracteres de espaço em branco, o que pode ser um problema se o espaço em branco for importante na string.
Matt Boehm
Pergunta muito relacionada: vi.stackexchange.com/questions/2135/... ...
Martin Tournoij

Respostas:

6

Oh, isso é difícil. Eu acho que a melhor abordagem é possivelmente uma macro, mas provavelmente um plugin. Aqui está um exemplo que eu criei (preciso de um hobby melhor). Pareceu funcionar para mim, mas seria necessário o plugin de recuo python para recuar corretamente. Experimente.

function ReformatMultiLines()
  let brx = '^\s*"'
  let erx = '"\s*$'
  let fullrx = brx . '\(.\+\)' . erx
  let startLine = line(".")
  let endLine   = line(".")
  while getline(startLine) =~ fullrx
    let startLine -= 1
  endwhile
  if getline(endLine) =~ erx
    let endLine += 1
  endif
  while getline(endLine) =~ fullrx
    let endLine += 1
  endwhile
  if startLine != endLine
    exec endLine . ' s/' . brx . '//'
    exec startLine . ' s/' . erx . '//'
    exec startLine . ',' . endLine . ' s/' . fullrx . '/\1/'
    exec startLine . ',' . endLine . ' join'
  endif
  exec startLine
  let orig_tw = &tw
  if &tw == 0
    let &tw = &columns
    if &tw > 79
      let &tw = 79
    endif
  endif
  let &tw -= 3 " Adjust for missing quotes and space characters
  exec "normal A%-%\<Esc>gqq"
  let &tw = orig_tw
  let endLine = search("%-%$")
  exec endLine . ' s/%-%$//'
  if startLine == endLine
    return
  endif
  exec endLine
  exec 'normal I"'
  exec startLine
  exec 'normal A "'
  if endLine - startLine == 1
    return
  endif
  let startLine += 1
  while startLine != endLine
    exec startLine
    exec 'normal I"'
    exec 'normal A "'
    let startLine += 1
  endwhile
endfunction

Então você pode usá-lo com :call ReformatMultiLines()e / ou usá-lo em um mapeamento.

Sukima
fonte
6

Se essa é uma ocorrência regular, é melhor procurar um plug-in ou usar a função do @Sukima. Se eu estivesse fazendo isso rapidamente, aqui está o que eu provavelmente faria:

  1. Adicione uma nova linha após o ponto de abertura e antes do ponto de fechamento, para que as strings estejam em linhas separadas.

    myobj.myfunc(
                 "Some string parameter that goes on and on and on and on and sometimes doesn't"
                 "split very"
                 "neatly over different lines so that"
                 "it is formatted attractively"
    )
    
  2. Selecione linhas com strings e exclua aspas: :norm ^x$x

  3. Reduzir a largura do texto (para contabilizar cotações ausentes) :set tw-=2
  4. Selecione novamente e formate: gvgq
  5. Corrigir largura do texto: :set tw+=2
  6. Citações adicionar novamente: gv:norm I"<Esc>A". Em vez de <Esc>querer inserir uma fuga literal, digite ctrl-v seguido pela tecla Escape. Desde que mapeio jj para escapar, geralmente apenas digito jj aqui.
  7. Opcionalmente, use J para juntar novamente as duas primeiras / últimas duas linhas. Quando eu tenho uma string muito longa em python como essa, normalmente prefiro iniciá-la na próxima linha e apenas recuá-la um nível a mais que a linha anterior. Isso dá mais espaço horizontal para a corda e me parece mais natural. Como alternativa, você pode salvar a string em uma variável em algum lugar acima. Supondo que seja uma sequência estática, você pode até salvá-la em uma variável global para que ela esteja em um nível de indentação muito mais baixo e possa caber em menos linhas. Observe que você deve voltar ao ponto de fechamento na mesma linha, provavelmente deseja diminuir / aumentar a largura do texto em 3 em vez de 2.
Matt Boehm
fonte
1
Obrigado. Eu apenas tentei isso, mas :set tw-=2resulta em E487: Argument must be positive: tw-=2. A sintaxe está incorreta?
Andrew Ferrier 23/03
1
Verifique o valor atual da largura do texto com :set tw?. Meu palpite é que você tenha a largura do texto definida como 0. Essas etapas pressupõem que a largura do texto começa como o número de caracteres que você deseja por linha.
Matt Boehm 23/03
No seu exemplo, você provavelmente deseja s/2/3/adicionar e um espaço antes da citação de fechamento, sua cadeia longa não teria espaçamento adequado entre as palavras.
Cdsborn #
1

A análise e a formatação do código são difíceis, especialmente para linguagens dinâmicas como Python.

Em vez de tentar analisar Python no Vimscript, recomendo que você possa usar um formatador externo como o yapf . Com a minha resposta aqui, você pode escrever isso automaticamente ao escrever com:

augroup write_cmd
    autocmd BufWritePre *.py call s:write_cmd('yapf')
augroup end

A s:write_cmd()função está na outra resposta - não a adicionarei aqui, portanto não precisarei atualizá-la em dois lugares se encontrar um bug :-)

Você também pode definir formatprga yapfe formatá-lo manualmente com gq.

Martin Tournoij
fonte
Oi. Resposta útil, mas estou com voto negativo, pois acho que não responde a essa pergunta: essa pergunta é muito específica sobre reformatar cadeias de linhas múltiplas, o que yapfparece não ser suficiente. No entanto, eu concordo que parece uma ferramenta útil.
Andrew Ferrier
@AndrewFerrier Fair bastante; Eu podia jurar que o vi reformatar as strings quando o testei outro dia, mas não consigo fazê-lo funcionar agora: - / Acho que fiquei confuso.
Martin Tournoij
0

As seguintes funções e mapeamentos correspondentes resolveram esse problema para mim:

function! BreakMultlineString(quote)
let c = 100
while c == 100
    normal 100|
    let c = virtcol('.')
    if c == 100
        execute "normal ," . a:quote
    endif
endwhile
endfunction

function! JoinMultilineString()
    let c = "'"
    while c == "'" || c == '"'
        normal gj^
        let c = matchstr(getline('.'), '\%' . col('.') . 'c.')
        normal k
        if c == "'" || c == '"'
            normal ,j
        endif
    endwhile
endfunction

" break lines
nnoremap <Leader>" 100\|Bi"<CR>"<Esc>
nnoremap <Leader><Leader>" :call BreakMultlineString('"')<CR>
nnoremap <Leader>' 100\|Bi'<CR>'<Esc>
nnoremap <Leader><Leader>' :call BreakMultlineString("'")<CR>

" join lines
nmap <Leader>j Jds'ds"x
nnoremap <Leader>J :call JoinMultilineString()<CR>
Bryan Bugyi
fonte