¿Cómo puedo reformatear una cadena de varias líneas en vim cuando uso el tipo de archivo Python?

23

Supongamos que estoy editando un código de Python en vim que se parece un poco a:

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")

Preferiría volver a formatear esto para que se redirija al textwidthque he configurado:

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")

¿Hay una forma fácil de hacer esto? Si este fuera un párrafo de texto convencional, gqipo similar sería útil, pero esto no manejará las comillas utilizadas para delinear la cadena.

Nota : Estoy preguntando específicamente sobre Python aquí, pero idealmente, esta respuesta sería relevante para muchos tipos de lenguaje de programación que permiten la continuación de cadenas.

Andrew Ferrier
fuente
1
Pregunta similar aquí: vim.1045645.n5.nabble.com/…
Andrew Ferrier
3
¿También ha considerado reescribir el código como a """multi-line string"""?
200_success
1
@ 200_success, ese es un gran consejo, gracias. No conocía esa característica de Python en absoluto. Parece funcionar, sobre todo, aunque todos los parámetros de la función se vuelven a fluir, lo cual no es ideal. Creo que mi pregunta aún es válida para el tipo de cadena que tengo, y ciertamente para otros langs.
Andrew Ferrier
2
Tenga en cuenta que una cadena de varias líneas agregará caracteres de espacio en blanco, lo que puede ser un problema si el espacio en blanco es importante en la cadena.
Matt Boehm
Pregunta muy relacionada: vi.stackexchange.com/questions/2135/… ...
Martin Tournoij

Respuestas:

6

Oh, esa es una pregunta difícil. Creo que el mejor enfoque es posiblemente una macro, pero más probablemente un complemento. Aquí hay un ejemplo que preparé (necesito un pasatiempo mejor). Parecía funcionar para mí, pero necesitaría el complemento de sangría de Python para sangrar correctamente. Pruébalo.

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

Entonces podría usarlo :call ReformatMultiLines()y / o usarlo en un mapeo.

Sukima
fuente
6

Si esto ocurre con frecuencia, sería mejor buscar un complemento o usar la función de @Sukima. Sin embargo, si estuviera haciendo esto sobre la marcha, esto es lo que probablemente haría:

  1. Agregue una nueva línea después del par de apertura y antes del par de cierre para que las cadenas estén en líneas 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. Seleccione líneas con cadenas y elimine las comillas circundantes: :norm ^x$x

  3. Reduzca el ancho de texto (para tener en cuenta las citas faltantes) :set tw-=2
  4. Vuelva a seleccionar y formatear: gvgq
  5. Fix textwidth: :set tw+=2
  6. Cotizaciones de volver a agregar: gv:norm I"<Esc>A". En lugar de <Esc>querer insertar un escape literal, escriba ctrl-v seguido de la tecla de escape. Como mapeo jj para escapar, generalmente solo escribo jj aquí.
  7. Opcionalmente, use J para volver a unir las primeras dos / últimas dos líneas. Cuando tengo una cadena muy larga en Python como esta, generalmente prefiero comenzar en la siguiente línea y solo sangrar un nivel más que la línea anterior. Esto le da más espacio horizontal a la cuerda y me parece más natural. Alternativamente, puede guardar la cadena en una variable en algún lugar arriba. Suponiendo que es una cadena estática, incluso podría guardarla en una variable global para que esté en un nivel de sangría mucho más bajo y pueda caber en menos líneas. Tenga en cuenta que debe volver a unirse al par de cierre en la misma línea, probablemente desee disminuir / incrementar el ancho de texto en 3 en lugar de 2.
Matt Boehm
fuente
1
Gracias. Acabo de intentar esto pero :set tw-=2resulta en E487: Argument must be positive: tw-=2. ¿Es incorrecta la sintaxis?
Andrew Ferrier
1
Verifique el valor actual del ancho de texto con :set tw?. Supongo que tiene el ancho de texto establecido en 0. Estos pasos suponen que el ancho de texto comienza como la cantidad de caracteres que desea por línea.
Matt Boehm
En su ejemplo, probablemente desee s/2/3/agregar un espacio antes de la cita de cierre, su cadena larga no tendría un espacio adecuado entre las palabras.
cdosborn
1

Analizar y formatear código es difícil, especialmente para lenguajes dinámicos como Python.

En lugar de intentar analizar Python en Vimscript, le recomiendo que pueda usar un formateador externo como yapf . Con mi respuesta aquí , puede escribir esto automáticamente al escribir con:

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

La s:write_cmd()función está en la otra respuesta: no la agregaré aquí, así que no tendré que actualizarla en dos lugares si encuentro un error :-)

También puede configurar formatprgpara yapfy dar formato manualmente con gq.

Martin Tournoij
fuente
Hola. Respuesta útil, pero estoy votando negativamente ya que no creo que responda a esta pregunta: esta pregunta es muy específica sobre el formateo de cadenas multilínea, lo que yapfparece no funcionar. Sin embargo, estoy de acuerdo en que parece una herramienta útil.
Andrew Ferrier
@AndrewFerrier Bastante justo; Sin embargo, podría haber jurado que lo vi formatear cadenas cuando lo probé el otro día, pero tampoco puedo hacer que funcione ahora: - / Supongo que estaba confundido.
Martin Tournoij
0

Las siguientes funciones y las asignaciones correspondientes me resolvieron este problema:

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
fuente