【问题标题】:Python multi-line strings breaks Vim's indent foldingPython 多行字符串打破了 Vim 的缩进折叠
【发布时间】:2011-10-08 15:16:10
【问题描述】:

Python 的字符串文字并置使多行字符串更容易编写,也更漂亮,但是当我缩进四五个深度并且想要使用整行(前导空格无关紧要)时,Vim 的 foldmethod=indent 会中断下来。

例如:

def getQuotation():
    print "Fetching quotation from the absolutely useless function."
    return ("Four score and seven years ago our fathers brought forth, "
"upon this continent, a new nation, conceived in liberty, and dedicated "
"to the proposition that \"all men are created equal\"")

应该这样折叠:

def getQuotation():
+--  4 lines: print "Fetching quotation from the absolutely useless function."--

但是我得到了这个:

def getQuotation():
+--  2 lines: print "Fetching quotation from the absolutely useless function."--
"upon this continent, a new nation, conceived in liberty, and dedicated "
"to the proposition that \"all men are created equal\"")

我尝试设置foldignore=\",但无济于事。 Vim 的 help foldignore 提供了这个主题:

仅在 'foldmethod' 为“缩进”时使用。以开头的行 'foldignore' 中的字符将从周围获得折叠级别 线。在检查此字符之前会跳过空格。

我是否有明显遗漏的东西,或者我必须求助于foldmethod=expr,将折叠级别基于缩进,除了角落案例我自己?

