Skip to content

Commit 1022821

Browse files
blueyedhynek
authored andcommitted
Improve handling of multiline strings (#50)
* Improve handling of multiline strings Fixes #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: #49 (comment) * Use indent directly if prevnonblank is known This removes the now unused s:indent_prevnonblank function.
1 parent 2c38ff0 commit 1022821

File tree

4 files changed

+172
-98
lines changed

4 files changed

+172
-98
lines changed

README.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ Follow the instructions on installing NeoBundle_ and add the appropriate NeoBund
5353
NeoBundle 'hynek/vim-python-pep8-indent'
5454
5555
56+
Configuration
57+
-------------
58+
59+
python_pep8_indent_multiline_string
60+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61+
62+
You can configure the initial indentation of multiline strings using ``g:python_pep8_indent_multiline_string`` (which can also be set per buffer).
63+
This defaults to ``0``, which means that multiline strings are not indented.
64+
``-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).
65+
``-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::
66+
67+
testdir.makeconftest("""
68+
_
69+
70+
With content already, it will be aligned to the opening parenthesis::
71+
72+
testdir.makeconftest("""def pytest_addoption(parser):
73+
_
74+
75+
Existing indentation (including ``0``) in multiline strings will be kept, so this setting only applies to the indentation of new/empty lines.
76+
77+
5678
Notes
5779
-----
5880

indent/python.vim

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ setlocal tabstop=4
3232
setlocal softtabstop=4
3333
setlocal shiftwidth=4
3434

35+
if !exists('g:python_pep8_indent_multiline_string')
36+
let g:python_pep8_indent_multiline_string = 0
37+
endif
38+
3539
let s:maxoff = 50
3640
let s:block_rules = {
3741
\ '^\s*elif\>': ['if', 'elif'],
@@ -333,28 +337,40 @@ function! s:is_python_string(lnum, ...)
333337
endfunction
334338

335339
function! GetPythonPEPIndent(lnum)
336-
337340
" First line has indent 0
338341
if a:lnum == 1
339342
return 0
340343
endif
341344

342345
" Multilinestrings: continous, docstring or starting.
343-
if s:is_python_string(a:lnum)
346+
if s:is_python_string(a:lnum, 1)
347+
\ && s:is_python_string(a:lnum-1, len(getline(a:lnum-1)))
348+
" Keep existing indent.
349+
if match(getline(a:lnum), '\v^\s*\S') != -1
350+
return -1
351+
endif
352+
344353
if s:is_python_string(a:lnum-1)
345354
" Previous line is (completely) a string.
346-
return s:indent_like_previous_line(a:lnum)
355+
return indent(a:lnum-1)
347356
endif
348357

349358
if match(getline(a:lnum-1), '^\s*\%("""\|''''''\)') != -1
350359
" docstring.
351-
return s:indent_like_previous_line(a:lnum)
360+
return indent(a:lnum-1)
352361
endif
353362

354-
if s:is_python_string(a:lnum-1, len(getline(a:lnum-1)))
355-
" String started in previous line.
356-
return 0
363+
let indent_multi = get(b:, 'python_pep8_indent_multiline_string',
364+
\ get(g:, 'python_pep8_indent_multiline_string', 0))
365+
if indent_multi != -2
366+
return indent_multi
367+
endif
368+
369+
if match(getline(a:lnum-1), '\v%("""|'''''')$') != -1
370+
" Opening multiline string, started in previous line.
371+
return indent(a:lnum-1) + s:sw()
357372
endif
373+
return s:indent_like_opening_paren(a:lnum)
358374
endif
359375

360376
" Parens: If we can find an open parenthesis/bracket/brace, line up with it.

spec/indent/indent_spec.rb

Lines changed: 102 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
before { vim.feedkeys '0ggipass' }
3030

3131
it "does not indent" do
32-
proposed_indent.should == 0
3332
indent.should == 0
33+
proposed_indent.should == 0
3434
end
3535

3636
it "does not indent when using '=='" do
@@ -161,68 +161,6 @@
161161
end
162162
end
163163

164-
describe "when after an '(' that is followed by an unfinished string" do
165-
before { vim.feedkeys 'itest("""' }
166-
167-
it "it does not indent the next line" do
168-
vim.feedkeys '\<CR>'
169-
proposed_indent.should == 0
170-
indent.should == 0
171-
end
172-
173-
it "with contents it does not indent the next line" do
174-
vim.feedkeys 'string_contents\<CR>'
175-
proposed_indent.should == 0
176-
indent.should == 0
177-
end
178-
end
179-
180-
describe "when after assigning an unfinished string" do
181-
before { vim.feedkeys 'itest = """' }
182-
183-
it "it does not indent the next line" do
184-
vim.feedkeys '\<CR>'
185-
proposed_indent.should == 0
186-
indent.should == 0
187-
end
188-
end
189-
190-
describe "when after assigning an unfinished string" do
191-
before { vim.feedkeys 'i test = """' }
192-
193-
it "it does not indent the next line" do
194-
vim.feedkeys '\<CR>'
195-
proposed_indent.should == 0
196-
indent.should == 0
197-
end
198-
end
199-
200-
describe "when after assigning a finished string" do
201-
before { vim.feedkeys 'i test = ""' }
202-
203-
it "it does indent the next line" do
204-
vim.feedkeys '\<CR>'
205-
proposed_indent.should == 4
206-
indent.should == 4
207-
end
208-
209-
it "and writing a new string, it does indent the next line" do
210-
vim.feedkeys '\<CR>""'
211-
proposed_indent.should == 4
212-
indent.should == 4
213-
end
214-
end
215-
216-
describe "when after a docstring" do
217-
before { vim.feedkeys 'i """' }
218-
219-
it "it does indent the next line" do
220-
vim.feedkeys '\<CR>'
221-
proposed_indent.should == 4
222-
indent.should == 4
223-
end
224-
end
225-
226164
describe "when using simple control structures" do
227165
it "indents shiftwidth spaces" do
228166
vim.feedkeys 'iwhile True:\<CR>pass'
@@ -436,43 +374,126 @@
436374
indent.should == shiftwidth * 2
437375
end
438376
end
377+
end
378+
379+
shared_examples_for "multiline strings" do
380+
describe "when after an '(' that is followed by an unfinished string" do
381+
before { vim.feedkeys 'itest("""' }
439382

440-
def shiftwidth
441-
@shiftwidth ||= vim.echo("exists('*shiftwidth') ? shiftwidth() : &sw").to_i
383+
it "it indents the next line" do
384+
vim.feedkeys '\<CR>'
385+
expected_proposed, expected_indent = multiline_indent(0, shiftwidth)
386+
proposed_indent.should == expected_proposed
387+
indent.should == expected_indent
388+
end
389+
390+
it "with contents it indents the second line to the parenthesis" do
391+
vim.feedkeys 'second line\<CR>'
392+
expected_proposed, expected_indent = multiline_indent(0, 5)
393+
proposed_indent.should == expected_proposed
394+
indent.should == expected_indent
395+
end
396+
end
397+
398+
describe "when after assigning an unfinished string" do
399+
before { vim.feedkeys 'itest = """' }
400+
401+
it "it indents the next line" do
402+
vim.feedkeys '\<CR>'
403+
expected_proposed, expected_indent = multiline_indent(0, shiftwidth)
404+
proposed_indent.should == expected_proposed
405+
indent.should == expected_indent
406+
end
442407
end
443-
def tabstop
444-
@tabstop ||= vim.echo("&tabstop").to_i
408+
409+
describe "when after assigning an unfinished string" do
410+
before { vim.feedkeys 'i test = """' }
411+
412+
it "it indents the next line" do
413+
vim.feedkeys '\<CR>'
414+
expected_proposed, expected_indent = multiline_indent(4, shiftwidth + 4)
415+
proposed_indent.should == expected_proposed
416+
indent.should == expected_indent
417+
end
445418
end
446-
def indent
447-
vim.echo("indent('.')").to_i
419+
420+
describe "when after assigning a finished string" do
421+
before { vim.feedkeys 'i test = ""' }
422+
423+
it "it does indent the next line" do
424+
vim.feedkeys '\<CR>'
425+
indent.should == 4
426+
end
427+
428+
it "and writing a new string, it does indent the next line" do
429+
vim.feedkeys '\<CR>""'
430+
indent.should == 4
431+
end
448432
end
449-
def previous_indent
450-
pline = vim.echo("line('.')").to_i - 1
451-
vim.echo("indent('#{pline}')").to_i
433+
434+
describe "when after a docstring" do
435+
before { vim.feedkeys 'i """' }
436+
it "it does indent the next line to the docstring" do
437+
vim.feedkeys '\<CR>'
438+
indent.should == 4
439+
proposed_indent.should == 4
440+
end
452441
end
453-
def proposed_indent
454-
line = vim.echo("line('.')")
455-
col = vim.echo("col('.')")
456-
indent_value = vim.echo("GetPythonPEPIndent(line('.'))").to_i
457-
vim.command("call cursor(#{line}, #{col})")
458-
return indent_value
442+
443+
describe "when after a docstring with contents" do
444+
before { vim.feedkeys 'i """First line' }
445+
it "it does indent the next line to the docstring" do
446+
vim.feedkeys '\<CR>'
447+
indent.should == 4
448+
proposed_indent.should == 4
449+
end
459450
end
460451
end
461452

462453
describe "vim when using width of 4" do
463454
before {
464455
vim.command("set sw=4 ts=4 sts=4 et")
465456
}
457+
it_behaves_like "vim"
458+
end
466459

460+
describe "vim when using width of 3" do
461+
before {
462+
vim.command("set sw=3 ts=3 sts=3 et")
463+
}
467464
it_behaves_like "vim"
468465
end
469466

470-
describe "vim when using width of 8" do
467+
describe "vim when not using python_pep8_indent_multiline_string" do
471468
before {
472-
vim.command("set sw=8 ts=8 sts=8 et")
469+
vim.command("set sw=4 ts=4 sts=4 et")
470+
vim.command("unlet! g:python_pep8_indent_multiline_string")
473471
}
472+
it_behaves_like "multiline strings"
473+
end
474474

475-
it_behaves_like "vim"
475+
describe "vim when using python_pep8_indent_multiline_first=0" do
476+
before {
477+
vim.command("set sw=4 ts=4 sts=4 et")
478+
vim.command("let g:python_pep8_indent_multiline_string=0")
479+
}
480+
it_behaves_like "multiline strings"
481+
end
482+
483+
describe "vim when using python_pep8_indent_multiline_string=-1" do
484+
before {
485+
vim.command("set sw=4 ts=4 sts=4 et")
486+
vim.command("let g:python_pep8_indent_multiline_string=-1")
487+
}
488+
it_behaves_like "multiline strings"
489+
end
490+
491+
describe "vim when using python_pep8_indent_multiline_string=-2" do
492+
before {
493+
vim.command("set sw=4 ts=4 sts=4 et")
494+
vim.command("let g:python_pep8_indent_multiline_string=-2")
495+
}
496+
it_behaves_like "multiline strings"
476497
end
477498

478499
describe "vim for cython" do
@@ -482,16 +503,6 @@ def proposed_indent
482503
vim.command "runtime indent/python.vim"
483504
}
484505

485-
def shiftwidth
486-
@shiftwidth ||= vim.echo("exists('*shiftwidth') ? shiftwidth() : &sw").to_i
487-
end
488-
def tabstop
489-
@tabstop ||= vim.echo("&tabstop").to_i
490-
end
491-
def indent
492-
vim.echo("indent('.')").to_i
493-
end
494-
495506
describe "when using a cdef function definition" do
496507
it "indents shiftwidth spaces" do
497508
vim.feedkeys 'icdef long_function_name(\<CR>arg'

spec/spec_helper.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,31 @@
2222
vim.command "runtime syntax/python.vim"
2323
vim.command "runtime indent/python.vim"
2424

25+
def shiftwidth
26+
@shiftwidth ||= vim.echo("exists('*shiftwidth') ? shiftwidth() : &sw").to_i
27+
end
28+
def tabstop
29+
@tabstop ||= vim.echo("&tabstop").to_i
30+
end
31+
def indent
32+
vim.echo("indent('.')").to_i
33+
end
34+
def previous_indent
35+
pline = vim.echo("line('.')").to_i - 1
36+
vim.echo("indent('#{pline}')").to_i
37+
end
38+
def proposed_indent
39+
line = vim.echo("line('.')")
40+
col = vim.echo("col('.')")
41+
indent_value = vim.echo("GetPythonPEPIndent(line('.'))").to_i
42+
vim.command("call cursor(#{line}, #{col})")
43+
return indent_value
44+
end
45+
def multiline_indent(prev, default)
46+
i = vim.echo("get(g:, 'python_pep8_indent_multiline_string', 0)").to_i
47+
return (i == -2 ? default : i), i < 0 ? (i == -1 ? prev : default) : i
48+
end
49+
2550
vim
2651
end
2752
end

0 commit comments

Comments
 (0)