1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-04-14 07:09:11 +08:00

chore(lang#python): use bundle python plugins

This commit is contained in:
wsdjeg 2022-05-28 23:21:55 +08:00
parent f3093ccdcf
commit 5d7bd47ccf
33 changed files with 3922 additions and 3 deletions

View File

@ -110,11 +110,11 @@ function! SpaceVim#layers#lang#python#plugins() abort
endif
call add(plugins, ['heavenshell/vim-pydocstring',
\ { 'on_cmd' : 'Pydocstring'}])
call add(plugins, ['Vimjas/vim-python-pep8-indent',
call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-python-pep8-indent',
\ { 'on_ft' : 'python'}])
call add(plugins, ['jeetsukumaran/vim-pythonsense',
call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-pythonsense',
\ { 'on_ft' : 'python'}])
call add(plugins, ['alfredodeza/coveragepy.vim',
call add(plugins, [g:_spacevim_root_dir . 'bundle/coveragepy.vim',
\ { 'merged' : 0}])
call add(plugins, [g:_spacevim_root_dir . 'bundle/vim-virtualenv',
\ { 'merged' : 0}])

18
bundle/README.md vendored
View File

@ -2,6 +2,15 @@
In `bundle/` directory, there are two kinds of plugins: forked plugins without changes and forked plugins which have been changed.
<!-- vim-markdown-toc GFM -->
- [Changed plugin:](#changed-plugin)
- [No changed plugins](#no-changed-plugins)
- [`lang#ruby` layer](#langruby-layer)
- [`lang#python` layer](#langpython-layer)
<!-- vim-markdown-toc -->
### Changed plugin:
- `vim-bookmarks`: based on [`MattesGroeger/vim-bookmarks@3adeae1`](https://github.com/MattesGroeger/vim-bookmarks/commit/3adeae10639edcba29ea80dafa1c58cf545cb80e)
@ -33,4 +42,13 @@ In `bundle/` directory, there are two kinds of plugins: forked plugins without c
- [vim-autohotkey@6bf1e71](https://github.com/wsdjeg/vim-autohotkey/tree/6bf1e718c73cad22caad3ecd8c4db96db05b37f7)
- [vim-cmake-syntax@bcc3a97a](https://github.com/pboettch/vim-cmake-syntax/tree/bcc3a97ab934f03e112becd4ce79286793152b47)
- [itchyny/calendar.vim@896360bfd](https://github.com/itchyny/calendar.vim/tree/896360bfd9d5347b2726dd247df2d2cbdb8cf1d6)
#### `lang#ruby` layer
- [vim-ruby@55335f261](https://github.com/vim-ruby/vim-ruby/tree/55335f2614f914b117f02995340886f409eddc02)
#### `lang#python` layer
- [jeetsukumaran/vim-pythonsense@9200a57](https://github.com/jeetsukumaran/vim-pythonsense/tree/9200a57629c904ed2ab8c9b2e8c5649d311794ba)
- [alfredodeza/coveragepy.vim@afcef30](https://github.com/alfredodeza/coveragepy.vim/tree/afcef301b723048c25250d2d539b9473a8e4f747)
- [Vimjas/vim-python-pep8-indent@60ba5e](https://github.com/Vimjas/vim-python-pep8-indent/tree/60ba5e11a61618c0344e2db190210145083c91f8)

1
bundle/coveragepy.vim/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/doc/tags

137
bundle/coveragepy.vim/README.rst vendored Normal file
View File

@ -0,0 +1,137 @@
coveragepy.vim
==============
A Vim plugin to help integrate Ned Batchelder's excellent ``coverage.py`` (see:
http://nedbatchelder.com/code/coverage/) tool into the editor.
Allows you to bring up a buffer with detailed information from a coverage
report command and mark each line in your source that is not being covered.
You can also use that buffer to navigate into files that have reported missing
statements and display the missed lines.
Optionally, you can also hide or display the marks as you make progress.
Showing a Coverage Report
-------------------------
.. image:: https://github.com/alfredodeza/coveragepy.vim/raw/master/extras/session.png
Installation
------------
If you have Tim Pope's Pathogen you only need to place the plugin directory
inside your bundle dir, otherwise it is a single file that should go into::
vim/ftplugin/python/
Usage
=====
This plugin provides a single command: ``Coveragepy`` that accepts a few
arguments. Each argument and its usage is described in detail below.
Whenever a ``report`` or a ``session`` is called the cursor will be placed on
the first uncovered line if any.
``report``
--------
The main action is performed with this command (same as with ``coverage.py``) and
when it runs it calls ``coverage.py`` and loads the information into a split
buffer.
It also collects all the information needed to be able to mark all lines from
files that have reported missing coverage statements. To run this command do::
:Coveragepy report
``session``
-----------
This argument toggles the reporting buffer (closes it is open or opens if it is
not already there). Makes sense to map it directly as a shortcut as it is
completely toggable.
A big plus on this session buffer is that you can navigate through the list of
reported paths (it actually circles through!) with the arrow keys or j and k.
If you want to navigate to a reported path that has missing lines just hit
Enter (or Return) and the plugin will go to the previous window and open that
selected file and then display the missing lines.
``show``
--------
Shows or hides the actual Vim `sign` marks that display which lines are missing
coverage. It is implemented as a toggable argument so it will do the opposite
of what is currently shown.
It is useful to be able to hide these if you are already aware about the lines
that need to be covered and do not want to be visually disturbed by the signs.
``refresh``
--------
Reloads and parses coverage data similar to ``:Coveragepy report`` but does
not open report window and only updates line coverage marks (displayed by
``show`` command above).
``version``
-----------
Displays the current plugin version
sign configuration
------------------
By default, the character used for identifying uncovered lines is '^', but this
can be overridden with the following configuration flag::
g:coveragepy_uncovered_sign
In a ``.vimrc`` file or locally in a buffer, changing this value to `-` would
look like::
let g:coveragepy_uncovered_sign = '-'
Executable auto-detection
-------------------------
The plugin tries to detect the right executable name for ``coverage`` in the
following order of precedence:
* ``coverage``
* ``python-coverage``
* ``python3-coverage``
* ``python2-coverage``
* ``python2.7-coverage``
If none of the above match or if multiple exist but are not suitable for the
project (e.g. both python2 and python3 exist), it is possible to force it by
using the following configuration flag::
let g:coveragepy_executable = "/path/to/prefered/coverage"
License
-------
MIT
Copyright (c) 2011 Alfredo Deza <alfredodeza [at] gmail [dot] com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

121
bundle/coveragepy.vim/doc/coveragepy.txt vendored Normal file
View File

@ -0,0 +1,121 @@
*coveragepy.txt* Runs coverage.py within vim and displays and interactive
buffer that allows to mark missing statements.
==============================================================================
CONTENTS *Coveragepy-contents*
1. Intro ............................ |CoveragepyIntro|
2. Usage ............................ |CoveragepyUsage|
3. report .......................... |CoveragepyReport|
4. Session .................................. |Session|
5. Show .................................. |ShowNoshow|
6. License ........................ |CoveragepyLicense|
7. Bugs .............................. |CoveragepyBugs|
8. Credits ........................ |CoveragepyCredits|
==============================================================================
1. Intro *CoveragepyIntro*
A Vim plugin to help integrate Ned Batchelder's excellent ``coverage.py`` (see:
http://nedbatchelder.com/code/coverage/) tool into the editor.
Allows you to bring up a buffer with detailed information from a coverage
report command and mark each line in your source that is not being covered.
You can also use that buffer to navigate into files that have reported missing
statements and display the missed lines.
Optionally, you can also hide or display the marks as you make progress.
==============================================================================
2. Usage *CoveragepyUsage*
This plugin provides a single command::
Coveragepy
All arguments are able to be tab-completed.
For running tests the plugin provides 3 arguments with an optional one.
These arguments are::
report
session
show
version
==============================================================================
3. Report *CoveragepyReport*
The main action is performed with this command (same as with ``coverage.py``)
and when it runs it calls ``coverage.py`` and loads the information into
a split buffer.
It also collects all the information needed to be able to mark all lines from
files that have reported missing coverage statements. To run this command do::
:Coveragepy report
==============================================================================
4. Session *Session*
This argument toggles the reporting buffer (closes it is open or opens if it is
not already there). Makes sense to map it directly as a shortcut as it is
completely toggable.
A big plus on this session buffer is that you can navigate through the list of
reported paths (it actually circles through!) with the arrow keys or j and k.
If you want to navigate to a reported path that has missing lines just hit
Enter (or Return) and the plugin will go to the previous window and open that
selected file and then display the missing lines.
==============================================================================
5. Show *Show*
Shows or hides the actual Vim `sign` marks that display which lines are missing
coverage.
The ``show`` command is implemented as a toggable argument, so whenever you
call it and the signs are showing it will hide them, if they are not it will
show them.
It is useful to be able to hide these if you are already aware about the lines
that need to be covered and do not want to be visually disturbed by the signs.
==============================================================================
5. License *CoveragepyLicense*
MIT
Copyright (c) 2011 Alfredo Deza <alfredodeza [at] gmail [dot] com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
==============================================================================
6. Bugs *CoveragepyBugs*
If you find a bug please post it on the issue tracker:
https://github.com/alfredodeza/coveragepy.vim/issues
==============================================================================
7. Credits *CoveragepyCredits*
Thanks to Ned Batchelder for the awesome ``coverage.py`` tool, continuously
making a huge difference in my day to day testing.
==============================================================================

BIN
bundle/coveragepy.vim/extras/session.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View File

@ -0,0 +1,387 @@
" File: coveragepy.vim
" Description: Displays coverage reports from Ned Batchelder's excellent
" coverage.py tool
" (see: http://nedbatchelder.com/code/coverage )
" Maintainer: Alfredo Deza <alfredodeza AT gmail.com>
" License: MIT
"============================================================================
if exists("g:loaded_coveragepy") || &cp
finish
endif
function! s:HasCoverage() abort
if (g:coveragepy_executable != "")
return
endif
let executable_list = ["coverage", "python-coverage", "python3-coverage", "python2-coverage", "python2.7-coverage"]
for executable_name in executable_list
if (executable(executable_name) == 1)
let g:coveragepy_executable = executable_name
break
endif
endfor
if (g:coveragepy_executable == "")
echoerr("This plugin needs coverage.py installed and accessible")
endif
endfunction
" Global variables for registering next/previous error
let g:coveragepy_last_session = ""
let g:coveragepy_marks = []
let g:coveragepy_session_map = {}
let g:coveragepy_executable = ""
function! s:ToggleSigns()
if exists("b:coveragepy_is_displaying") && b:coveragepy_is_displaying
call s:ClearSigns()
let b:coveragepy_is_displaying = 0
else
call s:HighlightMissing()
endif
endfunction
function! s:CoveragepySyntax() abort
let b:current_syntax = 'Coveragepy'
syn match CoveragepyTitleDecoration "\v\-{2,}"
syn match CoveragepyHeaders '\v(^Name\s+|\s*Stmts\s*|\s*Miss\s+|Cover|Missing$)'
syn match CoveragepyDelimiter "\v^(\-\-)\s+"
syn match CoveragepyPercent "\v(\d+\%\s+)"
syn match CoveragepyLineNumbers "\v(\s*\d+,|\d+-\d+,|\d+-\d+$|\d+$)"
hi def link CoveragepyFiles Number
hi def link CoveragepyHeaders Comment
hi def link CoveragepyTitleDecoration Keyword
hi def link CoveragepyDelimiter Comment
hi def link CoveragepyPercent Boolean
hi def link CoveragepyLineNumbers Error
endfunction
function! s:Echo(msg, ...) abort
redraw!
let x=&ruler | let y=&showcmd
set noruler noshowcmd
if (a:0 == 1)
echo a:msg
else
echohl WarningMsg | echo a:msg | echohl None
endif
let &ruler=x | let &showcmd=y
endfun
function! s:FindCoverage() abort
let found = findfile(".coverage", ".;")
if (found !~ '.coverage')
return ""
endif
" Return the actual directory where .coverage is found
return fnamemodify(found, ":h")
endfunction
function! s:ClearSigns() abort
execute("sign unplace * group=uncovered buffer=".bufnr('%'))
execute("sign unplace * group=branchuncovered buffer=".bufnr('%'))
endfunction
function! s:SetHighlight()
if exists('g:coveragepy_uncovered_sign')
let text = g:coveragepy_uncovered_sign
else
let text = '^'
endif
highlight default NoCoverage ctermfg=red guifg=#ef0000
highlight default NoBranchCoverage ctermfg=yellow guifg=#ebef00
execute 'sign define uncovered text=' . text . ' texthl=NoCoverage'
execute 'sign define branchuncovered text=' . text . ' texthl=NoBranchCoverage'
endfunction
function! s:HighlightMissing() abort
call s:SetHighlight()
let b:coveragepy_is_displaying = 1
if (g:coveragepy_session_map == {})
call s:CoveragepyReport()
endif
call s:ClearSigns()
let current_buffer_py = matchlist(expand("%:p"), '\v(.*)(.py)')[0]
let current_buffer = matchlist(expand("%:p"), '\v(.*)(.py)')[1]
for path in keys(g:coveragepy_session_map)
if (current_buffer =~ path) || (current_buffer_py =~ path)
for position in g:coveragepy_session_map[path]
execute(":sign place ". position ." line=". position ." group=uncovered name=uncovered buffer=".bufnr("%"))
endfor
for position in g:coveragepy_session_map['BRANCH' . path]
execute(":sign place ". position ." line=". position ." group=branchuncovered name=branchuncovered buffer=".bufnr("%"))
endfor
" FIXME: I had to comment this out because it was no longer correct
" after adding branch support
"execute g:coveragepy_session_map[path][0]
redraw!
return
endif
endfor
call s:Echo("Coveragepy ==> 100% covered", 1)
endfunction
function! s:Strip(input_string) abort
return split(a:input_string, " ")[0]
endfunction
function! s:Roulette(direction) abort
let orig_line = line('.')
let last_line = line('$') - 3
" if for some reason there is not enough
" coverage output return
if last_line < 3
return
endif
" Move to the line we need
let move_to = orig_line + a:direction
if move_to > last_line
let move_to = 3
exe move_to
elseif (move_to < 3) && (a:direction == -1)
let move_to = last_line
exe move_to
elseif (move_to < 3) && (a:direction == 1)
let move_to = 3
exe move_to
else
exe move_to
endif
if move_to == 1
let _num = move_to
else
let _num = move_to - 1
endif
endfunction
function! s:CoveragepyReport() abort
" Run a report, ignore errors and show missing lines,
" which is what we are interested after all :)
let original_dir = getcwd()
" First try to see if we actually have a .coverage file
" to work with
let has_coverage = s:FindCoverage()
if (has_coverage == "")
return 0
else
" set the original directory path
" as a global
let g:coveragepy_path = has_coverage
" change dir to where coverage is
" and do all the magic we need
exe "cd " . has_coverage
call s:ClearSigns()
let g:coveragepy_last_session = ""
" Allow for rcfile
if exists("g:coveragepy_rcfile")
let s:coveragepy_rcfile=" --rcfile=".resolve(expand(g:coveragepy_rcfile))
else
let s:coveragepy_rcfile=""
endif
let cmd = g:coveragepy_executable." report -m -i".s:coveragepy_rcfile
let out = system(cmd)
let g:coveragepy_last_session = out
call s:ReportParse()
" Finally get back to where we initially where
exe "cd " . original_dir
return 1
endif
endfunction
function! s:ReportParse() abort
" After coverage runs, parse the content so we can get
" line numbers mapped to files
let path_to_lines = {}
for line in split(g:coveragepy_last_session, '\n')
if (line =~ '\v(\s*\d+,|\d+-\d+,|\d+-\d+$|\d+$)') && line !~ '\v(100\%)'
let path = split(line, ' ')[0]
let match_split = split(line, '%')
let line_nos = match_split[-1]
let all_line_nos = s:LineNumberParse(line_nos)
let all_branch_line_nos = s:BranchLineNumberParse(line_nos)
let path_to_lines[path] = all_line_nos
let path_to_lines['BRANCH' . path] = all_branch_line_nos
endif
endfor
let g:coveragepy_session_map = path_to_lines
endfunction
function! s:BranchLineNumberParse(numbers) abort
" Line numbers will come with a possible comma in them
" and lots of extra space. Let's remove them and strip them
let parsed_list = []
let splitted = split(a:numbers, ',')
for line_no in splitted
" only add numbers that are branch-coverage numbers
if len(split(line_no, '->')) > 1
if line_no =~ '->-'
let split_char = '->-'
else
let split_char = '->'
endif
if line_no =~ '-'
let split_nos = split(line_no, split_char)
let first = s:Strip(split_nos[0])
call add(parsed_list, first)
else
call add(parsed_list, s:Strip(line_no))
endif
endif
endfor
return parsed_list
endfunction
function! s:LineNumberParse(numbers) abort
" Line numbers will come with a possible comma in them
" and lots of extra space. Let's remove them and strip them
let parsed_list = []
let splitted = split(a:numbers, ',')
for line_no in splitted
" only add numbers that are not branch-coverage numbers
if len(split(line_no, '->')) == 1
if line_no =~ '-'
let split_nos = split(line_no, '-')
let first = s:Strip(split_nos[0])
let second = s:Strip(split_nos[1])
for range_no in range(first, second)
call add(parsed_list, range_no)
endfor
else
call add(parsed_list, s:Strip(line_no))
endif
endif
endfor
return parsed_list
endfunction
function! s:ClearAll() abort
let bufferL = ['LastSession.coveragepy']
for b in bufferL
let _window = bufwinnr(b)
if (_window != -1)
silent! execute _window . 'wincmd w'
silent! execute 'q'
endif
endfor
endfunction
function! s:LastSession() abort
call s:ClearAll()
let winnrback = bufwinnr(expand("%"))
if (len(g:coveragepy_last_session) == 0)
call s:CoveragepyReport()
endif
let winnr = bufwinnr('LastSession.coveragepy')
silent! execute winnr < 0 ? 'botright new ' . 'LastSession.coveragepy' : winnr . 'wincmd w'
setlocal buftype=nowrite bufhidden=wipe nobuflisted noswapfile nowrap number filetype=coveragepy
let session = split(g:coveragepy_last_session, '\n')
call append(0, session)
silent! execute 'resize ' . line('$')
silent! execute 'normal gg'
silent! execute 'nnoremap <silent> <buffer> q :q! <CR>'
nnoremap <silent><script> <buffer> <C-n> :call <sid>Roulette(1)<CR>
nnoremap <silent><script> <buffer> <down> :call <sid>Roulette(1)<CR>
nnoremap <silent><script> <buffer> j :call <sid>Roulette(1)<CR>
nnoremap <silent><script> <buffer> <C-p> :call <sid>Roulette(-1)<CR>
nnoremap <silent><script> <buffer> <up> :call <sid>Roulette(-1)<CR>
nnoremap <silent><script> <buffer> k :call <sid>Roulette(-1)<CR>
nnoremap <silent> <buffer> <Enter> :call <sid>OpenBuffer()<CR>
call s:CoveragepySyntax()
exe winnrback . 'wincmd w'
endfunction
function! s:OpenBuffer() abort
let raw_path = split(getline('.'), ' ')[0] . '.py'
" newer coverage versions use the .py extension, previously it
" didn't.
let path = split(raw_path, '.py')[0] . '.py'
let absolute_path = g:coveragepy_path . '/' . path
if filereadable(absolute_path)
execute 'wincmd p'
silent! execute ":e " . absolute_path
call s:HighlightMissing()
execute 'wincmd p'
call s:CoveragepySyntax()
else
call s:Echo("Could not load file: " . path)
endif
endfunction
function! s:Version() abort
call s:Echo("coveragepy version 1.1dev", 1)
endfunction
function! s:Completion(ArgLead, CmdLine, CursorPos) abort
let actions = "report\nshow\nnoshow\nrefresh\nsession\n"
let extras = "version\n"
return actions . extras
endfunction
function! s:Proxy(action, ...) abort
" Make sure that if we are called, we have coverage installed
call s:HasCoverage()
if (a:action == "show")
call s:ToggleSigns()
elseif (a:action == "noshow")
call s:ClearSigns()
elseif (a:action == "refresh")
call s:ClearAll()
let g:coveragepy_session_map = {}
call s:HighlightMissing()
elseif (a:action == "session")
let winnr = bufwinnr('LastSession.coveragepy')
if (winnr != -1)
silent! execute 'wincmd b'
silent! execute 'q'
return
else
call s:LastSession()
endif
elseif (a:action == "report")
let report = s:CoveragepyReport()
if report == 1
call s:LastSession()
call s:HighlightMissing()
else
call s:Echo("No .coverage was found in current or parent dirs")
endif
elseif (a:action == "version")
call s:Version()
endif
endfunction
command! -nargs=+ -complete=custom,s:Completion Coveragepy call s:Proxy(<f-args>)

View File

@ -0,0 +1,37 @@
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

View File

@ -0,0 +1,7 @@
[run]
plugins = covimerage
data_file = .coverage_covimerage
source = indent/python.vim
[report]
include = indent/python.vim

View File

@ -0,0 +1,2 @@
*
!Gemfile

View File

@ -0,0 +1,3 @@
.*.swp
.coverage_covimerage
Gemfile.lock

View File

@ -0,0 +1,37 @@
How To Contribute
=================
``vim-python-pep8-indent`` is always open for suggestions and contributions by generous developers.
Ive 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

View File

@ -0,0 +1,121 @@
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.

View File

@ -0,0 +1,24 @@
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"]

3
bundle/vim-python-pep8-indent/Gemfile vendored Normal file
View File

@ -0,0 +1,3 @@
source 'https://rubygems.org'
gem "vimrunner", "0.3.4"
gem "rspec"

25
bundle/vim-python-pep8-indent/Makefile vendored Normal file
View File

@ -0,0 +1,25 @@
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

169
bundle/vim-python-pep8-indent/README.rst vendored Normal file
View File

@ -0,0 +1,169 @@
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 Klenovs python-mode_ ships its own version of this bundle.
Therefore, if you want to use this version specifically, youll have to disable python-modes using:
.. code-block:: vim
let g:pymode_indent = 0
License and Authorship
----------------------
This script is based on one from Vims official `script repo`_ that was *not* originally written by me.
Unfortunately the indentation was off by one character in one case and the script hasnt been updated since 2005.
Even more unfortunately, I wasnt 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 dont 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`_.
Id 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 Ill triage through bugs and pull requests but wont 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

View File

@ -0,0 +1,6 @@
version: '2'
services:
rspec:
build: .
volumes:
- .:/vim-python-pep8-indent

View File

@ -0,0 +1 @@
python.vim

View File

@ -0,0 +1,454 @@
" 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

View File

@ -0,0 +1,36 @@
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

View File

@ -0,0 +1,36 @@
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

View File

@ -0,0 +1,796 @@
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

View File

@ -0,0 +1,24 @@
#!/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

View File

@ -0,0 +1,70 @@
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

View File

@ -0,0 +1,22 @@
" 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

32
bundle/vim-pythonsense/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# svn crud
.svn*
# OS X / Darwin
.DS_Store*
# IDE/Editor Project Files
tags
# Python
*.py[cod]
*.so
*.egg
pip-log.txt
nosetests.xml
setup.cfg
*.mo
.installed.cfg
.coverage
.tox
*.egg-info/*
dist/*
build/*
eggs
parts
var
sdist/*
develop-eggs
lib
lib64
__pycache__
setuptools-*.egg

9
bundle/vim-pythonsense/LICENSE vendored Normal file
View File

@ -0,0 +1,9 @@
Copyright (C) 2018 Jeet Sukumaran.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

297
bundle/vim-pythonsense/README.md vendored Normal file
View File

@ -0,0 +1,297 @@
# Pythonsense
## Description
`Pythonsense` is a Vim plugin that provides [text objects](http://vimdoc.sourceforge.net/htmldoc/motion.html#object-select) and [motions](http://vimdoc.sourceforge.net/htmldoc/motion.html#object-motions) for Python classes, methods, functions, and doc strings.
### Python Text Objects
- "`ac`" : Outer class text object. This includes the entire class, including the header (class name declaration) and decorators, the class body, as well as a blank line if this is given after the class definition.
- "`ic`" : Inner class text object. This is the class body *only*, thus excluding the class name declaration line and any blank lines after the class definition.
- "`af`" : Outer function text object. This includes the entire function, including the header (function name declaration) and decorators, the function body, as well as a blank line if this is given after the function definition.
- "`if`" : Inner function text object. This is the function body *only*, thus excluding the function name declaration line and any blank lines after the function definition.
- "`ad`" : Outer docstring text object.
- "`id`" : Inner docstring text object.
The differences between inner and outer class and method/function text objects are illustrated by the following:
```
class OneRing(object): -----------------------------+
--------------------+ |
def __init__(self): | |
print("One ring to ...") | |
| |
def rule_them_all(self): | |
self.find_them() | |
| |
def find_them(self): ------------+ | |
a = [3, 7, 9, 1] ----+ | | |
self.bring_them(a) |- `if` |- `af` |- `ic` | - `ac`
self.bind_them("darkness") ----+ | | |
------------+ | |
def bring_them_all(self, a): | |
self.bind_them(a, "#000") | |
| |
def bind_them(self, a, c): | |
print("shadows lie.") --------------------+ |
-----------------------------+
```
### Python Object Motions
- "`]]`" : Move (forward) to the beginning of the *next* Python class.
- "`][`" : Move (forward) to the end of the *current* Python class.
- "`[[`" : Move (backward) to beginning of the *current* Python class (or beginning of the previous Python class if not currently in a class or already at the beginning of a class).
- "`[]`" : Move (backward) to end of the *previous* Python class.
- "`]m`" : Move (forward) to the beginning of the *next* Python method or function.
- "`]M`" : Move (forward) to the end of the *current* Python method or function.
- "`[m`" : Move (backward) to the beginning of the *current* Python method or function (or to the beginning of the previous method or function if not currently in a method/function or already at the beginning of a method/function).
- "`[M`" : Move (backward) to the end of the *previous* Python method or function.
Note that in Vim 8.0 or later, motions with these key-bindings are provided by default.
While in the most simplest of cases they converge with "Pythonsense" in operation, they result in very different behavior in more complex cases.
This difference ([discussed in detail below](#stock-vim-vs-pythonsense-motions)) basically comes down to "Pythonsense" providing semantically aware and contextual class-wise and method- or functionwise motions, while the stock Vim 8+ provides non-semantically aware indent-level based motions.
If you prefer the stock Vim 8.0+ motions to the ones overridden by "Pythonsense", then, as described in the [section on suppressing the default key mappings](#suppressing-the-key-mappings), just specify the following in your "~/.vimrc":
```
let g:is_pythonsense_suppress_motion_keymaps = 1
```
If you want *both* the stock Vim 8.0+ motions, then you can [activate alternate mappings](#activating-alternative-motion-key-mappings) to the Pythonsense semantic motions that do not hide the the stock Vim 8.0+ motions by specifying the following in your "~/.vimrc":
```
let g:is_pythonsense_alternate_motion_keymaps = 1
```
See [below](#activating-alternative-motion-key-mappings) for more details on these key mappings.
### Python Location Information
- "`g:`" : print (echo) current semantic location (e.g. ""``(class:)Foo > (def:)bar"``")
## Installation
### Manually (Example)
If you are using Vim 8.0 and above, I recommend you take advantage of the '[package](https://vi.stackexchange.com/a/9523/17621)' feature and do something like this:
$ cd ~/.vim
$ mkdir -p pack/import/start
$ cd pack/import/start && git clone git://github.com/jeetsukumaran/vim-pythonsense.git
You do not have to name the mid-level directory "import"; it can be anything else that makes sense to you for this plugin (e.g., "bundle", "jeetsukumaran", "python", etc.). For more information, see the documentation on "[packages](http://vimhelp.appspot.com/repeat.txt.html#packages)".
If you are using an older Vim version and do not want to upgrade, then you would be much, much, *much* better served by using a plugin manager like [Vim-Plug](https://github.com/junegunn/vim-plug) (described below).
However, if you are stuck with an old version of Vim and *really* do not or cannot use a plugin manager, then will need to manually copy the files to the corresponding locations in your home directory:
- "vim-pythonsense/after/ftplugin/python/pythonsense.vim" to: "~/.vim/after/ftplugin/python/pythonsense.vim"
- "vim-pythonsense/autoload/pythonsense.vim" to: "~/.vim/autoload/pythonsense.vim"
- "vim-pythonsense/ftplugin/python/pythonsense.vim" to: "~/.vim/ftplugin/python/pythonsense.vim"
### [pathogen.vim](https://github.com/tpope/vim-pathogen)
$ cd ~/.vim/bundle
$ git clone git://github.com/jeetsukumaran/vim-pythonsense.git
### [Vim-Plug](https://github.com/junegunn/vim-plug)
Add the line below into your "~/.vimrc":
Plug 'jeetsukumaran/vim-pythonsense'
and run:
:PlugInstall
More information on setting up [Vim-Plug](https://github.com/junegunn/vim-plug) to manage your plugins can be found [here](https://github.com/junegunn/vim-plug/wiki/tutorial).
### [Vundle](https://github.com/gmarik/vundle.git)
Add the line below into your "~/.vimrc":
Vundle 'jeetsukumaran/vim-pythonsense'
or run:
:VundleInstall jeetsukumaran/vim-pythonsense
## Customization
### [Changing the Key Mappings](#changing-the-key-mappings)
If you are unhappy with the default key-mappings you can define your own which will individually override the default ones.
However, instead of doing so in your "~/.vimrc", you need to do so in a file located in your "~/.vim/ftplugin/python/" directory, so that this key mappings are only enabled when editing a Python file.
Furthermore, you should make sure that you limit the key mapping to the "``<buffer>``" scope.
For example, to override yet replicate the default mappings you would define, the following in "~/.vim/ftplugin/python/pythonsense-custom.vim":
```
map <buffer> ac <Plug>(PythonsenseOuterClassTextObject)
map <buffer> ic <Plug>(PythonsenseInnerClassTextObject)
map <buffer> af <Plug>(PythonsenseOuterFunctionTextObject)
map <buffer> if <Plug>(PythonsenseInnerFunctionTextObject)
map <buffer> ad <Plug>(PythonsenseOuterDocStringTextObject)
map <buffer> id <Plug>(PythonsenseInnerDocStringTextObject)
map <buffer> ]] <Plug>(PythonsenseStartOfNextPythonClass)
map <buffer> ][ <Plug>(PythonsenseEndOfPythonClass)
map <buffer> [[ <Plug>(PythonsenseStartOfPythonClass)
map <buffer> [] <Plug>(PythonsenseEndOfPreviousPythonClass)
map <buffer> ]m <Plug>(PythonsenseStartOfNextPythonFunction)
map <buffer> ]M <Plug>(PythonsenseEndOfPythonFunction)
map <buffer> [m <Plug>(PythonsenseStartOfPythonFunction)
map <buffer> [M <Plug>(PythonsenseEndOfPreviousPythonFunction)
map <buffer> g: <Plug>(PythonsensePyWhere)
```
Note that you do not need to specify *all* the key mappings if you just want to customize a few.
Simply provide your own key mapping to each of the "``<Plug>``" mappings you want to override, and these will be respected, while any "``<Plug>``" maps that are not so explicitly bound will be assigned to the default key maps.
### [Suppressing the Key Mappings](#suppressing-the-key-mappings)
If you want to suppress some of the key mappings entirely (i.e., without providing your own to override the functionality), you can specify one or more of the following in your "~/.vimrc":
```
let g:is_pythonsense_suppress_object_keymaps = 1
let g:is_pythonsense_suppress_motion_keymaps = 1
let g:is_pythonsense_suppress_location_keymaps = 1
```
to selectively suppress just the text object, motion, or information key mapping sets by respectively.
You can also suppress *all* default key mappings by specifying the following in your "~/.vimrc":
```
let g:is_pythonsense_suppress_keymaps = 1
```
### [Activating Alternative Motion Key Mappings](#activating-alternative-motion-key-mappings)
As discussed above, "Pythonsense" overrides some native Vim 8.0+ motion key mappings, replacing the indent-based non-semantic ones with semantically-aware ones.
If you wish to have access to both types of motions, i.e., the stock Vim 8.0+ non-semantic indent-based block motions as well as the "Pythonsense" semantically aware class/method/function motions, then you can specify
```
let g:is_pythonsense_alternate_motion_keymaps = 1
```
in your "~/.vimrc".
Then (unless you specify you want all or motion key mappings suppressed entirely), the "Pythonsense" semantic class/method/function motions will be bound to the alternate keys below:
- "`]k`" : Move (forward) to the beginning of the *next* Python class.
- "`]K`" : Move (forward) to the end of the *current* Python class.
- "`[k`" : Move (backward) to beginning of the *current* Python class (or beginning of the previous Python class if not currently in a class or already at the beginning of a class).
- "`[K`" : Move (backward) to end of the *previous* Python class.
- "`]f`" : Move (forward) to the beginning of the *next* Python method or function.
- "`]F`" : Move (forward) to the end of the *current* Python method or function.
- "`[f`" : Move (backward) to the beginning of the *current* Python method or function (or to the beginning of the previous method or function if not currently in a method/function or already at the beginning of a method/function).
- "`[F`" : Move (backward) to the end of the *previous* Python method or function.
## Similar Projects
Most notable is the [vim-textobj-python](https://github.com/bps/vim-textobj-python) plugin.
Pythonsense distinguishes itself from this plugin in the following ways:
- Stand-alone and lightweight: does not rely on [vim-textobj-user](https://github.com/kana/vim-textobj-user/wiki).
- Also includes a docstring text object (originally from [here](https://pastebin.com/u/gfixler)).
- Crucially (for me!) selecting an outer class, method, or function also selects the blank line following. Without this, I find I always have to (a) add a new blank line after I have, e.g. pasted the class/method/function in its new location, and (b) delete the extra blank like in the old class/method/function location. With Pythonsense, this is taken care of automatically and the cleaner approach (for me!) not only saves time and key-strokes, but the oh-so-valuable headspace taskload. For a discussion of this issue, see [here](https://github.com/bps/vim-textobj-python/issues/17), and, in particular, [here](https://github.com/bps/vim-textobj-python/issues/17#issuecomment-187735637).
- Allows for successive invocations of text object selectors to select enclosing classes/functions.
- Handles nested classes/functions better (though with some failing corner cases).
- Seems to handle functions/classes with multiline arguments better.
- Handles folds better (opens them automatically if needed).
- Of course, also provides a docstring text object as well as the semantic location information helper ("``g:``").
## More Information on Text Objects
If you are reading all this and wondering what is a text object or why are text objects such a big deal, then the short answer is that text objects are like using a socket and ratchet as opposed to a monkey wrench: as long as you can find a fit, you cannot beat the efficiency, speed, and precision (as well as elegance and pure pleasure). For more details, you could look at some of the following:
- https://blog.carbonfive.com/2011/10/17/vim-text-objects-the-definitive-guide/
- http://owen.cymru/vim-text-objects-extend-vims-natural-language-2/
- http://codyveal.com/posts/vim-killer-features-part-1-text-objects/
## Acknowledgements
- The seed/inspiration (and basic logic) for the code for Python class and function text objects came from Nat Williams' "[pythontextobj.vim](https://github.com/natw/vim-pythontextobj)".
From a practical usage standpoint, principal differences are:
- The semantics of outer ("a") vs inner ("i") function/class. In the original, both "a" and "i" include the class/function definition line, but "i" extends to include the space after the class/function definition. I felt that "i" should exclude the class/function declaration heading line *and* the space after the definition (i.e., just the body of the class/function), while "a" should include both the class/function declaration heading line and a space after the definition.
- Handles nested cases better (or at all): multiple invocations of "``ic``", "``ac``", "``if``", "``af``", etc. grab successively enclosing classes/functions. The seed/inspiration for the logic for *this*, in turn, came from Michael Smith's "[vim-indent-object](http://github.com/michaeljsmith/vim-indent-object)".
- Code for the docstring text objects taken from the [pastebin shared by gfixler](https://pastebin.com/u/gfixler).
## Appendices
### [Stock Vim vs Pythonsense Motions](#stock-vim-vs-pythonsense-motions)
The stock Vim 8.0 "class" motions ("``]]``", "`[[`", etc.), find blocks that begin at the first column, *regardless* of whether or not these are class or function blocks, while its method/function motions ("``[m``", "``]m``", etc.) find all blocks at any indent *regardless* of whether or not these are class or function blocks.
In contrast, "Pythonsense" class motions work on finding *all* and *only* class definitions, *regardless* of their indent level, while its method/function motions work on finding *all* and *only* method/function definitions, *regardless* of their indent level.
The stock Vim 8.0 motions can thus be seen as non-semantically aware motions that target indent levels rather than Python classes/methods/functions, while the "Pythonsense" motions, on the other hand, can been seen as semantically aware motions that target Python classes/methods/functions rather than indent levels.
In a simple structured file (without even bare functions), e.g.,
```
class Foo1(object):
def __init__(self):
pass
def bar(self):
pass
def baz(self):
pass
class Foo2(object):
def __init__(self):
pass
def bar(self):
pass
def baz(self):
pass
```
both the stock Vim and "Pythonsense" motions work the same.
However, in a file like the following:
```
class Foo1(object):
def __init__(self):
pass
def bar(self):
pass
def baz(self):
pass
class Foo2(object):
class Foo2Exception1(Exception):
def __init__(self):
pass
class Foo2Exception2(Exception):
def __init__(self):
pass
def __init__(self):
pass
def bar(self):
pass
def baz(self):
pass
def fn1(a):
pass
def fn2(a):
pass
def fn3(a):
pass
```
the differences in behavior show up:
- With respect to the "``]]``"/"``[[``"/etc. motions, the stock Vim 8.0+ implementation will visit both top-level class and function definitions freely and miss the nested classes, while the "Pythonsense" implementation will visit *just* the class definitions exclusively and comprehensively (i.e., no functions and also include the nested classes in the visits).
- With respect to the "``[m``"/"``]m``"/etc. motions, the stock Vim 8.0+ implementation will visit all class, method, and function definitions in the file, whereas the "Pythonsense" implementation will visit *just* the method and function definitions exclusively (i.e., no classes).
## Copyright, License, and Warranty
Copyright (c) 2018 Jeet Sukumaran. All rights reserved.
This work is licensed under the terms of the (expat) MIT license.
See the file "LICENSE" (or https://opensource.org/licenses/MIT) for specific terms and details.

View File

@ -0,0 +1,58 @@
" Key Mappings to Override Default Ones
if ! get(g:, "is_pythonsense_suppress_keymaps", 0) && ! get(g:, "is_pythonsense_suppress_motion_keymaps", 0)
if get(g:, "is_pythonsense_alternate_motion_keymaps", 0)
if !hasmapto('<Plug>PythonsenseStartOfNextPythonClass')
map <buffer> ]k <Plug>(PythonsenseStartOfNextPythonClass)
endif
if !hasmapto('<Plug>PythonsenseEndOfPythonClass')
map <buffer> ]K <Plug>(PythonsenseEndOfPythonClass)
endif
if !hasmapto('<Plug>PythonsenseStartOfPythonClass')
map <buffer> [k <Plug>(PythonsenseStartOfPythonClass)
endif
if !hasmapto('<Plug>PythonsenseEndOfPreviousPythonClass')
map <buffer> [K <Plug>(PythonsenseEndOfPreviousPythonClass)
endif
if !hasmapto('<Plug>PythonsenseStartOfNextPythonFunction')
map <buffer> ]f <Plug>(PythonsenseStartOfNextPythonFunction)
endif
if !hasmapto('<Plug>PythonsenseEndOfPythonFunction')
map <buffer> ]F <Plug>(PythonsenseEndOfPythonFunction)
endif
if !hasmapto('<Plug>PythonsenseStartOfPythonFunction')
map <buffer> [f <Plug>(PythonsenseStartOfPythonFunction)
endif
if !hasmapto('<Plug>PythonsenseEndOfPreviousPythonFunction')
map <buffer> [F <Plug>(PythonsenseEndOfPreviousPythonFunction)
endif
else
if !hasmapto('<Plug>PythonsenseStartOfNextPythonClass')
map <buffer> ]] <Plug>(PythonsenseStartOfNextPythonClass)
endif
if !hasmapto('<Plug>PythonsenseEndOfPythonClass')
map <buffer> ][ <Plug>(PythonsenseEndOfPythonClass)
endif
if !hasmapto('<Plug>PythonsenseStartOfPythonClass')
map <buffer> [[ <Plug>(PythonsenseStartOfPythonClass)
endif
if !hasmapto('<Plug>PythonsenseEndOfPreviousPythonClass')
map <buffer> [] <Plug>(PythonsenseEndOfPreviousPythonClass)
endif
if !hasmapto('<Plug>PythonsenseStartOfNextPythonFunction')
map <buffer> ]m <Plug>(PythonsenseStartOfNextPythonFunction)
endif
if !hasmapto('<Plug>PythonsenseEndOfPythonFunction')
map <buffer> ]M <Plug>(PythonsenseEndOfPythonFunction)
endif
if !hasmapto('<Plug>PythonsenseStartOfPythonFunction')
map <buffer> [m <Plug>(PythonsenseStartOfPythonFunction)
endif
if !hasmapto('<Plug>PythonsenseEndOfPreviousPythonFunction')
map <buffer> [M <Plug>(PythonsenseEndOfPreviousPythonFunction)
endif
endif
endif

View File

@ -0,0 +1,699 @@
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" File: autoload/pythonsense.vim
" Author: Jeet Sukumaran
"
" Copyright: (C) 2018 Jeet Sukumaran
"
" License: Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
"
" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"
" Credits: - pythontextobj.vim by Nat Williams (https://github.com/natw/vim-pythontextobj)
" - chapa.vim by Alfredo Deza (https://github.com/alfredodeza/chapa.vim)
" - indentobj by Austin Taylor (https://github.com/austintaylor/vim-indentobject)
" - Python Docstring Text Objects by gfixler (https://pastebin.com/u/gfixler)
" - vim-indent-object by Michael Smith (http://github.com/michaeljsmith/vim-indent-object)
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Support Functions {{{1
function! pythonsense#trawl_search(pattern, start_line, fwd)
let current_line = a:start_line
let lastline = line('$')
if a:fwd
let stepvalue = 1
else
let stepvalue = -1
endif
while (current_line > 0 && current_line <= lastline)
let line_text = getline(current_line)
let m = match(line_text, a:pattern)
if m >= 0
return current_line
endif
let current_line = current_line + stepvalue
endwhile
return 0
endfunction
" }}}1
" Python (Statement) Text Objects {{{1
" Based on:
" - https://github.com/natw/vim-pythontextobj
" - https://github.com/alfredodeza/chapa.vim
" - https://github.com/austintaylor/vim-indentobject
" - http://github.com/michaeljsmith/vim-indent-object
" Select an object ("class"/"function")
let s:pythonsense_obj_start_line = -1
let s:pythonsense_obj_end_line = -1
function! pythonsense#select_named_object(obj_name, inner, range)
" Is this a new selection?
let new_vis = 0
let new_vis = new_vis || s:pythonsense_obj_start_line != a:range[0]
let new_vis = new_vis || s:pythonsense_obj_end_line != a:range[1]
" store current range
let s:pythonsense_obj_start_line = a:range[0]
let s:pythonsense_obj_end_line = a:range[1]
" Repeatedly increase the scope of the selection.
let cnt = 1
let scan_start_line = s:pythonsense_obj_start_line
while scan_start_line > 0
if getline(scan_start_line) !~ '^\s*$'
break
endif
let scan_start_line -= 1
endwhile
if scan_start_line == 0
return [-1, -1]
endif
let obj_max_indent_level = -1
while cnt > 0
let current_line_nr = scan_start_line
let [obj_start_line, obj_end_line] = pythonsense#get_object_line_range(a:obj_name, obj_max_indent_level, current_line_nr, s:pythonsense_obj_end_line, a:inner)
if obj_start_line == -1
return [-1, -1]
endif
let is_changed = 0
let is_changed = is_changed || s:pythonsense_obj_start_line != obj_start_line
let is_changed = is_changed || s:pythonsense_obj_end_line != obj_end_line
if new_vis
let is_changed = 1
endif
let s:pythonsense_obj_start_line = obj_start_line
let s:pythonsense_obj_end_line = obj_end_line
" If there was no change, then don't decrement the count (it didn't
" count because it didn't do anything).
if is_changed
let cnt = cnt - 1
else
" no change to selection;
" move to line above selection and try again
if scan_start_line == 0
return [-1, -1]
endif
let [min_indent, max_indent] = pythonsense#get_minmax_indent_count('\(class\|def\|async def\)', scan_start_line, obj_end_line)
if min_indent == 0
return [-1, -1]
endif
let obj_max_indent_level = min_indent - 1
let scan_start_line -= 1
endif
endwhile
" select range
if obj_end_line >= obj_start_line
exec obj_start_line
execute "normal! V" . obj_end_line . "G"
return [obj_start_line, obj_end_line]
else
return [-1, -1]
endif
endfunction
function! pythonsense#get_object_line_range(obj_name, obj_max_indent_level, line_range_start, line_range_end, inner)
" find definition line
let current_line_nr = a:line_range_start
if a:line_range_start == a:line_range_end
let search_past_decorator_last_line = line("$")
else
let search_past_decorator_last_line = a:line_range_end
endif
while current_line_nr <= search_past_decorator_last_line
if getline(current_line_nr) !~ '^\s*@.*$'
break
end
let current_line_nr += 1
endwhile
if current_line_nr > search_past_decorator_last_line
return [-1, -1]
endif
if current_line_nr > a:line_range_end
let effective_line_range_end = current_line_nr
else
let effective_line_range_end = a:line_range_end
endif
let obj_start_line = pythonsense#get_named_python_obj_start_line_nr(a:obj_name, a:obj_max_indent_level, current_line_nr, 0)
" no object definition line in file
if (! obj_start_line)
return [-1, -1]
endif
let obj_header_line = obj_start_line
let obj_header_indent = pythonsense#get_line_indent_count(obj_header_line)
if obj_header_indent > 0
let obj_header_indent -= 1
endif
let obj_end_line = pythonsense#get_object_end_line_nr(obj_start_line, obj_start_line, a:inner)
" in case of a class definition, the parentheses are optional
if a:obj_name == "def"
let pattern = '^[^#]*)[^#]*:\(\s*$\|\s*#.*$\)'
else
let pattern = '^[^#]*)\?[^#]*:\(\s*$\|\s*#.*$\)'
endif
if (a:inner)
" find class/function body
let inner_start_line = obj_start_line
while inner_start_line <= line('$')
if getline(inner_start_line) =~# pattern
break
endif
let inner_start_line += 1
endwhile
if inner_start_line <= line('$')
let obj_start_line = inner_start_line + 1
endif
else
" include decorators
let dec_line = pythonsense#get_start_decorators_line_nr(obj_start_line)
if dec_line < obj_start_line
let obj_start_line = dec_line
endif
endif
" This is an ugly hack to deal with (some) specially indented cases
" (especially when searching for a 'class' while inside a non-class member
" function, or when searching for a 'def' with nothing but class
" definitions above)
" Make sure there is no statement line with a lower indentation than the
" definition line in between the current line and the definition line
if a:obj_name == 'class'
let pattern = 'def\|async def'
else
let pattern = 'class'
endif
let pattern = '^\s*[^#]*\s*\k'
if pythonsense#is_statement_encountered_between_two_lines(pattern, obj_header_indent, obj_start_line, current_line_nr)
return [-1, -1]
endif
return [obj_start_line, obj_end_line]
endfunction
function! pythonsense#get_object_end_line_nr(obj_start, search_start, inner)
let obj_indent = pythonsense#get_line_indent_count(a:obj_start)
let obj_end = pythonsense#get_next_indent_line_nr(a:search_start, obj_indent)
if a:inner
let obj_end = prevnonblank(obj_end)
endif
return obj_end
endfunction
function! pythonsense#get_next_indent_line_nr(search_start, obj_indent)
let line = a:search_start
" Handle multiline definition
let saved_cursor = getcurpos()
call cursor(line, 0)
normal! f(%
let line = line('.')
call setpos('.', saved_cursor)
let lastline = line('$')
while (line > 0 && line <= lastline)
let line = line + 1
if (pythonsense#get_line_indent_count(line) <= a:obj_indent && getline(line) !~ '^\s*$')
return line - 1
endif
endwhile
return lastline
endfunction
function! pythonsense#get_start_decorators_line_nr(start)
" Returns the line of the first decorator line above the starting line,
" counting only decorators with the same level.
let start_line_indent = pythonsense#get_line_indent_count(a:start)
let last_non_blank_line = a:start
let current_line = a:start - 1
while current_line > 0
if getline(current_line) !~ '^\s*$'
if pythonsense#get_line_indent_count(current_line) != start_line_indent
break
endif
if getline(current_line) !~ '^\s*@'
break
endif
let last_non_blank_line = current_line
endif
let current_line -= 1
endwhile
return last_non_blank_line
endfunction
function! pythonsense#get_line_indent_count(line_nr)
if b:pythonsense_is_tab_indented
let indent_count = matchstrpos(getline(a:line_nr), '^\t\+')[2]
else
let indent_count = indent(a:line_nr)
endif
return indent_count
endfunction
function! pythonsense#get_minmax_indent_count(pattern, line_range_start, line_range_end)
let current_line = a:line_range_start
let min_indent_count = -1
let max_indent_count = -1
while current_line <= a:line_range_end && current_line <= line('$')
if getline(current_line) !~ '^\s*$'
if getline(current_line) =~# a:pattern
let current_indent = pythonsense#get_line_indent_count(current_line)
if min_indent_count < 0 || current_indent < min_indent_count
let min_indent_count = current_indent
endif
if max_indent_count < 0 || current_indent > max_indent_count
let max_indent_count = current_indent
endif
endif
endif
let current_line = current_line + 1
endwhile
return [min_indent_count, max_indent_count]
endfunction
function! pythonsense#is_statement_encountered_between_two_lines(pattern, max_indent, line_range_start, line_range_end)
let current_line = a:line_range_start
while current_line <= a:line_range_end && current_line <= line('$')
if getline(current_line) !~ '^\s*$'
if getline(current_line) =~# a:pattern
let current_indent = pythonsense#get_line_indent_count(current_line)
if a:max_indent > -1 && current_indent < a:max_indent
return 1
endif
endif
endif
let current_line += 1
endwhile
return 0
endfunction
function! pythonsense#get_indent_char()
if b:pythonsense_is_tab_indented
let indent_char = '\t'
else
let indent_char = " "
endif
return indent_char
endfunction
function! pythonsense#get_named_python_obj_start_line_nr(obj_name, obj_max_indent_level, start_line, fwd)
let lastline = line('$')
if a:fwd
let stepvalue = 1
else
let stepvalue = -1
endif
let indent_char = pythonsense#get_indent_char()
let current_line = a:start_line
while (current_line > 0 && current_line <= lastline)
" if getline(current_line) !~ '\(^\s*$\|^\s*[#@].*$\)'
if getline(current_line) !~ '^\s*$'
break
endif
let current_line = current_line + stepvalue
endwhile
if a:obj_max_indent_level > -1
let pattern = '^' . indent_char . '\{0,' . a:obj_max_indent_level . '}' . '\(class\|def\|async def\)'
else
let pattern = '^\s*' . '\(class\|def\|async def\)'
endif
if getline(current_line) =~# pattern
if getline(current_line) =~# a:obj_name
return current_line
endif
endif
let target_line_indent = pythonsense#get_line_indent_count(current_line) - 1
if target_line_indent < 0
let target_line_indent = 0
endif
if a:obj_max_indent_level > -1 && target_line_indent > a:obj_max_indent_level
let target_line_indent = a:obj_max_indent_level
endif
let max_indent = target_line_indent
while (current_line > 0 && current_line <= lastline)
let pattern = '^' . indent_char . '\{0,' . max_indent . '}' . '\(class\|def\|async def\)'
if getline(current_line) =~# pattern
if getline(current_line) =~# a:obj_name
return current_line
else
if a:obj_name != 'class' && pythonsense#get_line_indent_count(current_line) <= max_indent
" encountered a scope block at a lower indent level before
" encountering object definition
return 0
endif
endif
endif
" let m = match(getline(current_line), pattern)
" if m >= 0
" return current_line
" endif
let target_line_indent = pythonsense#get_line_indent_count(current_line) - 1
" Special case for multiline argument lines, with the parameter being
" indented one step more than the open def/class and the closing
" parenthesis.
let closing_pattern = '^' . indent_char . '*)'
if target_line_indent > 0 && target_line_indent < max_indent && getline(current_line) !~# closing_pattern
let max_indent = target_line_indent
endif
if a:obj_max_indent_level > -1 && target_line_indent > a:obj_max_indent_level
let target_line_indent = a:obj_max_indent_level
endif
let current_line = current_line + stepvalue
endwhile
return 0
endfunction
function! pythonsense#python_text_object(obj_name, inner, mode)
if a:mode == "o"
let lnrange = [line("."), line(".")]
else
let lnrange = [line("'<"), line("'>")]
endif
let nreps_left = 1 "v:count1
while nreps_left > 0
let lnrange = pythonsense#select_named_object(a:obj_name, a:inner, lnrange)
if lnrange[0] == -1
break
endif
let s:pythonsense_obj_start_line = lnrange[0]
let s:pythonsense_obj_end_line = lnrange[1]
let nreps_left -= 1
endwhile
if lnrange[0] != -1
if has("folding") && foldclosed(line('.')) != -1
" try
" execute "normal! zO"
" catch /E490/ " no fold found
" endtry
execute "normal! zO"
endif
" let s:pythonsense_obj_start_line = -1
" let s:pythonsense_obj_end_line = -1
" execute "normal! \<ESC>gv"
endif
endfunction
function! pythonsense#python_function_text_object(inner, mode)
call pythonsense#python_text_object('def', a:inner, a:mode)
endfunction
function! pythonsense#python_class_text_object(inner, mode)
call pythonsense#python_text_object('class', a:inner, a:mode)
endfunction
" }}}1
" Python Docstring Text Objects {{{1
" From: https://pastebin.com/u/gfixler
function! pythonsense#python_docstring_text_object (inner)
" get current line number
let s = line('.')
" climb up to first def/class line, or first line of buffer
while s > 0 && getline(s) !~# '^\s*\(def\|async def\|class\)'
let s = s - 1
endwhile
" set search start to just after def/class line, or on first buffer line
let s = s + 1
" descend lines obj_end_line end of buffer or def/class line
while s < line('$') && getline(s) !~# '^\s*\(def\|async def\|class\)'
" if line begins with optional whitespace followed by '''
if getline(s) =~ "^\\s*'''" || getline(s) =~ '^\s*"""'
if getline(s) =~ "^\\s*'''"
let close_pattern = "'''\\s*$"
else
let close_pattern = '"""\s*$'
endif
" set search end to just after found start line
let e = s + 1
" descend lines obj_end_line end of buffer or def/class line
while e <= line('$') && getline(e) !~# '^\s*\(def\|async def\|class\)'
" if line ends with ''' followed by optional whitespace
if getline(e) =~ close_pattern
" TODO check first for blank lines above to select instead
" for 'around', extend search end through blank lines
if a:inner
let e -= 1
let s += 1
else
let x = e + 1
while x <= line('$') && getline(x) =~ '^\s*$'
let e = x
let x = x + 1
endwhile
endif
" visual line select from start to end (first cursor move)
exe 'norm '.s.'ggV'.e.'gg'
return
endif
" move search end down a line
let e = e + 1
endwhile
endif
" move search start down a line
let s = s + 1
endwhile
endfunction
" }}}1
" Python Movements {{{1
function! pythonsense#move_to_python_object(obj_name, to_end, fwd, vim_mode) range
if a:fwd
let initial_search_start_line = a:lastline
else
let initial_search_start_line = a:firstline
endif
if a:to_end
let target_line = pythonsense#find_end_of_python_object_to_move_to(a:obj_name, initial_search_start_line, a:fwd, v:count1)
else
let target_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, initial_search_start_line, a:fwd, -1, v:count1)
endif
if target_line < 0 || target_line > line('$')
return
endif
let current_column = col('.')
let preserve_col_pos = get(b:, "pythonsense_preserve_col_pos", get(g:, "pythonsense_preserve_col_pos", 0))
let fold_open = ""
if a:vim_mode == "v"
normal! gv
endif
if has("folding") && foldclosed(line('.')) != -1
let fold_open = "zO"
else
let fold_open = ""
endif
try
if preserve_col_pos
execute "normal! " . target_line . "G" . current_column . "|" . preserve_col_pos . fold_open
else
execute "normal! " . target_line . "G^" . fold_open
endif
catch /E490/ " no fold found
endtry
endfunction
function! pythonsense#find_end_of_python_object_to_move_to(obj_name, start_line, fwd, nreps)
let initial_search_start_line = a:start_line
let effective_start_line = initial_search_start_line
let niters = 0
while niters < 2
let [start_line, nreps_remaining] = pythonsense#find_start_line_for_end_movement(a:obj_name, effective_start_line, a:fwd, a:nreps)
if start_line <= 0
let start_line = 1
endif
let start_of_object_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, start_line, a:fwd, -1, nreps_remaining)
if start_of_object_line < 0 || start_of_object_line > line('$')
return -1
endif
let target_line = pythonsense#get_object_end_line_nr(start_of_object_line, start_of_object_line, 1)
if target_line > 0
\ && niters == 0
\ && (
\ target_line == initial_search_start_line
\ || (a:fwd && target_line < initial_search_start_line)
\ || (!a:fwd && target_line > initial_search_start_line)
\ )
" no change; possibly because we are already at an end boundary;
" make ONE more attempt at trying again
let niters += 1
if a:fwd
let effective_start_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, target_line, a:fwd, -1, 1)
if effective_start_line <= 0
break
endif
else
let prev_obj_indent = pythonsense#get_line_indent_count(start_of_object_line)
let [new_start_line, nreps_remaining] = pythonsense#find_start_line_for_end_movement(a:obj_name, start_of_object_line, a:fwd, a:nreps)
while new_start_line > 0 && getline(new_start_line) =~ '^\s*$'
let new_start_line -= 1
endwhile
if new_start_line <= 0
break
endif
let start_of_object_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, new_start_line, a:fwd, prev_obj_indent, nreps_remaining)
let target_line = pythonsense#get_object_end_line_nr(start_of_object_line, start_of_object_line, 1)
break
endif
else
break
endif
endwhile
if a:fwd && target_line < initial_search_start_line
return -1
elseif !a:fwd && target_line > initial_search_start_line
return -1
else
return target_line
endif
endfunction
function! pythonsense#find_start_of_python_object_to_move_to(obj_name, start_line, fwd, max_indent, nreps)
let current_line = a:start_line
if a:fwd
let stepvalue = 1
else
let stepvalue = -1
endif
let target_pattern = '^\s*' . a:obj_name . '\s\+'
let nreps_left = a:nreps
let start_line = current_line
let target_line = current_line
let scope_block_indent = a:max_indent
if getline(start_line) =~# target_pattern
let start_line += stepvalue
endif
while nreps_left > 0
while start_line > 0 && start_line <= line("$")
if getline(start_line) =~ '^\s*\(class\|def\|async def\)'
let current_line_indent = pythonsense#get_line_indent_count(start_line)
if getline(start_line) =~# target_pattern
if a:max_indent < 0 || current_line_indent < scope_block_indent
let target_line = start_line
break
endif
endif
if scope_block_indent == -1 || current_line_indent < scope_block_indent
let scope_block_indent = current_line_indent
endif
endif
let start_line += stepvalue
endwhile
if start_line < 1 || start_line > line("$")
break
endif
let start_line += stepvalue
let nreps_left -= 1
endwhile
return target_line
endfunction
function! pythonsense#find_start_line_for_end_movement(obj_name, initial_search_start_line, fwd, nreps_requested)
let start_line = a:initial_search_start_line
let nreps_remaining = a:nreps_requested
let target_pattern = '^\s*' . a:obj_name . '\s\+'
let scope_block_indent = -1
let is_found = 0
while start_line > 0
if getline(start_line) =~ '^\s*\(class\|def\|async def\)'
let current_line_indent = pythonsense#get_line_indent_count(start_line)
if getline(start_line) =~ target_pattern
if scope_block_indent == -1 || current_line_indent < scope_block_indent
let is_found = 1
break
endif
endif
if scope_block_indent == -1 || current_line_indent < scope_block_indent
let scope_block_indent = current_line_indent
endif
endif
let start_line -= 1
endwhile
if is_found
if !a:fwd
let start_line -= 1
else
let nreps_remaining -= 1 " skip finding this block
endif
endif
return [start_line, nreps_remaining]
endfunction
" }}}1
" Python Location Information {{{1
function! pythonsense#echo_python_location()
let indent_char = pythonsense#get_indent_char()
let pyloc = []
let current_line = line('.')
let obj_pattern = '\(class\|def\|async def\)'
while current_line > 0
if getline(current_line) !~ '^\s*$'
break
endif
let current_line = current_line - 1
endwhile
if current_line == 0
return
endif
let target_line_indent = pythonsense#get_line_indent_count(current_line)
if target_line_indent < 0
break
endif
let previous_line = current_line
while current_line > 0
let pattern = '^' . indent_char . '\{0,' . target_line_indent . '}' . obj_pattern
let current_line_text = getline(current_line)
if current_line_text =~# pattern
let obj_name = matchstr(current_line_text, '^\s*\(class\|def\|async def\)\s\+\zs\k\+')
if get(g:, "pythonsense_extended_location_info", 1)
let obj_type = matchstr(current_line_text, '^\s*\zs\(class\|def\|async def\)')
call add(pyloc, "(" . obj_type . ":)" . obj_name)
else
call add(pyloc, obj_name)
endif
let target_line_indent = pythonsense#get_line_indent_count(current_line) - 1
endif
if target_line_indent < 0
break
endif
let previous_line = current_line
let current_line = current_line - 1
endwhile
if get(g:, "pythonsense_extended_location_info", 1)
let joiner = " > "
else
let joiner = "."
endif
echo join(reverse(pyloc), joiner)
return
endfunction
" }}}1

View File

@ -0,0 +1,147 @@
*pythonsense.txt* For Vim version 7.3 Last change: 2018 May 31
===============================================================================
*pythonsense*
Pythonsense provides text selection and motion objects for Python classes,
methods, functions, and doc strings.
-------------------------------------------------------------------------------
*pythonsense-object-select*
The following text objects are provided (see |pythonsense-custom-key-maps|
to use your own custom key maps):
ac "a class" text object. This includes the entire class, including
the header (class name declaration) and decorators, the class body,
as well as a blank line if this is given after the class
definition.
ic "inner class" text object. This is the class body only, thus
excluding the class name declaration line and any blank lines after
the class definition.
af "a function" (or method) text object. This includes the entire
function, including the header (function name declaration) and
decorators, the function body, as well as a blank line if this is
given after the function definition.
if "inner function" (or method) text object. This is the function
body only, thus excluding the function name declaration line and
any blank lines after the function definition.
ad "a docstring" text object; includes triple quotes as well as
docstring body.
id "inner docstring" text object; includes docstring body only, and
excludes triple quotes.
-------------------------------------------------------------------------------
*pythonsense-object-motion*
The following motions are provided (see pythonsense-alternate-motion-keymaps|
to use an alternate set of mappings that do not override the native Vim ones,
or |pythonsense-custom-key-maps| to use your own custom key maps):
{count}]] Move (forward) to the beginning of the next Python class.
{count}][ Move (forward) to the end of the current Python class.
{count}[[ Move (backward) to beginning of the current Python class
(or beginning of the previous Python class if not currently in a
class or already at the beginning of a class).
{count}[] Move (backward) to end of the previous Python class.
{count}]m Move (forward) to the beginning of the next Python method or
function.
{count}]M Move (forward) to the end of the current Python method or
function.
{count}[m Move (backward) to the beginning of the current Python method or
function (or to the beginning of the previous method or function if
not currently in a method/function or already at the beginning of a
method/function).
{count}[M Move (backward) to the end of the previous Python method or
function.
-------------------------------------------------------------------------------
*pythonsense-object-information*
The following information key maps are also provided:
g: Echo information about the current semantic Python location.
-------------------------------------------------------------------------------
*pythonsense-options*
g:is_pythonsense_suppress_object_keymaps
Specify this in your '~/.vimrc' to disable the object selection key mappings: >
let g:is_pythonsense_suppress_object_keymaps = 1
<
g:is_pythonsense_suppress_motion_keymaps
Specify this in your '~/.vimrc' to disable the motion key mappings: >
let g:is_pythonsense_suppress_motion_keymaps = 1
<
g:is_pythonsense_suppress_location_keymaps
Specify this in your '~/.vimrc' to disable the information key mappings: >
let g:is_pythonsense_suppress_location_keymaps = 1
<
g:is_pythonsense_alternate_motion_keymaps *pythonsense-alternate-motion-keymaps*
Specify this in your '~/.vimrc' to activate an alternate set of key mappings for object motions: >
let g:is_pythonsense_alternate_motion_keymaps = 1
<
The above option will result in the following motion mappings as opposed to the
ones described above:
{count}]k Move (forward) to the beginning of the next Python class.
{count}]K Move (forward) to the end of the current Python class.
{count}[k Move (backward) to beginning of the current Python class
(or beginning of the previous Python class if not currently in a
class or already at the beginning of a class).
{count}[K Move (backward) to end of the previous Python class.
{count}]f Move (forward) to the beginning of the next Python method or
function.
{count}]F Move (forward) to the end of the current Python method or
function.
{count}[f Move (backward) to the beginning of the current Python method
or function (or to the beginning of the previous method or
function if not currently in a method/function or already at
the beginning of a method/function).
{count}[F Move (backward) to the end of the previous Python method or
function.
-------------------------------------------------------------------------------
*pythonsense-custom-key-maps*
If you are unhappy with the default key-mappings you can define your own which
will individually override the default ones. However, instead of doing so in
your "~/.vimrc", you need to do so in a file located in your
"~/.vim/ftplugin/python/" directory, so that this key mappings are only enabled
when editing a Python file. Furthermore, you should make sure that you limit
the key mapping to the buffer scope. For example, to override yet
replicate the default mappings you would define, the following in
"~/.vim/ftplugin/python/pythonsense-custom.vim": >
map <buffer> ac <Plug>(PythonsenseOuterClassTextObject)
map <buffer> ic <Plug>(PythonsenseInnerClassTextObject)
map <buffer> af <Plug>(PythonsenseOuterFunctionTextObject)
map <buffer> if <Plug>(PythonsenseInnerFunctionTextObject)
map <buffer> ad <Plug>(PythonsenseOuterDocStringTextObject)
map <buffer> id <Plug>(PythonsenseInnerDocStringTextObject)
map <buffer> ]] <Plug>(PythonsenseStartOfNextPythonClass)
map <buffer> ][ <Plug>(PythonsenseEndOfPythonClass)
map <buffer> [[ <Plug>(PythonsenseStartOfPythonClass)
map <buffer> [] <Plug>(PythonsenseEndOfPreviousPythonClass)
map <buffer> ]m <Plug>(PythonsenseStartOfNextPythonFunction)
map <buffer> ]M <Plug>(PythonsenseEndOfPythonFunction)
map <buffer> [m <Plug>(PythonsenseStartOfPythonFunction)
map <buffer> [M <Plug>(PythonsenseEndOfPreviousPythonFunction)
map <buffer> g: <Plug>(PythonsensePyWhere)
<
You do not need to specify all the key mappings if you just want to
customize a few. Simply provide your own key mapping to each of the
"<Plug>" mappings you want to override, and these will be respected, while
any "<Plug>" maps that are not so explicitly bound will be assigned to the
default key maps.
vim:tw=78:ts=8:ft=help:norl:

View File

@ -0,0 +1,140 @@
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" File: ftplugin/python/pythonsense.vim
" Author: Jeet Sukumaran
"
" Copyright: (C) 2018 Jeet Sukumaran
"
" License: Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be included
" in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
"
" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"
" Credits: - pythontextobj.vim by Nat Williams (https://github.com/natw/vim-pythontextobj)
" - chapa.vim by Alfredo Deza (https://github.com/alfredodeza/chapa.vim)
" - indentobj by Austin Taylor's (https://github.com/austintaylor/vim-indentobject)
" - Python Docstring Text Objects by gfixler (https://pastebin.com/u/gfixler)
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Book-Keeping Variables {{{1
let b:pythonsense_is_tab_indented = 0
" 1}}}
" Commands {{{1
command! Pywhere :call pythonsense#echo_python_location()
" }}}1
"
" Plug Definitions {{{1
" Text Objects {{{2
onoremap <buffer> <silent> <Plug>(PythonsenseOuterFunctionTextObject) :<C-u>call pythonsense#python_function_text_object(0, "o")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseInnerFunctionTextObject) :<C-u>call pythonsense#python_function_text_object(1, "o")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseOuterClassTextObject) :<C-u>call pythonsense#python_class_text_object(0, "o")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseInnerClassTextObject) :<C-u>call pythonsense#python_class_text_object(1, "o")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseOuterDocStringTextObject) :<C-u>call pythonsense#python_docstring_text_object(0)<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseInnerDocStringTextObject) :<C-u>call pythonsense#python_docstring_text_object(1)<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseOuterFunctionTextObject) :<C-u>call pythonsense#python_function_text_object(0, "v")<CR><Esc>gv
vnoremap <buffer> <silent> <Plug>(PythonsenseInnerFunctionTextObject) :<C-u>call pythonsense#python_function_text_object(1, "v")<CR><Esc>gv
vnoremap <buffer> <silent> <Plug>(PythonsenseOuterClassTextObject) :<C-u>call pythonsense#python_class_text_object(0, "v")<CR><Esc>gv
vnoremap <buffer> <silent> <Plug>(PythonsenseInnerClassTextObject) :<C-u>call pythonsense#python_class_text_object(1, "v")<CR><Esc>gv
vnoremap <buffer> <silent> <Plug>(PythonsenseOuterDocStringTextObject) :<C-u>cal pythonsense#python_docstring_text_object(0)<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseInnerDocStringTextObject) :<C-u>cal pythonsense#python_docstring_text_object(1)<CR>
" }}}2
" Motions {{{2
nnoremap <buffer> <silent> <Plug>(PythonsenseStartOfNextPythonClass) :<C-u>call pythonsense#move_to_python_object("class", 0, 1, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseStartOfNextPythonClass) :call pythonsense#move_to_python_object("class", 0, 1, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseStartOfNextPythonClass) V:<C-u>call pythonsense#move_to_python_object("class", 0, 1, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPythonClass) :<C-u>call pythonsense#move_to_python_object("class", 1, 1, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPythonClass) :call pythonsense#move_to_python_object("class", 1, 1, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseEndOfPythonClass) V:<C-u>call pythonsense#move_to_python_object("class", 1, 1, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseStartOfPythonClass) :<C-u>call pythonsense#move_to_python_object("class", 0, 0, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseStartOfPythonClass) :call pythonsense#move_to_python_object("class", 0, 0, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseStartOfPythonClass) V:<C-u>call pythonsense#move_to_python_object("class", 0, 0, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPreviousPythonClass) :<C-u>call pythonsense#move_to_python_object("class", 1, 0, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPreviousPythonClass) :call pythonsense#move_to_python_object("class", 1, 0, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseEndOfPreviousPythonClass) V:<C-u>call pythonsense#move_to_python_object("class", 1, 0, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseStartOfNextPythonFunction) :<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 0, 1, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseStartOfNextPythonFunction) :call pythonsense#move_to_python_object('\(def\\|async def\)', 0, 1, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseStartOfNextPythonFunction) V:<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 0, 1, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPythonFunction) :<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 1, 1, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPythonFunction) :call pythonsense#move_to_python_object('\(def\\|async def\)', 1, 1, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseEndOfPythonFunction) V:<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 1, 1, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseStartOfPythonFunction) :<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 0, 0, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseStartOfPythonFunction) :call pythonsense#move_to_python_object('\(def\\|async def\)', 0, 0, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseStartOfPythonFunction) V:<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 0, 0, "o")<CR>
nnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPreviousPythonFunction) :<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 1, 0, "n")<CR>
vnoremap <buffer> <silent> <Plug>(PythonsenseEndOfPreviousPythonFunction) :call pythonsense#move_to_python_object('\(def\\|async def\)', 1, 0, "v")<CR>
onoremap <buffer> <silent> <Plug>(PythonsenseEndOfPreviousPythonFunction) V:<C-u>call pythonsense#move_to_python_object('\(def\\|async def\)', 1, 0, "o")<CR>
" }}}2
" Information {{{2
nnoremap <buffer> <silent> <Plug>(PythonsensePyWhere) :Pywhere<CR>
" }}}2
" }}}1
" Plug Binding Key Maps {{{1
if ! get(g:, "is_pythonsense_suppress_keymaps", 0) && ! get(g:, "is_pythonsense_suppress_object_keymaps", 0)
if !hasmapto('<Plug>PythonsenseOuterClassTextObject')
vmap <buffer> ac <Plug>(PythonsenseOuterClassTextObject)
omap <buffer> ac <Plug>(PythonsenseOuterClassTextObject)
sunmap <buffer> ac
endif
if !hasmapto('<Plug>PythonsenseInnerClassTextObject')
vmap <buffer> ic <Plug>(PythonsenseInnerClassTextObject)
omap <buffer> ic <Plug>(PythonsenseInnerClassTextObject)
sunmap <buffer> ic
endif
if !hasmapto('<Plug>PythonsenseOuterFunctionTextObject')
vmap <buffer> af <Plug>(PythonsenseOuterFunctionTextObject)
omap <buffer> af <Plug>(PythonsenseOuterFunctionTextObject)
sunmap <buffer> af
endif
if !hasmapto('<Plug>PythonsenseInnerFunctionTextObject')
vmap <buffer> if <Plug>(PythonsenseInnerFunctionTextObject)
omap <buffer> if <Plug>(PythonsenseInnerFunctionTextObject)
sunmap <buffer> if
endif
if !hasmapto('<Plug>PythonsenseOuterDocStringTextObject')
omap <buffer> ad <Plug>(PythonsenseOuterDocStringTextObject)
vmap <buffer> ad <Plug>(PythonsenseOuterDocStringTextObject)
sunmap <buffer> ad
endif
if !hasmapto('<Plug>PythonsenseInnerDocStringTextObject')
omap <buffer> id <Plug>(PythonsenseInnerDocStringTextObject)
vmap <buffer> id <Plug>(PythonsenseInnerDocStringTextObject)
sunmap <buffer> id
endif
endif
if ! get(g:, "is_pythonsense_suppress_keymaps", 0) && ! get(g:, "is_pythonsense_suppress_location_keymaps", 0)
if !hasmapto('<Plug>(PythonsensePyWhere)')
map g: <Plug>(PythonsensePyWhere)
endif
endif
" }}}1