--- /dev/null
+ version: 2
+
+ common: &common
+ working_directory: ~/repo
+ docker:
+ - image: blueyed/vim-python-pep8-indent-vims-for-test:3@sha256:e7e3c4f4b021954a40f2f1d88dc470f119dc65603c63724d1c58cbe437fdc2d4
+
+ jobs:
+ test:
+ <<: *common
+ steps:
+ - checkout
+ - run:
+ name: Run tests
+ command: |
+ spec/make-coverage
+ - run:
+ name: Report coverage
+ command: |
+ covimerage xml
+ codecov -X search gcov pycov -f coverage.xml
+
+ checkqa:
+ <<: *common
+ steps:
+ - checkout
+ - run:
+ name: Lint
+ command: |
+ vint **/*.vim
+
+ workflows:
+ version: 2
+ test:
+ jobs:
+ - test
+ - checkqa
--- /dev/null
+ [run]
+ plugins = covimerage
+ data_file = .coverage_covimerage
+ source = indent/python.vim
+
+ [report]
+ include = indent/python.vim
--- /dev/null
+ *
+ !Gemfile
--- /dev/null
+ .*.swp
+ .coverage_covimerage
+ Gemfile.lock
--- /dev/null
+ How To Contribute
+ =================
+
+ ``vim-python-pep8-indent`` is always open for suggestions and contributions by generous developers.
+ I’ve collected a few tips to get you started.
+
+ Please:
+
+ - *Always* add tests for your code.
+ - Write `good commit messages`_.
+
+
+ Running Tests
+ -------------
+
+ - They are written in Ruby_ (sorry :() using vimrunner_ which requires rspec_.
+ - The tests go into ``spec/indent/indent_spec.rb``.
+ Look at the ``describe`` blocks to get the hang of it.
+ - Run the tests with the command::
+
+ $ rspec spec
+ - Alternatively you can use Docker::
+
+ $ make test_docker
+
+ - You can select tests based on line numbers, e.g.::
+
+ $ rspec ./spec/indent/indent_spec.rb:385
+ $ make test_docker RSPEC_ARGS=./spec/indent/indent_spec.rb:385
+
+ Thank you for considering to contribute!
+
+
+ .. _Ruby: https://www.ruby-lang.org/
+ .. _`good commit messages`: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+ .. _vimrunner: https://github.com/AndrewRadev/vimrunner
+ .. _rspec: https://github.com/rspec/rspec
--- /dev/null
+ Creative Commons Legal Code
+
+ CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+ Statement of Purpose
+
+ The laws of most jurisdictions throughout the world automatically confer
+ exclusive Copyright and Related Rights (defined below) upon the creator
+ and subsequent owner(s) (each and all, an "owner") of an original work of
+ authorship and/or a database (each, a "Work").
+
+ Certain owners wish to permanently relinquish those rights to a Work for
+ the purpose of contributing to a commons of creative, cultural and
+ scientific works ("Commons") that the public can reliably and without fear
+ of later claims of infringement build upon, modify, incorporate in other
+ works, reuse and redistribute as freely as possible in any form whatsoever
+ and for any purposes, including without limitation commercial purposes.
+ These owners may contribute to the Commons to promote the ideal of a free
+ culture and the further production of creative, cultural and scientific
+ works, or to gain reputation or greater distribution for their Work in
+ part through the use and efforts of others.
+
+ For these and/or other purposes and motivations, and without any
+ expectation of additional consideration or compensation, the person
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+ is an owner of Copyright and Related Rights in the Work, voluntarily
+ elects to apply CC0 to the Work and publicly distribute the Work under its
+ terms, with knowledge of his or her Copyright and Related Rights in the
+ Work and the meaning and intended legal effect of CC0 on those rights.
+
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights ("Copyright and
+ Related Rights"). Copyright and Related Rights include, but are not
+ limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+ iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+ vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
+ irrevocably and unconditionally waives, abandons, and surrenders all of
+ Affirmer's Copyright and Related Rights and associated claims and causes
+ of action, whether now known or unknown (including existing as well as
+ future claims and causes of action), in the Work (i) in all territories
+ worldwide, (ii) for the maximum duration provided by applicable law or
+ treaty (including future time extensions), (iii) in any current or future
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
+ including without limitation commercial, advertising or promotional
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+ member of the public at large and to the detriment of Affirmer's heirs and
+ successors, fully intending that such Waiver shall not be subject to
+ revocation, rescission, cancellation, termination, or any other legal or
+ equitable action to disrupt the quiet enjoyment of the Work by the public
+ as contemplated by Affirmer's express Statement of Purpose.
+
+ 3. Public License Fallback. Should any part of the Waiver for any reason
+ be judged legally invalid or ineffective under applicable law, then the
+ Waiver shall be preserved to the maximum extent permitted taking into
+ account Affirmer's express Statement of Purpose. In addition, to the
+ extent the Waiver is so judged Affirmer hereby grants to each affected
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
+ maximum duration provided by applicable law or treaty (including future
+ time extensions), (iii) in any current or future medium and for any number
+ of copies, and (iv) for any purpose whatsoever, including without
+ limitation commercial, advertising or promotional purposes (the
+ "License"). The License shall be deemed effective as of the date CC0 was
+ applied by Affirmer to the Work. Should any part of the License for any
+ reason be judged legally invalid or ineffective under applicable law, such
+ partial invalidity or ineffectiveness shall not invalidate the remainder
+ of the License, and in such case Affirmer hereby affirms that he or she
+ will not (i) exercise any of his or her remaining Copyright and Related
+ Rights in the Work or (ii) assert any associated claims and causes of
+ action with respect to the Work, in either case contrary to Affirmer's
+ express Statement of Purpose.
+
+ 4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
--- /dev/null
+ FROM testbed/vim:latest
+
+ RUN apk --no-cache add gtk+2.0-dev libx11-dev libxt-dev mcookie xauth xvfb
+ # NOTE: +profile needs huge features.
+ RUN install_vim -tag v8.1.0129 -name vim --with-features=huge \
+ --disable-channel --disable-netbeans --disable-xim \
+ --enable-gui=gtk2 --with-x -build
+ RUN ln -s /vim-build/bin/vim /usr/bin/gvim
+ RUN gvim --version
+
+ # Install covimerage and vint.
+ # NOTE: we have py2 already via gtk+2.0-dev.
+ # NOTE: enum34+pathlib+typing gets installed as workaround for broken vim-vint wheel.
+ RUN apk --no-cache add py2-pip \
+ && pip install --no-cache-dir codecov covimerage==0.0.9 vim-vint enum34 pathlib typing \
+ && rm -rf /usr/include /usr/lib/python*/turtle* /usr/lib/python*/tkinter
+
+ WORKDIR /vim-python-pep8-indent
+
+ ADD Gemfile .
+ RUN apk --no-cache add coreutils ruby-bundler
+ RUN bundle install
+
+ ENTRYPOINT ["rspec", "spec"]
--- /dev/null
+ source 'https://rubygems.org'
+ gem "vimrunner", "0.3.4"
+ gem "rspec"
--- /dev/null
+ test:
+ VIMRUNNER_REUSE_SERVER=1 xvfb-run bundle exec rspec
+
+ # Run tests in dockerized Vims.
+ DOCKER_REPO:=blueyed/vim-python-pep8-indent-vims-for-test
+ DOCKER_TAG:=3
+ DOCKER_IMAGE:=$(DOCKER_REPO):$(DOCKER_TAG)
+
+ docker_image:
+ docker build -t $(DOCKER_REPO):$(DOCKER_TAG) .
+ docker_push:
+ docker push $(DOCKER_REPO):$(DOCKER_TAG)
+ docker_update_latest:
+ docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REPO):latest
+ docker push $(DOCKER_REPO):latest
+
+ test_docker: XVFB_ERRORFILE:=/dev/null
+ test_docker:
+ @set -x; export DISPLAY=$(if $(VIMRUNNER_TEST_DISPLAY),$(VIMRUNNER_TEST_DISPLAY),172.17.0.1:99; Xvfb -ac -listen tcp :99 >$(XVFB_ERRORFILE) 2>&1 & XVFB_PID=$$!); \
+ docker run --rm -ti -e DISPLAY -e VIMRUNNER_REUSE_SERVER=1 \
+ -v $(CURDIR):/vim-python-pep8-indent $(DOCKER_IMAGE) $(RSPEC_ARGS) \
+ $(if $(VIMRUNNER_TEST_DISPLAY),,; ret=$$?; kill $$XVFB_PID; exit $$ret)
+
+ test_coverage:
+ spec/make-coverage
--- /dev/null
+ vim-python-pep8-indent
+ ======================
+
+ .. image:: https://circleci.com/gh/Vimjas/vim-python-pep8-indent.svg?style=svg
+ :target: https://circleci.com/gh/Vimjas/vim-python-pep8-indent
+ .. image:: https://codecov.io/gh/Vimjas/vim-python-pep8-indent/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/Vimjas/vim-python-pep8-indent
+
+ This small script modifies Vim_’s indentation behavior to comply with PEP8_ and my aesthetic preferences.
+ Most importantly::
+
+ foobar(foo,
+ bar)
+
+ and::
+
+ foobar(
+ foo,
+ bar
+ )
+
+
+ Installation
+ ------------
+
+ Install the plugin using your favorite plugin manager / method, a few examples
+ follow:
+
+ Pathogen
+ ^^^^^^^^
+
+ Follow the instructions on installing Pathogen_ and then:
+
+ .. code-block:: shell-session
+
+ $ cd ~/.vim/bundle
+ $ git clone https://github.com/Vimjas/vim-python-pep8-indent.git
+
+
+ Vundle
+ ^^^^^^
+
+ Follow the instructions on installing Vundle_ and add the appropriate plugin line into your ``.vimrc``:
+
+ .. code-block:: vim
+
+ Plugin 'Vimjas/vim-python-pep8-indent'
+
+
+ NeoBundle
+ ^^^^^^^^^
+
+ Follow the instructions on installing NeoBundle_ and add the appropriate NeoBundle line into your ``.vimrc``:
+
+ .. code-block:: vim
+
+ NeoBundle 'Vimjas/vim-python-pep8-indent'
+
+
+ Configuration
+ -------------
+
+ g: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.
+
+ g:python_pep8_indent_hang_closing
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Control closing bracket indentation with ``python_pep8_indent_hang_closing``, set globally or per buffer.
+
+ By default (set to ``0``), closing brackets line up with the opening line::
+
+ my_list = [
+ 1, 2, 3,
+ 4, 5, 6,
+ ]
+ result = some_function_that_takes_arguments(
+ 'a', 'b', 'c',
+ 'd', 'e', 'f',
+ )
+
+ With ``python_pep8_indent_hang_closing = 1``, closing brackets line up with the items::
+
+ my_list = [
+ 1, 2, 3,
+ 4, 5, 6,
+ ]
+ result = some_function_that_takes_arguments(
+ 'a', 'b', 'c',
+ 'd', 'e', 'f',
+ )
+
+
+ Troubleshooting
+ ---------------
+
+ In case it is not working, please make sure your Vim is configured to load
+ indent files (``filetype indent on``).
+ This is typically the case when using a plugin manager, but check its docs.
+
+ Check ``:verbose set indentexpr?`` in a Python file, which should show
+ something like the following:
+
+ indentexpr=GetPythonPEPIndent(v:lnum)
+ Last set from ~/…/plugged/vim-python-pep8-indent/indent/python.vim
+
+
+ Notes
+ -----
+
+ Please note that Kirill Klenov’s python-mode_ ships its own version of this bundle.
+ Therefore, if you want to use this version specifically, you’ll have to disable python-mode’s using:
+
+ .. code-block:: vim
+
+ let g:pymode_indent = 0
+
+
+ License and Authorship
+ ----------------------
+
+ This script is based on one from Vim’s official `script repo`_ that was *not* originally written by me.
+ Unfortunately the indentation was off by one character in one case and the script hasn’t been updated since 2005.
+
+ Even more unfortunately, I wasn’t able to reach any of the original authors/maintainers:
+ **David Bustos** and **Eric Mc Sween**.
+
+ So I fixed the annoyance with the help of `Steve Losh`_ and am putting it out here so you don’t have to patch the original yourself.
+ The original patch is still available here_.
+
+ Over the time a lot more improvements have been contributed_ by `generous people`_.
+
+ I’d like to thank the original authors here for their work and release it hereby to the *Public Domain* (using the CC0_ licence) since I hope that would be in their spirit.
+ If anyone with a say in this objects, please let me_ know immediately.
+ Also, if someone is in contact with one of them, I would appreciate being introduced.
+
+ While my Vimscript_ skills are still feeble, I intend to maintain it for now.
+ This mainly means that I’ll triage through bugs and pull requests but won’t be fixing much myself.
+
+
+ .. _Vim: http://www.vim.org/
+ .. _PEP8: http://www.python.org/dev/peps/pep-0008/
+ .. _`script repo`: http://www.vim.org/scripts/script.php?script_id=974
+ .. _`Steve Losh`: http://stevelosh.com/
+ .. _here: https://gist.github.com/2965846
+ .. _Neobundle: https://github.com/Shougo/neobundle.vim
+ .. _Pathogen: https://github.com/tpope/vim-pathogen
+ .. _python-mode: https://github.com/klen/python-mode
+ .. _`Vimscript`: http://learnvimscriptthehardway.stevelosh.com/
+ .. _vundle: https://github.com/gmarik/Vundle.vim
+ .. _me: https://hynek.me/
+ .. _CC0: http://creativecommons.org/publicdomain/zero/1.0/
+ .. _contributed: https://github.com/hynek/vim-python-pep8-indent/blob/master/CONTRIBUTING.rst
+ .. _`generous people`: https://github.com/hynek/vim-python-pep8-indent/graphs/contributors
--- /dev/null
+ version: '2'
+ services:
+ rspec:
+ build: .
+ volumes:
+ - .:/vim-python-pep8-indent
--- /dev/null
+ python.vim
--- /dev/null
+ " PEP8 compatible Python indent file
+ " Language: Python
+ " Maintainer: Daniel Hahler <https://daniel.hahler.de/>
+ " Prev Maintainer: Hynek Schlawack <hs@ox.cx>
+ " Prev Maintainer: Eric Mc Sween <em@tomcom.de> (address invalid)
+ " Original Author: David Bustos <bustos@caltech.edu> (address invalid)
+ " License: CC0
+ "
+ " vim-python-pep8-indent - A nicer Python indentation style for vim.
+ " Written in 2004 by David Bustos <bustos@caltech.edu>
+ " Maintained from 2004-2005 by Eric Mc Sween <em@tomcom.de>
+ " Maintained from 2013 by Hynek Schlawack <hs@ox.cx>
+ " Maintained from 2017 by Daniel Hahler <https://daniel.hahler.de/>
+ "
+ " To the extent possible under law, the author(s) have dedicated all copyright
+ " and related and neighboring rights to this software to the public domain
+ " worldwide. This software is distributed without any warranty.
+ " You should have received a copy of the CC0 Public Domain Dedication along
+ " with this software. If not, see
+ " <http://creativecommons.org/publicdomain/zero/1.0/>.
+
+ " Only load this indent file when no other was loaded.
+ if exists('b:did_indent')
+ finish
+ endif
+ let b:did_indent = 1
+
+ setlocal nolisp
+ setlocal autoindent
+ setlocal indentexpr=GetPythonPEPIndent(v:lnum)
+ setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except
+
+ if !exists('g:python_pep8_indent_multiline_string')
+ let g:python_pep8_indent_multiline_string = 0
+ endif
+
+ if !exists('g:python_pep8_indent_hang_closing')
+ let g:python_pep8_indent_hang_closing = 0
+ endif
+
+ " TODO: check required patch for timeout argument, likely lower than 7.3.429 though.
+ if !exists('g:python_pep8_indent_searchpair_timeout')
+ if has('patch-8.0.1483')
+ let g:python_pep8_indent_searchpair_timeout = 150
+ else
+ let g:python_pep8_indent_searchpair_timeout = 0
+ endif
+ endif
+
+ let s:block_rules = {
+ \ '^\s*elif\>': [['if', 'elif'], ['else']],
+ \ '^\s*except\>': [['try', 'except'], []],
+ \ '^\s*finally\>': [['try', 'except', 'else'], []]
+ \ }
+ let s:block_rules_multiple = {
+ \ '^\s*else\>': [['if', 'elif', 'for', 'try', 'except'], []]
+ \ }
+ " Pairs to look for when searching for opening parenthesis.
+ " The value is the maximum offset in lines.
+ let s:paren_pairs = {'()': 50, '[]': 100, '{}': 1000}
+
+ if &filetype ==# 'pyrex' || &filetype ==# 'cython'
+ let b:control_statement = '\v^\s*(class|def|if|while|with|for|except|cdef|cpdef)>'
+ else
+ let b:control_statement = '\v^\s*(class|def|if|while|with|for|except)>'
+ endif
+ let s:stop_statement = '^\s*\(break\|continue\|raise\|return\|pass\)\>'
+
+ let s:skip_after_opening_paren = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
+ \ '=~? "\\vcomment|jedi\\S"'
+
+ let s:special_chars_syn_pattern = "\\vstring|comment|^pythonbytes%(contents)=$|pythonTodo|jedi\\S"
+
+ if !get(g:, 'python_pep8_indent_skip_concealed', 0) || !has('conceal')
+ " Skip strings and comments. Return 1 for chars to skip.
+ " jedi* refers to syntax definitions from jedi-vim for call signatures, which
+ " are inserted temporarily into the buffer.
+ function! s:_skip_special_chars(line, col)
+ return synIDattr(synID(a:line, a:col, 0), 'name')
+ \ =~? s:special_chars_syn_pattern
+ endfunction
+ else
+ " Also ignore anything concealed.
+ " TODO: doc; likely only necessary with jedi-vim, where a better version is
+ " planned (https://github.com/Vimjas/vim-python-pep8-indent/pull/98).
+
+ " Wrapper around synconcealed for older Vim (7.3.429, used on Travis CI).
+ function! s:is_concealed(line, col)
+ let concealed = synconcealed(a:line, a:col)
+ return len(concealed) && concealed[0]
+ endfunction
+
+ function! s:_skip_special_chars(line, col)
+ return synIDattr(synID(a:line, a:col, 0), 'name')
+ \ =~? s:special_chars_syn_pattern
+ \ || s:is_concealed(a:line, a:col)
+ endfunction
+ endif
+
+ " Use 'shiftwidth()' instead of '&sw'.
+ " (Since Vim patch 7.3.629, 'shiftwidth' can be set to 0 to follow 'tabstop').
+ if exists('*shiftwidth')
+ function! s:sw()
+ return shiftwidth()
+ endfunction
+ else
+ function! s:sw()
+ return &shiftwidth
+ endfunction
+ endif
+
+ " Find backwards the closest open parenthesis/bracket/brace.
+ function! s:find_opening_paren(lnum, col)
+ " Return if cursor is in a comment.
+ if synIDattr(synID(a:lnum, a:col, 0), 'name') =~? 'comment'
+ return [0, 0]
+ endif
+
+ call cursor(a:lnum, a:col)
+
+ let nearest = [0, 0]
+ let timeout = g:python_pep8_indent_searchpair_timeout
+ let skip_special_chars = 's:_skip_special_chars(line("."), col("."))'
+ for [p, maxoff] in items(s:paren_pairs)
+ let stopline = max([0, line('.') - maxoff, nearest[0]])
+ let next = searchpairpos(
+ \ '\V'.p[0], '', '\V'.p[1], 'bnW', skip_special_chars, stopline, timeout)
+ if next[0] && (next[0] > nearest[0] || (next[0] == nearest[0] && next[1] > nearest[1]))
+ let nearest = next
+ endif
+ endfor
+ return nearest
+ endfunction
+
+ " Find the start of a multi-line statement
+ function! s:find_start_of_multiline_statement(lnum)
+ let lnum = a:lnum
+ while lnum > 0
+ if getline(lnum - 1) =~# '\\$'
+ let lnum = prevnonblank(lnum - 1)
+ else
+ let [paren_lnum, _] = s:find_opening_paren(lnum, 1)
+ if paren_lnum < 1
+ return lnum
+ else
+ let lnum = paren_lnum
+ endif
+ endif
+ endwhile
+ endfunction
+
+ " Find possible indent(s) of the block starter that matches the current line.
+ function! s:find_start_of_block(lnum, types, skip, multiple) abort
+ let r = []
+ let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
+ if !empty(a:skip)
+ let re_skip = '\V\^\s\*\('.join(a:skip, '\|').'\)\>'
+ else
+ let re_skip = ''
+ endif
+ let last_indent = indent(a:lnum) + 1
+ let lnum = a:lnum - 1
+ while lnum > 0 && last_indent > 0
+ let indent = indent(lnum)
+ if indent < last_indent
+ let line = getline(lnum)
+ if !empty(re_skip) && line =~# re_skip
+ let last_indent = indent
+ elseif line =~# re
+ if !a:multiple
+ return [indent]
+ endif
+ if index(r, indent) == -1
+ let r += [indent]
+ endif
+ let last_indent = indent
+ endif
+ endif
+ let lnum = prevnonblank(lnum - 1)
+ endwhile
+ return r
+ endfunction
+
+ " Is "expr" true for every position in "lnum", beginning at "start"?
+ " (optionally up to a:1 / 4th argument)
+ function! s:match_expr_on_line(expr, lnum, start, ...)
+ let text = getline(a:lnum)
+ let end = a:0 ? a:1 : len(text)
+ if a:start > end
+ return 1
+ endif
+ let save_pos = getpos('.')
+ let r = 1
+ for i in range(a:start, end)
+ call cursor(a:lnum, i)
+ if !(eval(a:expr) || text[i-1] =~# '\s')
+ let r = 0
+ break
+ endif
+ endfor
+ call setpos('.', save_pos)
+ return r
+ endfunction
+
+ " Line up with open parenthesis/bracket/brace.
+ function! s:indent_like_opening_paren(lnum)
+ let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum, 1)
+ if paren_lnum <= 0
+ return -2
+ endif
+ let text = getline(paren_lnum)
+ let base = indent(paren_lnum)
+
+ let nothing_after_opening_paren = s:match_expr_on_line(
+ \ s:skip_after_opening_paren, paren_lnum, paren_col+1)
+ let starts_with_closing_paren = getline(a:lnum) =~# '^\s*[])}]'
+
+ let hang_closing = get(b:, 'python_pep8_indent_hang_closing',
+ \ get(g:, 'python_pep8_indent_hang_closing', 0))
+
+ if nothing_after_opening_paren
+ if starts_with_closing_paren && !hang_closing
+ let res = base
+ else
+ let res = base + s:sw()
+
+ " Special case for parenthesis.
+ if text[paren_col-1] ==# '(' && getline(a:lnum) !~# '\v\)\s*:?\s*$'
+ return res
+ endif
+ endif
+ else
+ " Indent to match position of opening paren.
+ let res = paren_col
+ endif
+
+ " If this line is the continuation of a control statement
+ " indent further to distinguish the continuation line
+ " from the next logical line.
+ if text =~# b:control_statement && res == base + s:sw()
+ " But only if not inside parens itself (Flake's E127).
+ let [paren_lnum, _] = s:find_opening_paren(paren_lnum, 1)
+ if paren_lnum <= 0
+ return res + s:sw()
+ endif
+ endif
+ return res
+ endfunction
+
+ " Match indent of first block of this type.
+ function! s:indent_like_block(lnum)
+ let text = getline(a:lnum)
+ for [multiple, block_rules] in [
+ \ [0, s:block_rules],
+ \ [1, s:block_rules_multiple],
+ \ ]
+ for [line_re, blocks_ignore] in items(block_rules)
+ if text !~# line_re
+ continue
+ endif
+
+ let [blocks, skip] = blocks_ignore
+ let indents = s:find_start_of_block(a:lnum, blocks, skip, multiple)
+ if empty(indents)
+ return -1
+ endif
+ if len(indents) == 1
+ return indents[0]
+ endif
+
+ " Multiple valid indents, e.g. for 'else' with both try and if.
+ let indent = indent(a:lnum)
+ if index(indents, indent) != -1
+ " The indent is valid, keep it.
+ return indent
+ endif
+ " Fallback to the first/nearest one.
+ return indents[0]
+ endfor
+ endfor
+ return -2
+ endfunction
+
+ function! s:indent_like_previous_line(lnum)
+ let lnum = prevnonblank(a:lnum - 1)
+
+ " No previous line, keep current indent.
+ if lnum < 1
+ return -1
+ endif
+
+ let text = getline(lnum)
+ let start = s:find_start_of_multiline_statement(lnum)
+ let base = indent(start)
+ let current = indent(a:lnum)
+
+ " Ignore last character in previous line?
+ let lastcol = len(text)
+ let col = lastcol
+
+ " Search for final colon that is not inside something to be ignored.
+ while 1
+ if col == 1 | break | endif
+ if text[col-1] =~# '\s' || s:_skip_special_chars(lnum, col)
+ let col = col - 1
+ continue
+ elseif text[col-1] ==# ':'
+ return base + s:sw()
+ endif
+ break
+ endwhile
+
+ if text =~# '\\$' && !s:_skip_special_chars(lnum, lastcol)
+ " If this line is the continuation of a control statement
+ " indent further to distinguish the continuation line
+ " from the next logical line.
+ if getline(start) =~# b:control_statement
+ return base + s:sw() * 2
+ endif
+
+ " Nest (other) explicit continuations only one level deeper.
+ return base + s:sw()
+ endif
+
+ let empty = getline(a:lnum) =~# '^\s*$'
+
+ " Current and prev line are empty, next is not -> indent like next.
+ if empty && a:lnum > 1 &&
+ \ (getline(a:lnum - 1) =~# '^\s*$') &&
+ \ !(getline(a:lnum + 1) =~# '^\s*$')
+ return indent(a:lnum + 1)
+ endif
+
+ " If the previous statement was a stop-execution statement or a pass
+ if getline(start) =~# s:stop_statement
+ " Remove one level of indentation if the user hasn't already dedented
+ if empty || current > base - s:sw()
+ return base - s:sw()
+ endif
+ " Otherwise, trust the user
+ return -1
+ endif
+
+ if (current || !empty) && s:is_dedented_already(current, base)
+ return -1
+ endif
+
+ " In all other cases, line up with the start of the previous statement.
+ return base
+ endfunction
+
+ " If this line is dedented and the number of indent spaces is valid
+ " (multiple of the indentation size), trust the user.
+ function! s:is_dedented_already(current, base)
+ let dedent_size = a:current - a:base
+ return (dedent_size < 0 && a:current % s:sw() == 0) ? 1 : 0
+ endfunction
+
+ " Is the syntax at lnum (and optionally cnum) a python string?
+ function! s:is_python_string(lnum, ...)
+ let line = getline(a:lnum)
+ if a:0
+ let cols = type(a:1) != type([]) ? [a:1] : a:1
+ else
+ let cols = range(1, max([1, len(line)]))
+ endif
+ for cnum in cols
+ if match(map(synstack(a:lnum, cnum),
+ \ "synIDattr(v:val, 'name')"), 'python\S*String') == -1
+ return 0
+ end
+ endfor
+ return 1
+ endfunction
+
+ function! GetPythonPEPIndent(lnum)
+ " First line has indent 0
+ if a:lnum == 1
+ return 0
+ endif
+
+ let line = getline(a:lnum)
+ let prevline = getline(a:lnum-1)
+
+ " Multilinestrings: continous, docstring or starting.
+ if s:is_python_string(a:lnum-1, max([1, len(prevline)]))
+ \ && (s:is_python_string(a:lnum, 1)
+ \ || match(line, '^\%("""\|''''''\)') != -1)
+
+ " Indent closing quotes as the line with the opening ones.
+ let match_quotes = match(line, '^\s*\zs\%("""\|''''''\)')
+ if match_quotes != -1
+ " closing multiline string
+ let quotes = line[match_quotes:(match_quotes+2)]
+ call cursor(a:lnum, 1)
+ let pairpos = searchpairpos(quotes, '', quotes, 'bW', '', 0, g:python_pep8_indent_searchpair_timeout)
+ if pairpos[0] != 0
+ return indent(pairpos[0])
+ else
+ return -1
+ endif
+ endif
+
+ if s:is_python_string(a:lnum-1)
+ " Previous line is (completely) a string: keep current indent.
+ return -1
+ endif
+
+ if match(prevline, '^\s*\%("""\|''''''\)') != -1
+ " docstring.
+ return indent(a:lnum-1)
+ endif
+
+ let indent_multi = get(b:, 'python_pep8_indent_multiline_string',
+ \ get(g:, 'python_pep8_indent_multiline_string', 0))
+ if match(prevline, '\v%("""|'''''')$') != -1
+ " Opening multiline string, started in previous line.
+ if (&autoindent && indent(a:lnum) == indent(a:lnum-1))
+ \ || match(line, '\v^\s+$') != -1
+ " <CR> with empty line or to split up 'foo("""bar' into
+ " 'foo("""' and 'bar'.
+ if indent_multi == -2
+ return indent(a:lnum-1) + s:sw()
+ endif
+ return indent_multi
+ endif
+ endif
+
+ " Keep existing indent.
+ if match(line, '\v^\s*\S') != -1
+ return -1
+ endif
+
+ if indent_multi != -2
+ return indent_multi
+ endif
+
+ return s:indent_like_opening_paren(a:lnum)
+ endif
+
+ " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
+ let indent = s:indent_like_opening_paren(a:lnum)
+ if indent >= -1
+ return indent
+ endif
+
+ " Blocks: Match indent of first block of this type.
+ let indent = s:indent_like_block(a:lnum)
+ if indent >= -1
+ return indent
+ endif
+
+ return s:indent_like_previous_line(a:lnum)
+ endfunction
--- /dev/null
+ require "spec_helper"
+
+ describe "handles byte strings" do
+ before(:all) {
+ vim.command 'syn region pythonBytes start=+[bB]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonBytesError,pythonBytesContent,@Spell'
+ vim.command "syn match pythonBytesEscape '\\\\$'"
+ }
+
+ before(:each) {
+ # clear buffer
+ vim.normal 'gg"_dG'
+
+ # Insert two blank lines.
+ # The first line is a corner case in this plugin that would shadow the
+ # correct behaviour of other tests. Thus we explicitly jump to the first
+ # line when we require so.
+ vim.feedkeys 'i\<CR>\<CR>\<ESC>'
+ }
+
+ it "it does not indent to bracket in byte string" do
+ vim.feedkeys 'ireg = b"["\<Esc>'
+ vim.echo('map(synstack(line("."), col(".")), "synIDattr(v:val, \"name\")")'
+ ).should == "['pythonBytes']"
+ vim.feedkeys 'o'
+ indent.should == 0
+ end
+
+ it "it indents backslash continuation correctly" do
+ vim.feedkeys 'iwith foo, \<Bslash>\<Esc>'
+ vim.echo('getline(".")').should == "with foo, \\"
+ vim.echo('map(synstack(line("."), col(".")), "synIDattr(v:val, \"name\")")'
+ ).should == "['pythonBytesEscape']"
+ vim.feedkeys 'o'
+ indent.should == 8
+ end
+ end
--- /dev/null
+ require "spec_helper"
+
+ describe "vim for cython" do
+ before(:all) {
+ vim.command "new"
+ vim.command "set ft=cython"
+ vim.command("set indentexpr?").should include "GetPythonPEPIndent("
+ }
+ before(:each) {
+ # clear buffer
+ vim.normal 'gg"_dG'
+
+ # Insert two blank lines.
+ # The first line is a corner case in this plugin that would shadow the
+ # correct behaviour of other tests. Thus we explicitly jump to the first
+ # line when we require so.
+ vim.feedkeys 'i\<CR>\<CR>\<ESC>'
+ }
+ after(:all) {
+ vim.command "bwipe!"
+ }
+
+ describe "when using a cdef function definition" do
+ it "indents shiftwidth spaces" do
+ vim.feedkeys 'icdef long_function_name(\<CR>arg'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when using a cpdef function definition" do
+ it "indents shiftwidth spaces" do
+ vim.feedkeys 'icpdef long_function_name(\<CR>arg'
+ indent.should == shiftwidth
+ end
+ end
+ end
--- /dev/null
+ require "spec_helper"
+
+ shared_examples_for "vim" do
+ before(:each) {
+ # clear buffer
+ vim.normal 'gg"_dG'
+
+ # Insert two blank lines.
+ # The first line is a corner case in this plugin that would shadow the
+ # correct behaviour of other tests. Thus we explicitly jump to the first
+ # line when we require so.
+ vim.feedkeys 'i\<CR>\<CR>\<ESC>'
+ }
+
+ describe "when using the indent plugin" do
+ it "sets the indentexpr and indentkeys options" do
+ vim.command("set indentexpr?").should include "GetPythonPEPIndent("
+ vim.command("set indentkeys?").should include "=elif"
+ end
+
+ it "sets autoindent and expandtab" do
+ vim.command("set autoindent?").should match(/\s*autoindent/)
+ vim.command("set expandtab?").should match(/\s*expandtab/)
+ end
+ end
+
+ describe "when entering the first line" do
+ before { vim.feedkeys '0ggipass' }
+
+ it "does not indent" do
+ indent.should == 0
+ proposed_indent.should == 0
+ end
+
+ it "does not indent when using '=='" do
+ vim.normal "=="
+ indent.should == 0
+ end
+ end
+
+ describe "when after a '(' that is at the end of its line" do
+ before { vim.feedkeys 'itest(\<CR>' }
+
+ it "indents by one level" do
+ proposed_indent.should == shiftwidth
+ vim.feedkeys 'something'
+ indent.should == shiftwidth
+ vim.normal '=='
+ indent.should == shiftwidth
+ end
+
+ it "puts the closing parenthesis at the same level" do
+ vim.feedkeys ')'
+ indent.should == (hang_closing ? shiftwidth : 0)
+ end
+ end
+
+ describe "when after an '(' that is followed by something" do
+ before { vim.feedkeys 'itest(something,\<CR>' }
+
+ it "lines up on following lines" do
+ indent.should == 5
+ vim.feedkeys 'more,\<CR>'
+ indent.should == 5
+ end
+
+ it "lines up the closing parenthesis" do
+ vim.feedkeys ')'
+ indent.should == 5
+ end
+
+ it "does not touch the closing parenthesis if it is already indented further" do
+ vim.feedkeys ' )'
+ indent.should == 7
+ end
+ end
+
+ describe "when after an '{' that is followed by a comment" do
+ before { vim.feedkeys 'imydict = { # comment\<CR>' }
+
+ it "indent by one level" do
+ indent.should == shiftwidth
+ vim.feedkeys '1: 1,\<CR>'
+ indent.should == shiftwidth
+ end
+
+ it "lines up the closing parenthesis" do
+ vim.feedkeys '}'
+ indent.should == (hang_closing ? shiftwidth : 0)
+ end
+ end
+
+ describe "when using gq to reindent a '(' that is" do
+ before { vim.feedkeys 'itest(' }
+ it "something and has a string without spaces at the end" do
+ vim.feedkeys 'something_very_long_blaaaaaaaaa, "some_very_long_string_blaaaaaaaaaaaaaaaaaaaa"\<esc>gqq'
+ indent.should == 5
+ end
+ end
+
+ describe "when after multiple parens of different types" do
+ it "indents by one level" do
+ vim.feedkeys 'if({\<CR>'
+ indent.should == shiftwidth
+ end
+
+ it "lines up with the last paren" do
+ vim.feedkeys 'ifff({123: 456,\<CR>'
+ indent.should == 5
+ end
+ end
+
+ describe "when '#' is contained in a string that is followed by a colon" do
+ it "indents by one level" do
+ vim.feedkeys 'iif "some#thing" == "test":#test\<CR>pass'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when '#' is not contained in a string and is followed by a colon" do
+ it "does not indent" do
+ vim.feedkeys 'iif "some#thing" == "test"#:test\<CR>'
+ indent.should == 0
+ end
+ end
+
+ describe "when inside an unfinished string" do
+ it "does not indent" do
+ vim.feedkeys 'i"test:\<ESC>'
+ vim.echo('synIDattr(synID(line("."), col("."), 0), "name")'
+ ).downcase.should include 'string'
+ vim.feedkeys 'a\<CR>'
+ proposed_indent.should == -1
+ indent.should == 0
+ end
+
+ it "does not dedent" do
+ vim.feedkeys 'iif True:\<CR>"test:\<ESC>'
+ vim.echo('synIDattr(synID(line("."), col("."), 0), "name")'
+ ).downcase.should include 'string'
+ proposed_indent.should == shiftwidth
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when the previous line has a colon in a string" do
+ before { vim.feedkeys 'itest(":".join(["1","2"]))\<CR>' }
+ it "does not indent" do
+ vim.feedkeys 'if True:'
+ indent.should == 0
+ proposed_indent.should == 0
+ end
+ end
+
+ describe "when the previous line has a list slice" do
+ it "does not indent" do
+ vim.feedkeys 'ib = a[2:]\<CR>'
+ indent.should == 0
+ proposed_indent.should == 0
+ end
+ end
+
+ describe "when line is empty inside a block" do
+ it "is indented like the previous line" do
+ vim.feedkeys 'idef a():\<CR>1\<CR>\<CR>2\<ESC>kcc'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when an empty line is after empty line / before non-empty" do
+ it "is indented like the next line" do
+ vim.feedkeys 'idef a():\<CR>1\<CR>\<CR>\<CR>2\<ESC><<kcc'
+ indent.should == 0
+ end
+ end
+
+ describe "when an empty line is after empty line / before non-empty (nested)" do
+ it "is indented like the next line" do
+ vim.feedkeys 'idef a():\<CR>1\<CR>\<CR>\<CR>\<ESC>0i\<TAB>2\<ESC>kcc'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when line is empty inside a block following multi-line statement" do
+ it "is indented like the previous line" do
+ vim.feedkeys 'idef a():\<CR>x = (1 +\<CR>2)\<CR>\<CR>y\<ESC>kcc'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when line is empty inside a block following stop statement" do
+ it "is indented like the previous line minus shiftwidth" do
+ vim.feedkeys 'iif x:\<CR>if y:\<CR>pass\<CR>\<CR>z\<ESC>kcc'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when using simple control structures" do
+ it "indents shiftwidth spaces" do
+ vim.feedkeys 'iwhile True:\<CR>pass'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when using a function definition" do
+ it "handles indent with closing parenthesis on same line" do
+ vim.feedkeys 'idef long_function_name(\<CR>arg'
+ indent.should == shiftwidth
+ vim.feedkeys '):'
+ indent.should == shiftwidth * 2
+ end
+
+ it "handles indent with closing parenthesis on new line" do
+ vim.feedkeys 'idef long_function_name(\<CR>arg'
+ indent.should == shiftwidth
+ vim.feedkeys '\<CR>'
+ indent.should == shiftwidth
+ vim.feedkeys ')'
+ indent.should == (hang_closing ? shiftwidth * 2 : 0)
+ vim.feedkeys ':'
+ indent.should == (hang_closing ? shiftwidth * 2 : 0)
+ vim.feedkeys '\<Esc>k'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when using a class definition" do
+ it "indents shiftwidth spaces" do
+ vim.feedkeys 'iclass Foo(\<CR>'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when writing an 'else' block" do
+ it "aligns to the preceeding 'for' block" do
+ vim.feedkeys 'ifor x in "abc":\<CR>pass\<CR>else:'
+ indent.should == 0
+ end
+
+ it "aligns to the preceeding 'if' block" do
+ vim.feedkeys 'ifor x in "abc":\<CR>if True:\<CR>pass\<CR>else:'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when using parens and control statements" do
+ it "avoids ambiguity by using extra indentation" do
+ vim.feedkeys 'iif (111 and\<CR>'
+ if shiftwidth == 4
+ indent.should == shiftwidth * 2
+ else
+ indent.should == 4
+ end
+ vim.feedkeys '222):\<CR>'
+ indent.should == shiftwidth
+ vim.feedkeys 'pass\<CR>'
+ indent.should == 0
+ end
+
+ it "still aligns parens properly if not ambiguous" do
+ vim.feedkeys 'iwhile (111 and\<CR>'
+ indent.should == 7
+ vim.feedkeys '222):\<CR>'
+ indent.should == shiftwidth
+ vim.feedkeys 'pass\<CR>'
+ indent.should == 0
+ end
+
+ it "handles nested expressions (Flake8's E127)" do
+ vim.feedkeys 'i[\<CR>x for x in foo\<CR>if (\<CR>'
+ indent.should == shiftwidth * 2
+ end
+
+ it "still handles multiple parens correctly" do
+ vim.feedkeys 'iif (111 and (222 and 333\<CR>'
+ indent.should == 13
+ vim.feedkeys 'and 444\<CR>'
+ indent.should == 13
+ vim.feedkeys ')\<CR>'
+ if shiftwidth == 4
+ indent.should == shiftwidth * 2
+ else
+ indent.should == 4
+ end
+ vim.feedkeys 'and 555):\<CR>'
+ indent.should == shiftwidth
+ vim.feedkeys 'pass\<CR>'
+ indent.should == 0
+ end
+ end
+
+ describe "when a line breaks with a manual '\\'" do
+ it "indents shiftwidth spaces on normal line" do
+ vim.feedkeys 'ivalue = test + \\\\\<CR>'
+ indent.should == shiftwidth
+ end
+
+ it "indents 2x shiftwidth spaces for control structures" do
+ vim.feedkeys 'iif somevalue == xyz and \\\\\<CR>'
+ indent.should == shiftwidth * 2
+ end
+
+ it "indents relative to line above" do
+ vim.feedkeys 'i\<TAB>value = test + \\\\\<CR>'
+ indent.should == shiftwidth * 2
+ end
+ end
+
+ describe "when current line is dedented compared to previous line" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>if x:\<CR>y = True\<CR>\<ESC>' }
+ it "and current line has a valid indentation (Part 1)" do
+ vim.feedkeys '0i\<TAB>if y:'
+ proposed_indent.should == -1
+ end
+
+ it "and current line has a valid indentation (Part 2)" do
+ vim.feedkeys '0i\<TAB>\<TAB>if y:'
+ proposed_indent.should == -1
+ end
+
+ it "and current line has an invalid indentation" do
+ vim.feedkeys 'i while True:\<CR>'
+ indent.should == previous_indent + shiftwidth
+ end
+ end
+
+ describe "when current line is dedented compared to the last non-empty line" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>if x:\<CR>y = True\<CR>\<CR>\<ESC>' }
+ it "and current line has a valid indentation" do
+ vim.feedkeys '0i\<TAB>if y:'
+ proposed_indent.should == -1
+ end
+ end
+
+ describe "when an 'if' is followed by" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>if x:\<CR>' }
+ it "an elif, it lines up with the 'if'" do
+ vim.feedkeys 'elif y:'
+ indent.should == shiftwidth * 2
+ end
+
+ it "an 'else', it lines up with the 'if'" do
+ vim.feedkeys 'else:'
+ indent.should == shiftwidth * 2
+ end
+ end
+
+ describe "when an 'if' contains a try-except" do
+ before {
+ vim.feedkeys 'iif x:\<CR>try:\<CR>pass\<CR>except:\<CR>pass\<CR>'
+ indent.should == shiftwidth
+ }
+ it "an 'else' should be indented to the try" do
+ vim.feedkeys 'else:'
+ indent.should == shiftwidth
+ proposed_indent.should == shiftwidth
+ end
+ it "an 'else' should keep the indent of the 'if'" do
+ vim.feedkeys 'else:\<ESC><<'
+ indent.should == 0
+ proposed_indent.should == 0
+ end
+ end
+
+ describe "when a 'for' is followed by" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>for x in y:\<CR>' }
+ it "an 'else', it lines up with the 'for'" do
+ vim.feedkeys 'else:'
+ indent.should == shiftwidth * 2
+ end
+ end
+
+ describe "when an 'else' is followed by" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>else:\<CR>XXX\<CR>' }
+ it "a 'finally', it lines up with the 'else'" do
+ vim.feedkeys 'finally:'
+ indent.should == shiftwidth * 2
+ end
+ end
+
+
+ describe "when a 'try' is followed by" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>try:\<CR>' }
+ it "an 'except', it lines up with the 'try'" do
+ vim.feedkeys 'except:'
+ indent.should == shiftwidth * 2
+ end
+
+ it "an 'else', it lines up with the 'try'" do
+ vim.feedkeys 'else:'
+ indent.should == shiftwidth * 2
+ end
+
+ it "a 'finally', it lines up with the 'try'" do
+ vim.feedkeys 'finally:'
+ indent.should == shiftwidth * 2
+ end
+ end
+
+ describe "when an 'except' is followed by" do
+ before { vim.feedkeys 'i\<TAB>\<TAB>except:\<CR>' }
+ it "an 'else', it lines up with the 'except'" do
+ vim.feedkeys 'else:'
+ indent.should == shiftwidth * 2
+ end
+
+ it "another 'except', it lines up with the previous 'except'" do
+ vim.feedkeys 'except:'
+ indent.should == shiftwidth * 2
+ end
+
+ it "a 'finally', it lines up with the 'except'" do
+ vim.feedkeys 'finally:'
+ indent.should == shiftwidth * 2
+ end
+ end
+
+ describe "when an else is used inside of a nested if" do
+ before { vim.feedkeys 'iif foo:\<CR>if bar:\<CR>pass\<CR>' }
+ it "indents the else to the inner if" do
+ vim.feedkeys 'else:'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "when an else is used outside of a nested if" do
+ before { vim.feedkeys 'iif True:\<CR>if True:\<CR>pass\<CR>\<Esc>0' }
+ it "indents the else to the outer if" do
+ indent.should == 0
+ proposed_indent.should == shiftwidth
+
+ vim.feedkeys 'ielse:'
+ indent.should == 0
+ proposed_indent.should == 0
+ end
+ end
+
+ describe "when jedi-vim call signatures are used" do
+ before { vim.command 'syn match jediFunction "JEDI_CALL_SIGNATURE" keepend extend' }
+
+ it "ignores the call signature after a colon" do
+ vim.feedkeys 'iif True: JEDI_CALL_SIGNATURE\<CR>'
+ indent.should == shiftwidth
+ end
+
+ it "ignores the call signature after a function" do
+ vim.feedkeys 'idef f( JEDI_CALL_SIGNATURE\<CR>'
+ indent.should == shiftwidth
+ end
+ end
+ end
+
+ shared_examples_for "multiline strings" do
+ before(:each) {
+ # clear buffer
+ vim.normal 'gg"_dG'
+
+ # Insert two blank lines.
+ # The first line is a corner case in this plugin that would shadow the
+ # correct behaviour of other tests. Thus we explicitly jump to the first
+ # line when we require so.
+ vim.feedkeys 'i\<CR>\<CR>\<ESC>'
+ }
+
+ describe "when after an '(' that is followed by an unfinished string" do
+ before { vim.feedkeys 'itest("""' }
+
+ it "it indents the next line" do
+ vim.feedkeys '\<CR>'
+ 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\<CR>'
+ 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 '\<CR>'
+ expected_proposed, expected_indent = multiline_indent(0, shiftwidth)
+ proposed_indent.should == expected_proposed
+ indent.should == expected_indent
+ end
+ end
+
+ describe "when after assigning an indented unfinished string" do
+ before { vim.feedkeys 'i test = """' }
+
+ it "it indents the next line" do
+ vim.feedkeys '\<CR>'
+ expected_proposed, expected_indent = multiline_indent(4, shiftwidth + 4)
+ proposed_indent.should == expected_proposed
+ indent.should == expected_indent
+ end
+ end
+
+ describe "when after assigning an indented finished string" do
+ before { vim.feedkeys 'i test = ""' }
+
+ it "it does indent the next line" do
+ vim.feedkeys '\<CR>'
+ indent.should == 4
+ end
+
+ it "and writing a new string, it does indent the next line" do
+ vim.feedkeys '\<CR>""'
+ indent.should == 4
+ end
+ end
+
+ describe "when after a docstring" do
+ it "it does indent the next line to the docstring" do
+ vim.feedkeys 'i """\<CR>'
+ indent.should == 4
+ proposed_indent.should == 4
+ end
+
+ it "indents the closing docstring quotes" do
+ vim.feedkeys 'i """\<CR>\<CR>"""'
+ indent.should == 4
+ proposed_indent.should == 4
+ vim.echo('getline(3)').should == ' """'
+ end
+
+ it "indents non-matching docstring quotes" do
+ vim.feedkeys 'i """\<CR>\<Esc>'
+ vim.feedkeys "0C'''"
+ vim.echo('line(".")').should == "4"
+ vim.echo('getline(".")').should == "'''"
+ indent.should == 0
+ proposed_indent.should == -1
+ end
+ end
+
+ 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 '\<CR>'
+ indent.should == 4
+ proposed_indent.should == 4
+ end
+ end
+
+ describe "when breaking a string after opening parenthesis" do
+ before { vim.feedkeys 'i foo("""bar\<Left>\<Left>\<Left>' }
+ it "it does indent the next line as after an opening multistring" do
+ vim.feedkeys '\<CR>'
+ _, expected_indent = multiline_indent(4, 4 + shiftwidth)
+ indent.should == expected_indent
+ proposed_indent.should == -1
+
+ # it keeps the indent after an empty line
+ vim.feedkeys '\<CR>'
+ proposed_indent, expected_indent = multiline_indent(4, 4 + shiftwidth)
+ indent.should == expected_indent
+ proposed_indent.should == proposed_indent
+
+ # it keeps the indent of nonblank above
+ vim.feedkeys '\<End>\<CR>'
+ proposed_indent, expected_indent = multiline_indent(4, 4 + shiftwidth)
+ indent.should == expected_indent
+ proposed_indent.should == proposed_indent
+
+ # it keeps the indent of nonblank above before an empty line
+ vim.feedkeys '\<CR>'
+ proposed_indent, expected_indent = multiline_indent(4, 4 + shiftwidth)
+ indent.should == expected_indent
+ proposed_indent.should == proposed_indent
+ end
+ end
+ end
+
+ SUITE_SHIFTWIDTHS = [4, 3]
+ SUITE_HANG_CLOSINGS = [false, true]
+
+ SUITE_SHIFTWIDTHS.each do |sw|
+ describe "vim when using width of #{sw}" do
+ before {
+ vim.command("set sw=#{sw} ts=#{sw} sts=#{sw} et")
+ }
+ it "sets shiftwidth to #{sw}" do
+ shiftwidth.should == sw
+ end
+
+ SUITE_HANG_CLOSINGS.each do |hc|
+ describe "vim when hang_closing is set to #{hc}" do
+ before {
+ set_hang_closing hc
+ }
+ it "sets hang_closing to #{hc}" do
+ hang_closing.should == !!hc
+ end
+
+ it_behaves_like "vim"
+ end
+ end
+ end
+ end
+
+ describe "vim when not using python_pep8_indent_multiline_string" do
+ before {
+ 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
+
+ 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 "Handles far away opening parens" do
+ before { vim.feedkeys '\<ESC>ggdGifrom foo import (' }
+
+ it "indents by one level" do
+ vim.feedkeys '\<CR>'
+ proposed_indent.should == shiftwidth
+ end
+
+ it "indents by one level for 10 lines" do
+ vim.command('set paste | exe "norm 9o" | set nopaste')
+ vim.feedkeys '\<Esc>o'
+ indent.should == shiftwidth
+ end
+
+ it "indents by one level for 50 lines" do
+ vim.command('set paste | exe "norm 49o" | set nopaste')
+ vim.feedkeys '\<Esc>o'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "Handles far away opening square brackets" do
+ before { vim.feedkeys '\<ESC>ggdGibar = [' }
+
+ it "indents by one level" do
+ vim.feedkeys '\<CR>'
+ proposed_indent.should == shiftwidth
+ end
+
+ it "indents by one level for 10 lines" do
+ vim.command('set paste | exe "norm 9o" | set nopaste')
+ vim.feedkeys '\<Esc>o'
+ indent.should == shiftwidth
+ end
+
+ it "indents by one level for 100 lines" do
+ vim.command('set paste | exe "norm 99o" | set nopaste')
+ vim.feedkeys '\<Esc>o'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "Handles far away opening curly brackets" do
+ before { vim.feedkeys '\<ESC>ggdGijson = {' }
+
+ it "indents by one level" do
+ vim.feedkeys '\<CR>'
+ vim.feedkeys '\<Esc>o'
+ proposed_indent.should == shiftwidth
+ end
+
+ it "indents by one level for 10 lines" do
+ vim.command('set paste | exe "norm 9o" | set nopaste')
+ vim.feedkeys '\<Esc>o'
+ indent.should == shiftwidth
+ end
+
+ it "indents by one level for 1000 lines" do
+ vim.command('set paste | exe "norm 999o" | set nopaste')
+ vim.feedkeys '\<Esc>o'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "Compact multiline dict" do
+ before { vim.feedkeys '\<ESC>ggdGid = {"one": 1,' }
+
+ it "gets indented correctly" do
+ vim.feedkeys '\<CR>'
+ proposed_indent.should == 5
+
+ vim.feedkeys '"two": 2}'
+ proposed_indent.should == 5
+
+ vim.feedkeys '\<CR>'
+ proposed_indent.should == 0
+ end
+ end
+
+ describe "Using O" do
+ before {
+ vim.feedkeys '\<ESC>ggdG'
+ vim.feedkeys 'iif foo:\<CR>'
+ }
+
+ it "respects autoindent" do
+ vim.feedkeys '1\<CR>\<CR>'
+ indent.should == shiftwidth
+ vim.feedkeys '\<Esc>ko'
+ indent.should == shiftwidth
+ vim.feedkeys '\<Esc>kO'
+ indent.should == shiftwidth
+ # Uses/keeps indent from line above
+ vim.feedkeys '\<Esc>i2\<Esc>O'
+ indent.should == shiftwidth
+ # Uses/keeps indent from line above
+ vim.feedkeys '\<Esc>j\<Esc>O'
+ indent.should == 0
+ end
+ end
+
+ describe "searchpairpos" do
+ before { vim.feedkeys '\<ESC>ggdG' }
+ it "handles nested parenthesis" do
+ vim.feedkeys 'iif foo.startswith("("):\<CR>'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "o within TODO" do
+ before {
+ vim.feedkeys '\<ESC>ggdG'
+ vim.feedkeys 'iif 1: # TODO\<Esc>'
+ # Assertion that we have a pythonTodo here.
+ vim.echo('synIDattr(synID(line("."), col("."), 0), "name")').should match 'pythonTodo'
+ }
+
+ it "respects autoindent" do
+ vim.feedkeys 'o'
+ indent.should == shiftwidth
+ end
+ end
+
+ describe "elif after else" do
+ before {
+ vim.feedkeys '\<ESC>ggdG'
+ }
+
+ it "is indented to the outer if" do
+ vim.feedkeys 'iif 1:\<CR>if 2:\<CR>pass\<CR>else:\<CR>pass\<CR>elif 3:\<Esc>'
+ indent.should == 0
+
+ vim.feedkeys '\<ESC>ggdG'
+ vim.feedkeys 'i if 1:\<CR>if 2:\<CR>pass\<CR>else:\<CR>pass\<CR>elif 3:\<Esc>'
+ indent.should == 4
+ end
+ end
+
+ describe "elif after two ifs" do
+ before {
+ vim.feedkeys '\<ESC>ggdG'
+ }
+
+ it "keeps its indent to the outer if" do
+ vim.feedkeys 'iif 1:\<CR>if 2:\<CR>pass\<CR>elif 3:\<CR>pass\<CR>'
+ indent.should == 4
+ vim.feedkeys '\<Esc>'
+ indent.should == 0
+ proposed_indent.should == shiftwidth
+ vim.feedkeys 'ielif 4:'
+ indent.should == 0
+ proposed_indent.should == 0
+ vim.feedkeys '\<CR>'
+ indent.should == 4
+ proposed_indent.should == 4
+ end
+ end
--- /dev/null
+ #!/bin/sh
+
+ set -ex
+
+ rm -f .coverage_covimerage
+ export PYTHON_PEP8_INDENT_TEST_PROFILE_BASE=/tmp/vim-python-pep8-profile
+
+ Xvfb :99 2>/dev/null >&2 &
+ export DISPLAY=:99
+
+ export VIMRUNNER_REUSE_SERVER=1
+
+ ret=0
+ for file in ./spec/indent/*_spec.rb; do
+ # shellcheck disable=SC2086
+ bundle exec rspec "$file" $RSPEC_OPTIONS || ret=1
+
+ for p in "${PYTHON_PEP8_INDENT_TEST_PROFILE_BASE}".*; do
+ covimerage write_coverage --append "$p"
+ rm "$p"
+ covimerage report -m
+ done
+ done
+ exit $ret
--- /dev/null
+ require 'vimrunner'
+ require 'vimrunner/rspec'
+ require 'vimrunner/server'
+
+ # Explicitly enable usage of "should".
+ RSpec.configure do |config|
+ config.expect_with(:rspec) { |c| c.syntax = :should }
+ end
+
+ Vimrunner::RSpec.configure do |config|
+ # Use a single Vim instance for the test suite. Set to false to use an
+ # instance per test (slower, but can be easier to manage).
+ # This requires using gvim, otherwise it hangs after a few tests.
+ config.reuse_server = ENV['VIMRUNNER_REUSE_SERVER'] == '1' ? true : false
+
+ config.start_vim do
+ exe = config.reuse_server ? Vimrunner::Platform.gvim : Vimrunner::Platform.vim
+ vimrc = File.expand_path("../vimrc", __FILE__)
+ vim = Vimrunner::Server.new(:executable => exe,
+ :vimrc => vimrc).start
+ # More friendly killing.
+ # Otherwise profiling information might not be written.
+ def vim.kill
+ normal(':qall!<CR>')
+
+ Timeout.timeout(5) do
+ sleep 0.1 while server.running?
+ end
+ end
+
+ plugin_path = File.expand_path('../..', __FILE__)
+ vim.command "set rtp^=#{plugin_path}"
+ vim.command "set filetype=python"
+
+ 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
+ def hang_closing
+ i = vim.echo("get(g:, 'python_pep8_indent_hang_closing', 0)").to_i
+ return (i != 0)
+ end
+ def set_hang_closing(value)
+ i = value ? 1 : 0
+ vim.command("let g:python_pep8_indent_hang_closing=#{i}")
+ end
+
+ vim
+ end
+ end
--- /dev/null
+ " vint: -ProhibitSetNoCompatible
+ set nocompatible
+
+ filetype plugin on
+ filetype indent on
+ syntax on
+
+ set noswapfile nobackup
+
+ " remove default ~/.vim directories to avoid loading plugins
+ set runtimepath-=~/.vim
+ set runtimepath-=~/.vim/after
+
+ let sfile = expand('<sfile>')
+ let plugin_path = fnamemodify(sfile, ':p:h:h')
+ exe 'set runtimepath^='.plugin_path
+
+ if !empty($PYTHON_PEP8_INDENT_TEST_PROFILE_BASE)
+ execute printf('profile start %s.%s',
+ \ $PYTHON_PEP8_INDENT_TEST_PROFILE_BASE, getpid())
+ execute 'profile! file '. plugin_path . '/indent/python.vim'
+ endif