Vim scripting con Python

Vim permite agregar funcionalidades mediante varios lenguajes externos, entre ellos Perl, Python, Ruby y Tcl. Para poder hacer uso de esta característica es necesario haber compilado Vim con el soporte para el lenguaje necesario (con el flag-python, para soporte python). En debian existe un paquete llamado vim-nox (no X) que contiene una versión compilada con soporte para los cuatro lenguajes mencionados:

sudo apt-get install vim-nox

Para comprobar que efectivamente se encuentre habilitado el soporte para Python ejecutar Vim y en modo comando escribir:

:echo has("python")

Un 1 indica que Vim interpreta Python ;)

Es muy recomendable leer la ayuda en línea:

:help python

También se puede acceder vía web desde http://vimdoc.sourceforge.net/htmldoc/if_pyth.html

Ejecución de código Python

La ejecución de una línea de código Python se realiza según la sintaxis:

:[range]py[thon] {stmt}

Ejemplo:

:python print "hello world"

Aquí el prompt mostrará el mensaje “hello world”.

En cambio para escribir código en varias líneas hay que usar la siguiente forma:

[range]:py[thon] << {endmarker}
{script}
{endmarker}

Ejemplo:

:python << EOF
def get_user():
  import os
  return os.getenv('USER')
EOF

De esta manera la función queda guardada en la memoria de la sesión actual. Luego su uso podría ser el siguiente:

:py user = get_user()

Tambien es posible cargar y/o ejecutar código desde un archivo externo. La sintaxis es:

:[range]pyf[ile] {file}

Ejemplo:

:pyfile myscript.py

Simyscript.pynecesitara parámetros la forma de pasárselos es la siguiente:

:py import sys
:py sys.argv = ['foo', 'bar']
:pyf myscript.py

El módulo vim

La comunicación entre Python y Vim se realiza a través del módulo vim, el cual hay que importar antes de usar:

:python import vim

Como indica la ayuda de Vim, el módulo implementa dos métodos (vim.command(str)yvim.eval(str)), tres constantes (vim.buffers,vim.windowsyvim.current) y un objeto error (vim.error). Las tres constantes mencionadas no son realmente constantes sino variables que pueden ser reasignadas pero, como dice también la ayuda, esto sería absurdo ya que se perdería acceso a los objetos de Vim que referencian. Por otra parte cabe recordar que no existen constantes en Python. Como cita el libro “Inmersión en Python“:

Todo puede cambiar si lo intenta con ahínco. Esto se ajusta a uno de los principios básicos de Python: los comportamientos inadecuados sólo deben desaconsejarse, no prohibirse.


A continuación transcribo un detalle de cada una de las constantes mencionadas:

vim.buffers
Objeto de tipo secuencia que provee acceso al listado de buffers actuales. El objeto soporta las siguiente operaciones:

:py b = vim.buffers[i]     # Indexación (Read-Only)
:py b in vim.buffers       # Prueba de existencia
:py n = len(vim.buffers)   # Número de elementos
:py for b in vim.buffers:  # Acceso secuencial
vim.windows
Objeto de tipo secuencia que provee acceso al listado de ventanas actuales. El objeto soporta las mismas operaciones que vim.buffers:

:py w = vim.windows[i]     # Indexación (RO)
:py w in vim.windows       # Prueba de existencia
:py n = len(vim.windows)   # Número de elementos
:py for w in vim.windows:  # Acceso secuencial
vim.current
Objeto que provee acceso (vía atributos específicos) a distintos objetos actuales:

vim.current.line    # (RW) String
vim.current.buffer  # (RO) Buffer
vim.current.window  # (RO) Window
vim.current.range   # (RO) Range

Ejemplos básicos de uso del módulo vim

Usandovim.current.line:

:py from string import upper
:py vim.current.line = upper(vim.current.line)

Usandovim.current.buffer:

:py b = vim.current.buffer      # obtiene buffer actual
:py name = b.name               # nombre de archivo del buffer actual
:py num = len(b)                # total de líneas en el buffer
:py line = b[n]                 # obtiene la línea n+1
:py lines = b[n:m]              # obtiene una lista de líneas
:py b[n] = str                  # reemplaza la línea n+1
:py b[n:m] = [str1, str2, str3] # reemplaza varias líneas a la vez
:py b[0:0] = ["hola mundo"]     # inserta una línea al comienzo
:py b.append("fin")             # inserta una línea al final
:py del b[n]                    # borra la línea n+1
:py del b[n:m]                  # borra varias líneas a la vez
:py b[:] = None                 # borra el buffer completo

Usandovim.current.window:

:py cw = vim.current.window      # obtiene la ventana actual
:py cw.height = 30               # setea el alto de la ventana
:py cw.width = 80                # setea el ancho
:py pos = cw.cursor              # posicion del cursor (row, col)

Script para subrayado de títulos

El siguiente script (heading.vim) lo escribí para no tener que, manualmente, subrayar títulos y delimitar bloques de texto. Especialmente quería que reconociera cuando se tratara de texto dentro de comentarios de código fuente y que respetara los caracteres especiales al comienzo (y final) de los mismos.