编辑:我至少取得了一些进展;事实证明,如果我在字符串 之后添加一个非空行,并用set fdm=indent“刷新”缩进,那么块会按预期折叠。即使是空的评论 (#) 也足够了。

【问题讨论】:

  • 您是否尝试过foldmethod=syntax,并在做set filetype=pythonfoldmethod=indent 顾名思义,基于缩进折叠,因此在涉及特定语言的事情时并不是特别聪明。
  • 这不是多行字符串,而是与多个子字符串的隐式字符串连接。为什么不尝试使用实际的多行字符串,利用每一节末尾的行继续符\ 在代码中占一行?不知道这是否会帮助你,但值得一试。 ...到底是什么,反斜杠+反引号是 cmets 中的转义码?
  • @actionshrimp:我以前使用过语法折叠,但它的效果不如缩进和定义的折叠(通常)在比我想要的更广泛的范围内。
  • @JAB:你说得对,从技术上讲,它不是多行字符串。但是您提出的解决方案没有解决问题,即vim的折叠仍然需要我将每一行填充到相同的缩进级别,这是不可接受的。
  • 因为它不是使用 Vim 的缩进折叠的解决方案,所以我将它作为评论发布...我正在使用 python_ifold 插件 (vim.org/scripts/script.php?script_id=2002),并且所描述的行为不会发生。也许你应该尝试一下。如果您认为它是一种解决方案,我可以重新发布它并提供更多信息和详细信息。

标签: python vim ide multiline folding


【解决方案1】:

是否有明显的遗漏,或者我必须求助于 foldmethod=expr,将 foldlevel 基于缩进,除了极端情况之外?

简答:你不能用 foldmethod=indent 做到这一点,但我发现了一些你可以用 foldmethod=expr 的东西,所以不需要重新发明轮子。请参阅长答案。

长答案

简单回顾一下foldmethod=indent 的工作原理...

  • 查找从页面边缘移动 shiftwidth 并带有空格的文本
  • 插入一个新的foldlevel
  • 去缩进会降低折叠级别

由于您拥有的文本在屏幕边缘是合理的,因此任何涉及shiftwidth 的内容最终都会被破坏,除非您像以前那样破解它。

在找到可行的方法之前,我查看了几种不同的 .vimrc 配置。要获得比foldmethod=indent 更清洁的解决方案,请使用foldmethod=expr 和下面的~/.vimrc。我在jneb's bitbucket python-fold repo找到它

作为测试,我在您的示例中构建了更多案例...

class testclass(object):
    def __init__(self):
        self.testit = None
    def __repr__(self):
        return "guacamole"

def foobarme():
    assert False
    return 42

def getQuotation():
    print "Fetching quotation from the absolutely useless function."
    return ("Four score and seven years ago our fathers brought forth, "
"upon this continent, a new nation, conceived in liberty, and dedicated "
"to the proposition that \"all men are created equal\"")

在我的~/.vimrc 底部使用python-fold 会产生:

当我点击zR 展开时:

仅供参考,我在 ~/.vim/syntax/python.vim 中使用 Dmitry Vasiliev 的 python.vim 进行 Python 语法高亮显示。

我在下面复制了 jneb 的 vim 脚本,以防 bitbucket repo 消失...


" Fold routines for python code, version 3.2
" Source: http://www.vim.org/scripts/script.php?script_id=2527
" Last Change: 2009 Feb 25
" Author: Jurjen Bos
" Bug fixes and helpful comments: Grissiom, David Froger, Andrew McNabb

" Principles:
" - a def/class starts a fold
" a line with indent less than the previous def/class ends a fold
" empty lines and comment lines are linked to the previous fold
" comment lines outside a def/class are never folded
" other lines outside a def/class are folded together as a group
" for algorithm, see bottom of script

" - optionally, you can get empty lines between folds, see (***)
" - another option is to ignore non-python files see (**)
" - you can also modify the def/class check,
"    allowing for multiline def and class definitions see (*)

" Note for vim 7 users:
" Vim 6 line numbers always take 8 columns, while vim 7 has a numberwidth variable
" you can change the 8 below to &numberwidth if you have vim 7,
" this is only really useful when you plan to use more than 8 columns (i.e. never)

" Note for masochists trying to read this:
" I wanted to keep the functions short, so I replaced occurences of
" if condition
"     statement
" by
" if condition | statement
" wherever I found that useful

" (*)
" class definitions are supposed to ontain a colon on the same line.
" function definitions are *not* required to have a colon, to allow for multiline defs.
" I you disagree, use instead of the pattern '^\s*\(class\s.*:\|def\s\)'
" to enforce : for defs:                     '^\s*\(class\|def\)\s.*:'
" you'll have to do this in two places.
let s:defpat = '^\s*\(@\|class\s.*:\|def\s\)'

" (**) Ignore non-python files
" Commented out because some python files are not recognized by Vim
"if &filetype != 'python'
"    finish
"endif

setlocal foldmethod=expr
setlocal foldexpr=GetPythonFold(v:lnum)
setlocal foldtext=PythonFoldText()

function! PythonFoldText()
  let fs = v:foldstart
  while getline(fs) =~ '^\s*@' | let fs = nextnonblank(fs + 1)
  endwhile
  let line = getline(fs)
  let nnum = nextnonblank(fs + 1)
  let nextline = getline(nnum)
  "get the document string: next line is ''' or """
  if nextline =~ "^\\s\\+[\"']\\{3}\\s*$"
      let line = line . " " . matchstr(getline(nextnonblank(nnum + 1)), '^\s*\zs.*\ze$')
  "next line starts with qoutes, and has text
  elseif nextline =~ "^\\s\\+[\"']\\{1,3}"
      let line = line." ".matchstr(nextline, "^\\s\\+[\"']\\{1,3}\\zs.\\{-}\\ze['\"]\\{0,3}$")
  elseif nextline =~ '^\s\+pass\s*$'
    let line = line . ' pass'
  endif
  "compute the width of the visible part of the window (see Note above)
  let w = winwidth(0) - &foldcolumn - (&number ? 8 : 0)
  let size = 1 + v:foldend - v:foldstart
  "compute expansion string
  let spcs = '................'
  while strlen(spcs) < w | let spcs = spcs . spcs
  endwhile
  "expand tabs (mail me if you have tabstop>10)
  let onetab = strpart('          ', 0, &tabstop)
  let line = substitute(line, '\t', onetab, 'g')
  return strpart(line.spcs, 0, w-strlen(size)-7).'.'.size.' lines'
endfunction

function! GetBlockIndent(lnum)
    " Auxiliary function; determines the indent level of the surrounding def/class
    " "global" lines are level 0, first def &shiftwidth, and so on
    " scan backwards for class/def that is shallower or equal
    let ind = 100
    let p = a:lnum+1
    while indent(p) >= 0
        let p = p - 1
        " skip empty and comment lines
        if getline(p) =~ '^$\|^\s*#' | continue
        " zero-level regular line
        elseif indent(p) == 0 | return 0
        " skip deeper or equal lines
        elseif indent(p) >= ind || getline(p) =~ '^$\|^\s*#' | continue
        " indent is strictly less at this point: check for def/class
        elseif getline(p) =~ s:defpat && getline(p) !~ '^\s*@'
            " level is one more than this def/class
            return indent(p) + &shiftwidth
        endif
        " shallower line that is neither class nor def: continue search at new level
        let ind = indent(p)
    endwhile
    "beginning of file
    return 0
endfunction

" Clever debug code, use to display text for a given moment the statement is executed:
" call PrintIfCount(6, "Line: ".a:lnum.", indent: ".ind.", previous indent: ".pind)
let s:counter=0
function! PrintIfCount(n,t)
    "Print text the nth time this function is called
    let s:counter = s:counter+1
    if s:counter==a:n | echo a:t
    endif
endfunction

function! GetPythonFold(lnum)
    " Determine folding level in Python source (see "higher foldlevel theory" below)
    let line = getline(a:lnum)
    let ind = indent(a:lnum)
    " Case D***: class and def start a fold
    " If previous line is @, it is not the first
    if line =~ s:defpat && getline(prevnonblank(a:lnum-1)) !~ '^\s*@'
        " let's see if this range of 0 or more @'s end in a class/def
        let n = a:lnum
        while getline(n) =~ '^\s*@' | let n = nextnonblank(n + 1)
        endwhile
        " yes, we have a match: this is the first of a real def/class with decorators
        if getline(n) =~ s:defpat
            return ">".(ind/&shiftwidth+1)
        endif
    " Case E***: empty lines fold with previous
    " (***) change '=' to -1 if you want empty lines/comment out of a fold
    elseif line == '' | return '='
    endif
    " now we need the indent from previous
    let p = prevnonblank(a:lnum-1)
    while p>0 && getline(p) =~ '^\s*#' | let p = prevnonblank(p-1)
    endwhile
    let pind = indent(p)
    " If previous was definition: count as one level deeper
    if getline(p) =~ s:defpat && getline(prevnonblank(a:lnum - 1)) !~ '^\s*@'
        let pind = pind + &shiftwidth
    " if begin of file: take zero
    elseif p==0 | let pind = 0
    endif
    " Case S*=* and C*=*: indent equal
    if ind>0 && ind==pind | return '='
    " Case S*>* and C*>*: indent increase
    elseif ind>pind | return '='
    " All cases with 0 indent
    elseif ind==0
        " Case C*=0*: separate global code blocks
        if pind==0 && line =~ '^#' | return 0
        " Case S*<0* and S*=0*: global code
        elseif line !~'^#'
            " TODO: here we need to check GetBlockIndent(a:lnum) for version 3.3
            " Case S*<0*: new global statement if/while/for/try/with
            if 0<pind && line!~'^else\s*:\|^except.*:\|^elif.*:\|^finally\s*:' | return '>1'
            " Case S*=0*, after level 0 comment
            elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' | return '>1'
            " Case S*=0*, other, stay 1
            else | return '='
            endif
        endif
        " Case C*<0= and C*<0<: compute next indent
        let n = nextnonblank(a:lnum+1)
        while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1)
        endwhile
        " Case C*<0=: split definitions
        if indent(n)==0 | return 0
        " Case C*<0<: shallow comment
        else | return -1
        end
    endif
    " now we really need to compute the actual fold indent
    " do the hard computation
    let blockindent = GetBlockIndent(a:lnum)
    " Case SG<* and CG<*: global code, level 1
    if blockindent==0 | return 1
    endif
    " now we need the indent from next
    let n = nextnonblank(a:lnum+1)
    while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1)
    endwhile
    let nind = indent(n)
    " Case CR<= and CR<>
    endif
    if line =~ '^\s*#' && ind>=nind | return -1
    " Case CR<<: return next indent
    elseif line =~ '^\s*#' | return nind / &shiftwidth
    " Case SR<*: return actual indent
    else | return blockindent / &shiftwidth
    endif
