Home

Awesome

Pythonsense

Description

Pythonsense is a Vim plugin that provides text objects and motions for Python classes, methods, functions, and doc strings.

Python Text Objects

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

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) 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, 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 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 for more details on these key mappings.

Python Location Information

Installation

Manually (Example)

If you are using Vim 8.0 and above, I recommend you take advantage of the 'package' 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".

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 (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:

pathogen.vim

$ cd ~/.vim/bundle
$ git clone git://github.com/jeetsukumaran/vim-pythonsense.git

Vim-Plug

Add the line below into your "~/.vimrc":

Plug 'jeetsukumaran/vim-pythonsense'

and run:

:PlugInstall

More information on setting up Vim-Plug to manage your plugins can be found here.

Vundle

Add the line below into your "~/.vimrc":

Vundle 'jeetsukumaran/vim-pythonsense'

or run:

:VundleInstall jeetsukumaran/vim-pythonsense

Customization

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

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

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:

Similar Projects

Most notable is the vim-textobj-python plugin. Pythonsense distinguishes itself from this plugin in the following ways:

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:

Acknowledgements

Appendices

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:

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.