El script tiene una función que decora la línea actual colocando él o los caracteres decoradores tanto arriba como debajo de la misma (comportamiento por defecto). El largo (también por defecto) es el largo de la línea, pero se puede fijar un límite (ejemplo 80 columnas) y pedirle que ocupe todo el espacio disponible. Esta opción funciona bien sólamente cuando el indentado es con espacios. La opción uppercase funciona como se espera.

El código es el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
" ============================================================================
" File:        heading.vim
" Version:     0.1
" Description: vim global plugin that provides a way to create headings.
" Maintainer:  Leonardo Vidarte <lvidarte at gmail dot com>
" Last Change: 21 February, 2010
" ============================================================================
if has('python')
python << EOF
def heading(decorator='=', top=True, bottom=True, limit=0, uppercase=False):

    import vim
    from string import upper, rstrip, lstrip

    if not top and not bottom:
        return None # nothing to do

    (row, col) = vim.current.window.cursor
    line = vim.current.buffer[row-1].rstrip()
    line_length = len(line.decode('utf8'))
    title = line.lstrip()
    word_length = len(title.decode('utf8'))
    whitespace = line[0:line_length - word_length]

    if limit > 0:
        decorator *= limit
        total_length = limit
    else:
        decorator *= word_length
        total_length = line_length

    if title[0:3] in ('// ', '/* ', '-- '):
        decoline = whitespace + title[0:3] + decorator
    elif title[0:2] in ('//', '/*', '--', '# ', '* ', '" '):
        decoline = whitespace + title[0:2] + decorator
    elif title[0:1] in ('#', '*', '"'):
        decoline = whitespace + title[0:1] + decorator
    else:
        decoline = whitespace + decorator

    # Special case: comments like C /* hello world */
    cend = ''
    if title[0:2] == '/*':
        if title[-3:] == ' */':
            cend = ' */'
        elif title[-2:] == '*/':
            cend = '*/'

    decoline = decoline[0:total_length - len(cend)] + cend
    final_heading = []

    if top:
        final_heading.append(decoline)

    if uppercase:
        final_heading.append(line.upper())
    else:
        final_heading.append(line)

    if bottom:
        final_heading.append(decoline)

    del vim.current.buffer[row-1]
    vim.current.buffer[row-1:0] = final_heading

    # Set final cursor position
    if top and bottom:
        vim.current.window.cursor = (row + 2, 0)
    else:
        vim.current.window.cursor = (row + 1, 0)

H = heading # shortcut
EOF

" ============================================================================
" Vim maps (see :help leader)
" ============================================================================
nnoremap <silent> <Leader>hh :python heading()<CR>

nnoremap <silent> <Leader>h1 :python heading('#', uppercase=True)<CR>
nnoremap <silent> <Leader>h2 :python heading('*', uppercase=True)<CR>
nnoremap <silent> <Leader>h3 :python heading('=')<CR>
nnoremap <silent> <Leader>h4 :python heading('-')<CR>
nnoremap <silent> <Leader>h5 :python heading('~', top=False)<CR>

nnoremap <silent> <Leader>HH :python heading(limit=78)<CR>
nnoremap <silent> <Leader>Hb :python heading(limit=78, top=False)<CR>
nnoremap <silent> <Leader>Ht :python heading(limit=78, bottom=False)<CR>

endif

Para usarlo hay que colocar el archivo heading.vim en el directorio~/.vim/plugins.

Ejemplos de uso

1. Texto normal

Título
:py H()
======
Título
======

2. Comentario en línea (Python, Bash, Perl, PHP, Ruby)

    # Comment
:py H()
    # =======
    # Comment
    # =======

3. Comentario en bloque, estilo javadoc (C, C++, Java, Javascript, CSS, PHP, SQL)

    /**
     * Comment
     */
:py H('#', uppercase=True)
    /**
     * #######
     * COMMENT
     * #######
     */

4. Comentario en línea (C, C++, Java, Javascript, CSS, PHP, SQL)

/* Comment */
:py H('~+')
/* ~+~+~+~ */
/* Comment */
/* ~+~+~+~ */

5. Comentario en línea (C, Java, Javascript, PHP)

// Comment
:py H(limit=40)
// =====================================
// Comment
// =====================================

6. Comentario en línea (Vim script)

" Comment
:py H(top=None)
" Comment
" ======

7. Comentario en línea (Lua, SQL)

-- Comment
:py H()
-- =======
-- Comment
-- =======

Atajos de teclado

El script también define algunos atajos en modo normal para Vim. Para los mismos se usa la tecla especial<Leader>que es definida por la variablemapleadery que generealmente es el caracter\.

Atajo Comando
\hh :python heading()
\h1 :python heading(‘#’, uppercase=True)
\h2 :python heading(‘*’, uppercase=True)
\h3 :python heading(‘=’)
\h4 :python heading(‘-’)
\h5 :python heading(‘~’, top=False)
\HH :python heading(limit=78)
\Hb :python heading(limit=78, top=False)
\Ht :python heading(limit=78, bottom=False)

Lectura adicional

Tags: , ,

Leave a Reply