From 10228215c76da4004b5ac4b60cfdf94682ea6e22 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 6 Jul 2016 11:17:48 +0200 Subject: [PATCH] Improve handling of multiline strings (#50) * Improve handling of multiline strings Fixes https://github.com/hynek/vim-python-pep8-indent/issues/49. * tests: use an uneven width (3) instead of 8 This makes sure that the tests work as expected. * tests: move helper functions to spec_helper.rb * Add python_pep8_indent_for_multiline_string setting Ref: https://github.com/hynek/vim-python-pep8-indent/issues/49#issuecomment-228581896 * Use indent directly if prevnonblank is known This removes the now unused s:indent_prevnonblank function. --- README.rst | 22 +++++ indent/python.vim | 30 ++++-- spec/indent/indent_spec.rb | 193 ++++++++++++++++++++----------------- spec/spec_helper.rb | 25 +++++ 4 files changed, 172 insertions(+), 98 deletions(-) diff --git a/README.rst b/README.rst index 92479a4..1e3de55 100644 --- a/README.rst +++ b/README.rst @@ -53,6 +53,28 @@ Follow the instructions on installing NeoBundle_ and add the appropriate NeoBund NeoBundle 'hynek/vim-python-pep8-indent' +Configuration +------------- + +python_pep8_indent_multiline_string +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can configure the initial indentation of multiline strings using ``g:python_pep8_indent_multiline_string`` (which can also be set per buffer). +This defaults to ``0``, which means that multiline strings are not indented. +``-1`` and positive values will be used as-is, where ``-1`` is a special value for Vim's ``indentexpr``, and will keep the existing indent (using Vim's ``autoindent`` setting). +``-2`` is meant to be used for strings that are wrapped with ``textwrap.dedent`` etc. It will add a level of indentation if the multiline string started in the previous line, without any content in it already:: + + testdir.makeconftest(""" + _ + +With content already, it will be aligned to the opening parenthesis:: + + testdir.makeconftest("""def pytest_addoption(parser): + _ + +Existing indentation (including ``0``) in multiline strings will be kept, so this setting only applies to the indentation of new/empty lines. + + Notes ----- diff --git a/indent/python.vim b/indent/python.vim index 7bbc415..6bf45a8 100644 --- a/indent/python.vim +++ b/indent/python.vim @@ -32,6 +32,10 @@ setlocal tabstop=4 setlocal softtabstop=4 setlocal shiftwidth=4 +if !exists('g:python_pep8_indent_multiline_string') + let g:python_pep8_indent_multiline_string = 0 +endif + let s:maxoff = 50 let s:block_rules = { \ '^\s*elif\>': ['if', 'elif'], @@ -333,28 +337,40 @@ function! s:is_python_string(lnum, ...) endfunction function! GetPythonPEPIndent(lnum) - " First line has indent 0 if a:lnum == 1 return 0 endif " Multilinestrings: continous, docstring or starting. - if s:is_python_string(a:lnum) + if s:is_python_string(a:lnum, 1) + \ && s:is_python_string(a:lnum-1, len(getline(a:lnum-1))) + " Keep existing indent. + if match(getline(a:lnum), '\v^\s*\S') != -1 + return -1 + endif + if s:is_python_string(a:lnum-1) " Previous line is (completely) a string. - return s:indent_like_previous_line(a:lnum) + return indent(a:lnum-1) endif if match(getline(a:lnum-1), '^\s*\%("""\|''''''\)') != -1 " docstring. - return s:indent_like_previous_line(a:lnum) + return indent(a:lnum-1) endif - if s:is_python_string(a:lnum-1, len(getline(a:lnum-1))) - " String started in previous line. - return 0 + let indent_multi = get(b:, 'python_pep8_indent_multiline_string', + \ get(g:, 'python_pep8_indent_multiline_string', 0)) + if indent_multi != -2 + return indent_multi + endif + + if match(getline(a:lnum-1), '\v%("""|'''''')$') != -1 + " Opening multiline string, started in previous line. + return indent(a:lnum-1) + s:sw() endif + return s:indent_like_opening_paren(a:lnum) endif " Parens: If we can find an open parenthesis/bracket/brace, line up with it. diff --git a/spec/indent/indent_spec.rb b/spec/indent/indent_spec.rb index 5c9838a..ecdbcad 100644 --- a/spec/indent/indent_spec.rb +++ b/spec/indent/indent_spec.rb @@ -29,8 +29,8 @@ shared_examples_for "vim" do before { vim.feedkeys '0ggipass' } it "does not indent" do - proposed_indent.should == 0 indent.should == 0 + proposed_indent.should == 0 end it "does not indent when using '=='" do @@ -161,68 +161,6 @@ shared_examples_for "vim" do end end - describe "when after an '(' that is followed by an unfinished string" do - before { vim.feedkeys 'itest("""' } - - it "it does not indent the next line" do - vim.feedkeys '\' - proposed_indent.should == 0 - indent.should == 0 - end - - it "with contents it does not indent the next line" do - vim.feedkeys 'string_contents\' - proposed_indent.should == 0 - indent.should == 0 - end - end - - describe "when after assigning an unfinished string" do - before { vim.feedkeys 'itest = """' } - - it "it does not indent the next line" do - vim.feedkeys '\' - proposed_indent.should == 0 - indent.should == 0 - end - end - - describe "when after assigning an unfinished string" do - before { vim.feedkeys 'i test = """' } - - it "it does not indent the next line" do - vim.feedkeys '\' - proposed_indent.should == 0 - indent.should == 0 - end - end - - describe "when after assigning a finished string" do - before { vim.feedkeys 'i test = ""' } - - it "it does indent the next line" do - vim.feedkeys '\' - proposed_indent.should == 4 - indent.should == 4 - end - - it "and writing a new string, it does indent the next line" do - vim.feedkeys '\""' - proposed_indent.should == 4 - indent.should == 4 - end - end - - describe "when after a docstring" do - before { vim.feedkeys 'i """' } - - it "it does indent the next line" do - vim.feedkeys '\' - proposed_indent.should == 4 - indent.should == 4 - end - end - describe "when using simple control structures" do it "indents shiftwidth spaces" do vim.feedkeys 'iwhile True:\pass' @@ -436,26 +374,79 @@ shared_examples_for "vim" do indent.should == shiftwidth * 2 end end +end + +shared_examples_for "multiline strings" do + describe "when after an '(' that is followed by an unfinished string" do + before { vim.feedkeys 'itest("""' } - def shiftwidth - @shiftwidth ||= vim.echo("exists('*shiftwidth') ? shiftwidth() : &sw").to_i + it "it indents the next line" do + vim.feedkeys '\' + expected_proposed, expected_indent = multiline_indent(0, shiftwidth) + proposed_indent.should == expected_proposed + indent.should == expected_indent + end + + it "with contents it indents the second line to the parenthesis" do + vim.feedkeys 'second line\' + expected_proposed, expected_indent = multiline_indent(0, 5) + proposed_indent.should == expected_proposed + indent.should == expected_indent + end + end + + describe "when after assigning an unfinished string" do + before { vim.feedkeys 'itest = """' } + + it "it indents the next line" do + vim.feedkeys '\' + expected_proposed, expected_indent = multiline_indent(0, shiftwidth) + proposed_indent.should == expected_proposed + indent.should == expected_indent + end end - def tabstop - @tabstop ||= vim.echo("&tabstop").to_i + + describe "when after assigning an unfinished string" do + before { vim.feedkeys 'i test = """' } + + it "it indents the next line" do + vim.feedkeys '\' + expected_proposed, expected_indent = multiline_indent(4, shiftwidth + 4) + proposed_indent.should == expected_proposed + indent.should == expected_indent + end end - def indent - vim.echo("indent('.')").to_i + + describe "when after assigning a finished string" do + before { vim.feedkeys 'i test = ""' } + + it "it does indent the next line" do + vim.feedkeys '\' + indent.should == 4 + end + + it "and writing a new string, it does indent the next line" do + vim.feedkeys '\""' + indent.should == 4 + end end - def previous_indent - pline = vim.echo("line('.')").to_i - 1 - vim.echo("indent('#{pline}')").to_i + + describe "when after a docstring" do + before { vim.feedkeys 'i """' } + it "it does indent the next line to the docstring" do + vim.feedkeys '\' + indent.should == 4 + proposed_indent.should == 4 + end end - def proposed_indent - line = vim.echo("line('.')") - col = vim.echo("col('.')") - indent_value = vim.echo("GetPythonPEPIndent(line('.'))").to_i - vim.command("call cursor(#{line}, #{col})") - return indent_value + + describe "when after a docstring with contents" do + before { vim.feedkeys 'i """First line' } + it "it does indent the next line to the docstring" do + vim.feedkeys '\' + indent.should == 4 + proposed_indent.should == 4 + end end end @@ -463,16 +454,46 @@ describe "vim when using width of 4" do before { vim.command("set sw=4 ts=4 sts=4 et") } + it_behaves_like "vim" +end +describe "vim when using width of 3" do + before { + vim.command("set sw=3 ts=3 sts=3 et") + } it_behaves_like "vim" end -describe "vim when using width of 8" do +describe "vim when not using python_pep8_indent_multiline_string" do before { - vim.command("set sw=8 ts=8 sts=8 et") + vim.command("set sw=4 ts=4 sts=4 et") + vim.command("unlet! g:python_pep8_indent_multiline_string") } + it_behaves_like "multiline strings" +end - it_behaves_like "vim" +describe "vim when using python_pep8_indent_multiline_first=0" do + before { + vim.command("set sw=4 ts=4 sts=4 et") + vim.command("let g:python_pep8_indent_multiline_string=0") + } + it_behaves_like "multiline strings" +end + +describe "vim when using python_pep8_indent_multiline_string=-1" do + before { + vim.command("set sw=4 ts=4 sts=4 et") + vim.command("let g:python_pep8_indent_multiline_string=-1") + } + it_behaves_like "multiline strings" +end + +describe "vim when using python_pep8_indent_multiline_string=-2" do + before { + vim.command("set sw=4 ts=4 sts=4 et") + vim.command("let g:python_pep8_indent_multiline_string=-2") + } + it_behaves_like "multiline strings" end describe "vim for cython" do @@ -482,16 +503,6 @@ describe "vim for cython" do vim.command "runtime indent/python.vim" } - def shiftwidth - @shiftwidth ||= vim.echo("exists('*shiftwidth') ? shiftwidth() : &sw").to_i - end - def tabstop - @tabstop ||= vim.echo("&tabstop").to_i - end - def indent - vim.echo("indent('.')").to_i - end - describe "when using a cdef function definition" do it "indents shiftwidth spaces" do vim.feedkeys 'icdef long_function_name(\arg' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 82b152c..148f705 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,6 +22,31 @@ Vimrunner::RSpec.configure do |config| vim.command "runtime syntax/python.vim" vim.command "runtime indent/python.vim" + def shiftwidth + @shiftwidth ||= vim.echo("exists('*shiftwidth') ? shiftwidth() : &sw").to_i + end + def tabstop + @tabstop ||= vim.echo("&tabstop").to_i + end + def indent + vim.echo("indent('.')").to_i + end + def previous_indent + pline = vim.echo("line('.')").to_i - 1 + vim.echo("indent('#{pline}')").to_i + end + def proposed_indent + line = vim.echo("line('.')") + col = vim.echo("col('.')") + indent_value = vim.echo("GetPythonPEPIndent(line('.'))").to_i + vim.command("call cursor(#{line}, #{col})") + return indent_value + end + def multiline_indent(prev, default) + i = vim.echo("get(g:, 'python_pep8_indent_multiline_string', 0)").to_i + return (i == -2 ? default : i), i < 0 ? (i == -1 ? prev : default) : i + end + vim end end -- 2.39.5