使用Python文件类型时,如何在vim中重新格式化多行字符串?


23

假设我正在vim中编辑一些Python代码,如下所示:

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

我希望重新格式化,以便重新设置到textwidth我设置的位置:

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

是否有捷径可寻?如果这是常规的文本段落,gqip或类似的段落将很有用,但这将无法处理用于修饰字符串的引号。

注意:我在这里专门询问有关Python的问题,但理想情况下,此答案与允许字符串连续的许多类型的编程语言有关。



3
您是否也考虑过将代码重写为"""multi-line string"""
200_success

1
@ 200_success,非常感谢,谢谢。我完全不了解Python的功能。似乎大部分都可以正常工作-尽管该函数的所有参数均已重排,但这并不理想。我认为我的问题对于我拥有的字符串类型仍然有效,当然对于其他语言也仍然有效。
安德鲁·费里尔

2
请注意,多行字符串将添加空格字符,如果字符串中的空格很重要,则可能会出现问题。
Matt Boehm

Answers:


6

哦,那是一个艰难的过程。我认为最好的方法可能是宏,但更可能是插件。这是我举个例子(我需要一个更好的爱好)。它似乎为我工作,但需要python缩进插件正确缩进。试试看。

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

然后,您可以将其与:call ReformatMultiLines()和/或在映射中使用。


6

如果这是经常发生的情况,那么最好是寻找插件或使用@Sukima的函数。但是,如果我即时进行此操作,则可能会执行以下操作:

  1. 在开始括号之前和结束括号之前添加换行符,以使字符串位于单独的行上。

    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. 选择带字符串的行并删除引号: :norm ^x$x

  3. 减小文本宽度(以解决缺少引号的情况) :set tw-=2
  4. 重新选择并格式化: gvgq
  5. 修正文字宽度: :set tw+=2
  6. 重新添加引号:gv:norm I"<Esc>A"。而不是<Esc>您想通过键入ctrl-v和后跟转义键来插入文字转义符。由于我将jj映射为转义,因此我通常只需在此处输入jj即可。
  7. (可选)使用J重新连接前两行/后两行。当我在python这样的字符串中有一个很长的字符串时,我通常更喜欢在下一行启动它,并且只将其缩进一个级别,而不是上一行。这为弦乐提供了更多的水平空间,让我感觉更自然。或者,您可以将字符串保存到上方的某个变量中。假设它是静态字符串,您甚至可以将其保存到全局变量中,以使其缩进级别低得多,并且可以容纳更少的行。请注意,要在同一行上重新加入结束括号,您可能希望将文本宽度减少/增加3,而不是2。

1
谢谢。我只是试过了,但:set tw-=2结果是E487: Argument must be positive: tw-=2。语法不正确吗?
安德鲁·费里尔

1
用检查当前的textwidth值:set tw?。我的猜测是您将textwidth设置为0。这些步骤假定textwidth开始时是每行所需的字符数。
马特·博姆

在您的示例中,您可能想要s/2/3/在右引号之前添加一个空格,因此长字符串在单词之间没有适当的间距。
cdosborn

1

解析和格式化代码非常困难,尤其是对于动态语言(例如Python)而言。

而不是试图Vimscript中解析Python的,我建议你可以使用一个外部格式如yapf。在这里给出我的答案,您可以使用以下命令自动编写:

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

s:write_cmd()函数在另一个答案中–我不会在这里添加它,因此,如果我发现错误,则不必在两个地方进行更新:-)

您也可以将设置formatprgyapf并手动设置其格式gq


你好 有用的答案,但是我很不赞成,因为我认为它不能回答这个问题:这个问题非常具体地要求重新格式化多行字符串,这yapf似乎没有用。不过,我同意它看起来像是一个有用的工具。
安德鲁·费里尔

@AndrewFerrier足够公平;我发誓我前几天进行测试时看到它重新格式化了字符串,但现在也不能使它生效:-/猜猜我很困惑。
Martin Tournoij

0

以下功能和相应的映射为我解决了此问题:

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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.