endfunction

" higher foldlevel theory
" There are five kinds of statements: S (code), D (def/class), E (empty), C (comment)

" Note that a decorator statement (beginning with @) counts as definition,
" but that of a sequence of @,@,@,def only the first one counts
" This means that a definiion only counts if not preceded by a decorator

" There are two kinds of folds: R (regular), G (global statements)

" There are five indent situations with respect to the previous non-emtpy non-comment line:
" > (indent), < (dedent), = (same); < and = combine with 0 (indent is zero)
" Note: if the previous line is class/def, its indent is interpreted as one higher

" There are three indent situations with respect to the next (non-E non-C) line:
" > (dedent), < (indent), = (same)

" Situations (in order of the script):
" stat  fold prev   next
" SDEC  RG   ><=00  ><=
" D     *    *      *     begin fold level if previous is not @: '>'.ind/&sw+1
" E     *    *      *     keep with previous: '='
" S     *    =      *     stays the same: '='
" C     *    =      *     combine with previous: '='
" S     *    >      *     stays the same: '='
" C     *    >      *     combine with previous: '='
" C     *    =0     *     separate blocks: 0
" S     *    <0     *     becomes new level 1: >1 (except except/else: 1)
" S     *    =0     *     stays 1: '=' (after level 0 comment: '>1')
" C     *    <0     =     split definitions: 0
" C     *    <0     <     shallow comment: -1
" C     *    <0     >     [never occurs]
" S     G    <      *     global, not the first: 1
" C     G    <      *     indent isn't 0: 1
" C     R    <      =     foldlevel as computed for next line: -1
" C     R    <      >     foldlevel as computed for next line: -1
" S     R    <      *     compute foldlevel the hard way: use function
" C     R    <      <     foldlevel as computed for this line: use function

【讨论】:

  • 这是一个很棒的发现,非常感谢!我仍然有点惊讶你不仅找到了一个成功的 vimscript,而且还实现了一个测试用例并发布了答案的截图。谢谢,谢谢,谢谢。
【解决方案2】:

您可以将python-modeFastFold 一起使用。 Python-mode解决了很多折叠问题,FastFold提高了折叠速度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-13
    • 2012-06-13
    • 2013-03-09
    • 2021-01-01
    • 2020-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多