Programando el script del editor Vim, Parte 2: Funciones definidas por el usuario

Crear los bloques de compilación fundamentales de la automatización

Funciones definidas por el usuario son una herramienta esencial para descomponer una aplicación en componentes correctos y sostenibles, para administrar la complejidad de tareas de programación del mundo real. Este artículo (el segundo en una serie) explica cómo crear y desplegar las nuevas funciones dentro del lenguaje de Vimscript, entregando varios ejemplos prácticos de lo que usted podría querer.

Damian Conway, Dr., CEO and Chief Trainer, Thoughtstream

author photo - damian conwayDamian Conway es un Profesor Asociado Adjunto de Ciencias de la Computación en Universidad Monash, Australia, y Director Ejecutivo de Thoughtstream, una compañía internacional de entrenamiento en IT. Él ha sido un usuario diario de vi por más de un cuarto de siglo, y parece haber poca esperanza de que él vaya a vencer el vicio.



07-07-2009

Sobre Vimscript y esta serie

Vimscript es un potente lenguaje de programación de scripts que le permite a usted modificar y extender el editor Vim. Usted puede usarlo para crear nuevas herramientas, simplificar tareas comunes, e incluso revisar funciones existentes del editor. La presente serie de artículos supone una cierta familiaridad con el editor Vim. Usted también debería leer "Scripting the Vim editor, Part 1"el que cubre variables, valores y expresiones, conocimientos esenciales para las funciones de compilación.

Funciones definidas por el usuario

Pregúntele a los programadores de Haskell o Scheme y le dirán que las funciones son la característica más importante de cualquier lenguaje de programación serio. Pregúnteles a los programadores de C o de Perl, y le dirán exactamente lo mismo.

Las funciones le entregan al programador dos beneficios importantes:

  1. Permiten que las complejas tareas computacionales puedan ser subdivididas en fragmentos lo suficientemente pequeños para caber de manera cómoda dentro de un cerebro humano.
  2. Estos le permiten que se les entreguen a estos fragmentos subdivididos nombres lógicos y comprensibles, para que puedan ser manipulados de forma competente por un único cerebro humano.

Vimscript es un lenguaje de programación serio, por lo tanto soporta naturalmente la creación de las funciones definidas por el usuario. En efecto, se podría decir que tiene mejor soporte para las funciones definidas por el usuario que Scheme, C o Perl. Este artículo explora la variadas características de las funciones de Vimscript y le muestra cómo usted puede usarlas para realzar y extender la funcionalidad incorporada de Vim de una manera sostenible.

Declarar las funciones

Las funciones en Vimscript son definidas usando la palabra clave function, seguida por el nombre de la función, luego la lista de parámetros (la cual es obligatoria, aún si la función no permite argumentos). El cuerpo de la función comienza entonces en la siguiente línea y continúa hasta que una palabra clave de función final es encontrada. Por ejemplo:

Lista 1. Una función correctamente estructurada
functionExpurgateText (text)
    let expurgated_text = a:text

    for expletive in [ 'cagal', 'frak', 'gorram', 'mebs', 'zarking']
        let expurgated_text
        \   = substitute(expurgated_text, expletive, '[DELETED]', 'g')
    endfor

    return expurgated_text
endfunction

El valor de retorno de la función es especificado con una sentencia de return. Usted puede especificar tantas sentencias de return separadas como necesite. Puede: no incluir ninguna si la función está siendo utilizada como un procedimiento y no tiene un valor de retorno útil. Sin embargo, las funciones de Vimscript siempre retornan un valor, entonces si ningún retorno es especificado, la función automáticamente retorna a cero.

Los nombres de las funciones en Vimscript deben comenzar con una letra mayúscula:

Lista 2. Los nombres de las funciones comienzan con una letra mayúscula
function SaveBackup ()
    let b:backup_count = exists('b:backup_count') ? b:backup_count+1 : 1
    return writefile(getline(1,'$'), bufname('%') . '_' . b:backup_count)
endfunction

nmap <silent>  <C-B>  :call SaveBackup()<CR>

Este ejemplo define a una función que incrementa el valor de la actual variable b:backup_count del almacenamiento intermedio (o la inicia a 1, si es que aún no existe). La función entonces toma cada línea en el archivo actual (getline(1,'$')) y llama a la función de writefile() incorporada para escribirlas en el disco. El segundo argumento al writefile() es el nombre del nuevo archivo a ser escrito; en este caso el nombre del archivo actual (bufname('%')) con el nuevo valor del contador adjuntado. El valor retornado es el valor del éxito/fracaso de la orden para writefile(). Finalmente, el nmap configura un CTRL-B para dar la orden a la función para crear un respaldo numerado del archivo actual.

En vez de usar una letra mayúscula delantera, las funciones de Vimscript también pueden ser declaradas con un prefijo de alcance explícito (tal como las variables pueden ser, como ha sido descrito en la Parte 1). La elección más común es s:, la cual hace que la función sea local para el archivo script actual. Si una función es alcanzada de esta manera, sin que sea necesario que comience con una mayúscula; puede ser cualquier identificador válido identificador. Sin embargo, las funciones explícitamente alcanzadas siempre deben ser ordenadas con sus prefijos de alcance. Por ejemplo:

Lista 3. Llamando una función con su prefijo de alcance
" Function scoped to current script file...
function s:save_backup ()
    let b:backup_count = exists('b:backup_count') ? b:backup_count+1 : 1
    return writefile(getline(1,'$'), bufname('%') . '_' . b:backup_count)
endfunction

nmap <silent>  <C-B>  :call s:save_backup()<CR>

Funciones redeclarables

Las declaraciones de funciones en Vimscript son declaraciones de tiempos de ejecución, por lo tanto si un script se carga dos veces, cualquiera de las declaraciones de funciones de script serán ejecutadas dos veces, recreando las funciones correspondientes.

La redeclaración de una función es tratada como un error fatal (para prevenir choques en los lugares en que dos scripts separados accidentalmente declaran funciones al mismo tiempo). Esto dificulta la creación de funciones en scripts que son diseñados para que se carguen repetidamente, tales como scripts personalizados de destaque de sintaxis.

Por lo tanto Vimscript entrega un modificador de la palabra clave (¡función!) que le permite indicar que una declaración de función puede ser recargada de forma segura las veces que se requiera:

Lista 4. Indicar que la declaración de la función puede ser recargada de forma segura
function! s:save_backup ()
    let b:backup_count = exists('b:backup_count') ? b:backup_count+1 : 1
    return writefile(getline(1,'$'), bufname('%') . '_' . b:backup_count)
endfunction

Ningún chequeo de redeclaración es efectuado sobre las funciones definidas con esta palabra clave modificada, entonces es usado de mejor manera con funciones explícitamente alcanzadas (en caso de que el alcance ya asegure que la función no colisione con otra de otro script).

Funciones de la orden

Para llamar una función y usar su valor de retorno como parte de una expresión mayor, simplemente nómbrela y adjunte una lista de argumentos entre paréntesis:

Lista 5. Utilizando el valor de retorno de la función
"Clean up the current line...
let success = setline('.', ExpurgateText(getline('.')) )

Sin embargo, note que a diferencia de C o Perl, Vimscript no le permite botar el valor de retorno de una función sin usarlo. Entonces, si usted pretende usar la función como un procedimiento o una subrutina e ignorar su valor de retorno, usted debe prefijar la invocación con el comando call:

Lista 6. Utilizando una función sin usar su valor de retorno
"Checkpoint the text...
call SaveBackup()

De lo contrario, Vimscript asumirá que el llamado de función es de hecho un comando Vim incorporado y lo más probable es que alegue que dicho comando no existe. Observaremos la diferencia entre las funciones y los comandos en un futuro artículo en estas series.

Listas de Parámetros

Vimscript le permite definir ambos explicit parameters y variadic parameter lists e incluso combinaciones de ambos.

Usted puede especificar hasta 20 parámetros explícitos inmediatamente después de la declaración del nombre de la subrutina. Una vez especificados, los correspondientes valores del argumento para el llamado actual se pueden acceder dentro de la función al prefijar una a: al nombre del parámetro:

Lista 7.Acceder a los valores del argumento dentro de la función
function PrintDetails(name, title, email)
    echo 'Name:   '  a:title  a:name
    echo 'Contact:'  a:email
endfunction

Si no sabe cuántos argumentos pueden ser entregados a una función, usted puede especificar una lista de parámetros variadic, usando una elipsis (....) en vez de parámetros nombrados. En este caso, la función puede ser llamada con todos los argumentos que desee, y aquellos valores son recolectados dentro de una sola variable: una selección nombrada a:000 Los argumentos individuales también son nombres de parámetros posicionales entregados: a:1, a:2, a:3, etc. El número del argumento está disponible como a:0 . Por ejemplo:

Lista 8 Especificando y utilizando una lista de parámetros variadic
function Average(...)
    let sum = 0.0

    for nextval in a:000"a:000 is the list of arguments
        let sum += nextval
    endfor

    return sum / a:0"a:0 is the number of arguments
endfunction

Note que, en este ejemplo, sum debe ser iniciado a un valor de punto flotante explícito; de lo contrario, todos los cómputos subsecuentes serán efectuados usando aritmética con números enteros.

Combinar parámetros nombrados y parámetros variadic

Los parámetros nombrados y los variadic pueden ser usados en la misma función, simplemente al ubicar la elipsis variadic después de la lista de los parámetros nombrados.

Por ejemplo, supongamos que usted quisiera crear una función CommentBlock() que estaba pasada la serie y formateada en un bloque de comentario apropiado para varios idiomas de programación. Dicha función podría siempre requerir que la persona que le de la orden para que entregue la serie para ser formateada, para que el parámetro sea explícitamente nombrado. Pero usted podría preferir que el introductor del comentario, y el caracter del "boxing", y el ancho del comentario sea opcional (con fallas sensibles cuando es omitido) Entonces usted podría llamar:

Lista 9: Una simple llamada de función del Bloque de Comentario
call CommentBlock("This is a comment")

and it would return a multi-line string containing:

Listing 10. The CommentBlock return
//*******************
// This is a comment
//*******************

Por lo tanto, si usted entregó argumentos adicionales, éstos especificarían valores sin fallas para el introductor de comentario, el caracter de "boxing" y el ancho del comentario. Entonces esta llamada:

Lista 11: Una llamada de función del bloque de comentario más involucrada
call CommentBlock("This is a comment", '#', '=', 40)

retornaría la serie

Lista 12. El retorno del Bloque de Comentario
#========================================
# This is a comment
#========================================

Tal función podría ser implementada tal como:

Lista 13. La implementación del Bloque de Comentario
function CommentBlock(comment, ...)
    "If 1 or more optional args, first optional arg is introducer...
    let introducer =  a:0 >= 1  ?  a:1  :  "//"

    "If 2 or more optional args, second optional arg is boxing character...
    let box_char   =  a:0 >= 2  ?  a:2  :  "*"

    "If 3 or more optional args, third optional arg is comment width...
    let width      =  a:0 >= 3  ?  a:3  :  strlen(a:comment) + 2

    " Build the comment box and put the comment inside it...
    return introducer . repeat(box_char,width) . "\<CR>"
    \    . introducer . " " . a:comment        . "\<CR>"
    \    . introducer . repeat(box_char,width) . "\<CR>"
endfunction

Si hay al menos un argumento opcional (a:0>=1), el parámetro introductor es asignado a esa primera opción (es decir, a:1); de lo contrario se le asigna un valor de falla de "//". De igual forma, si hay dos o más argumentos opcionales (a:0>=2), a la box_char una variable se le asigna a la segunda opción (a:2), o de lo contrario el valor de falla de "*". Si tres o más argumentos opcionales son abastecidos, la tercera opción es asignada a la variable del ancho. Si ningún argumento de ancho es entregado, el ancho apropiado es computado automáticamente desde el argumento del comentario mismo (strlen(a:comentario)+2).

Finalmente, al haber resuelto todos los valores de parámetros, las líneas superiores e inferiores de la casilla de comentarios son construidas usando el introductor de comentario líder, seguido por el apropiado número de repeticiones del caracter de "boxing" (repetir(box_char,width)), con el texto del comentario mismo entre medio de ellos.

Por supuesto, para utilizar esta función, usted necesitaría invocarla de alguna manera. Un mapa de inserción es probablemente la manera ideal de hacerlo:

Lista 14. Invocar la función usando un mapa de inserción
"C++/Java/PHP comment...
imap <silent>  ///  <C-R>=CommentBlock(input("Enter comment: "))<CR>

"Ada/Applescript/Eiffel comment...
imap <silent>  ---  <C-R>=CommentBlock(input("Enter comment: "),'--')<CR>

"Perl/Python/Shell comment...
imap <silent>  ###  <C-R>=CommentBlock(input("Enter comment: "),'#','#')<CR>

En cada uno de estos mapas, la función del input() incorporado es llamado primero para solicitar que el usuario digite el texto del comentario. La función del CommentBlock() es entonces ordenada a convertir aquel texto en un bloque de comentario. Finalmente, el <C-R>=líder inserta la serie resultante.

Note que la correlación solamente pasa un único argumento, entonces falla al usar // como su marcador de comentario. Las segundas y terceras correlaciones pasan un segundo argumento para especificar # o -- como sus introductores de comentario respectivos. La correlación final también pasa por un tercer argumento, para hacer que el caracter de "boxing" coincida con su introductor de comentario.


Las funciones y intervalo de línea

Usted puede invocar cualquier comando Vim estándar—incluyendo call—con un intervalo de línea preliminar, que causa que el comando se repita una sola vez por cada línea dentro del intervalo:

"Delete every line from the current line (.) to the end-of-file ($)...
:.,$delete

"Replace "foo" with "bar" everywhere in lines 1 to 10
:1,10s/foo/bar/

"Center every line from five above the current line to five below it...
:-5,+5center

Usted puede digitar :help cmdline-ranges en cualquier sesión Vim para aprender más sobre su estructura.

En el caso del comando call, especificar un intervalo causa que la función solicitada sea llamada en forma reiterada: una vez para cada línea en el intervalo. Para conocer por qué esto es útil, consideremos cómo escribir una función que convierte cualquier caracter & "crudo" en la línea actual a entidades XML &amp; propias, pero eso también es lo suficientemente inteligente para omitir cualquier caracter & que ya es parte de alguna otra entidad. Esa función podría ser implementada como:

Lista 15. Función para convertir los caracteres &
function DeAmperfy()
    "Get current line...
    let curr_line   = getline('.')

    "Replace raw ampersands...
    let replacement = substitute(curr_line,'&\(\w\+;\)\@!','&amp;','g')

    "Update current line...
    call setline('.', replacement)
endfunction

La primera línea de DeAmprefy() toma la línea actual desde el almacenamiento intermedio editor (getline('.')). La segunda línea busca cualquier & en esa línea que no esté seguida por un identificador y dos puntos, usando una secuencia de lectura anticipada negativa '&\(W\+;)\@!'(ver :help\@! para detalles). El substitute() llama y luego reemplaza todos los supuestos caracter & "crudos" con la entidad XML&amp;. Finalmente, la tercera línea de DeAmperfy() actualiza la línea actual con el texto modificado.

Si usted pidió esta función desde la línea de comando:

:call DeAmperfy()

ejecutaría el reemplazo sólo en esa línea. Pero si especificó una gama antes de call:

:1.$call DeAmperfy()

entonces la función sería llamada una vez por cada línea dentro del intervalo (en este caso, para cada línea en el archivo).

Interiorizando los intervalos de línea de las funciones

Este comportamiento de dar la orden a la función para cada línea es una falla conveniente. Sin embargo, a veces usted podría preferir especificar un intervalo y luego dar la orden a la función solamente una vez, y luego manejar las semánticas del intervalo dentro de la función misma. Eso también es fácil en Vimscript. Usted simplemente adjunta un modificador especial (range) a la declaración de la función:

Lista 16. Las semánticas del intervalo dentro de una función
function DeAmperfyAll() range"Step through each line in the range...
    for linenum in range(a:firstline, a:lastline)
        "Replace loose ampersands (as in DeAmperfy())...
        let curr_line   = getline(linenum)
        let replacement = substitute(curr_line,'&\(\w\+;\)\@!','&amp;','g')
        call setline(linenum, replacement)
    endfor

    "Report what was done...
    if a:lastline > a:firstline
        echo "DeAmperfied" (a:lastline - a:firstline + 1) "lines"
    endif
endfunction

Con el modificador de range especificado después de la lista de parámetros, en cualquier momento DeAmperfyAll() es ordenado con un intervalo tal como:

:1.$call DeAmperfyAll()

entonces la función es invocada sólo una vez y dos argumentos especiales, a:firstline y a:lastline, son puestos al primer y al último número de línea en el intervalo. Si ningún intervalo es especificado, ambos a:firstline y a:lastline, son puestos al número de línea actual.

La función compila primero una lista de todos los números de línea relevantes (range(a:firstline, a:lastline)). Tenga en cuenta que esta llamada a la función incorporada range() no tiene relación alguna con el uso del modificador range como parte de la declaración de la función. La función range() es simplemente una constructora de listas, muy similar a la función range() en Python, o al .. operador en Haskell o Perl.

Después de determinar la lista del número de líneas que se van a procesar, la función utiliza una curva para cruzar cada:

for linenum in range(a:firstline, a:lastline)

y actualiza cada línea como corresponde (sólo como lo hizo el original DeAmperfy()).

Finalmente, si la gama cubre más de una línea única (en otras palabras, si a:lastline > a:firstline), la función informa cuantas líneas fueron actualizadas.

Gama visual

Una vez que usted tiene una llamada de función que puede operar en una gama de líneas, una técnica particularmente útil es llamar esa función a través del modo Visual (ver :help Visual-mode para detalles).

Por ejemplo, si su cursor está en alguna parte en un bloque de texto, usted podría codificar todos los caracteres & en cualquier lugar del párrafo circundante con:

Vip:call DeAmperfyAll()

Tipear V en el modo Normal lo cambia al modo Visual. El ip luego provoca que el modo Visual destaque el párrafo completo en el que usted se encuentra. Entonces, el : lo cambia para el modo Command y automáticamente configura la gama del comando para la gama de líneas que usted seleccionó en el modo Visual. En este punto usted llama DeAmperfyAll() para dejarlos deamperfy a todos.

Note que, en esta instancia, usted podría obtener el mismo efecto con solamente:

Vip:call DeAmperfy()

La única diferencia es que la función DeAmperfy() será llamada repetidamente: una vez para cada línea el Vip destacado en el modo Visual.


Una función para ayudarle a codificar

La mayoría de las funciones definidas por el usuario en Vimscript requieren muy pocos parámetros y a menudo ninguno. Eso se debe a que generalmente obtienen sus datos directamente del almacenamiento intermedio actual del editor y de información contextual (tales como la posición actual del cursor, el tamaño actual del párrafo, el tamaño actual de la ventana o los contenidos de la línea actual).

Además, las funciones a menudo son mucho más útiles y convenientes cuando obtienen sus datos a través del contexto, más que a través de sus listas de argumentos. Por ejemplo, un problema común cuando se mantiene el código de la fuente es que los operadores de asignación se caen de la alineación que acumulan, lo que reduce la legibilidad del código:

Lista 16. Operadores de asignación fuera de la alineación
let applicants_name = 'Luke'
let mothers_maiden_name = 'Amidala'
let closest_relative = 'sister'
let fathers_occupation = 'Sith'

Reordenarlos manualmente cada vez que se agrega una nueva sentencia, puede ser tedioso:

Lista 17. Operadores de asignación reordenados manualmente
let applicants_name     = 'Luke'
let mothers_maiden_name = 'Amidala'
let closest_relative    = 'sister'
let fathers_occupation  = 'Sith'

Para reducir el aburrimiento de esa tarea de codificación diaria, usted podría crear una correlación de teclas (tales como ;=) que selecciona el bloque actual del código, ubica cualquier línea con operadores de asignación y alinea automáticamente esos operadores. Así como:

Lista 18. Función para alinear operadores de asignación
function AlignAssignments ()
    "Patterns needed to locate assignment operators...
    let ASSIGN_OP   = '[-+*/%|&]\?=\@<!=[=~]\@!'
    let ASSIGN_LINE = '^\(.\{-}\)\s*\(' . ASSIGN_OP . '\)'

    "Locate block of code to be considered (same indentation, no blanks)
    let indent_pat = '^' . matchstr(getline('.'), '^\s*') . '\S'
    let firstline  = search('^\%('. indent_pat . '\)\@!','bnW') + 1
    let lastline   = search('^\%('. indent_pat . '\)\@!', 'nW') - 1
    if lastline < 0
        let lastline = line('$')
    endif

    "Find the column at which the operators should be aligned...
    let max_align_col = 0
    let max_op_width  = 0
    for linetext in getline(firstline, lastline)
        "Does this line have an assignment in it?
        let left_width = match(linetext, '\s*' . ASSIGN_OP)

        "If so, track the maximal assignment column and operator width...
        if left_width >= 0
            let max_align_col = max([max_align_col, left_width])

            let op_width      = strlen(matchstr(linetext, ASSIGN_OP))
            let max_op_width  = max([max_op_width, op_width+1])
         endif
    endfor

    "Code needed to reformat lines so as to align operators...
    let FORMATTER = '\=printf("%-*s%*s", max_align_col, submatch(1),
    \                                    max_op_width,  submatch(2))'

    " Reformat lines with operators aligned in the appropriate column...
    for linenum in range(firstline, lastline)
        let oldline = getline(linenum)
        let newline = substitute(oldline, ASSIGN_LINE, FORMATTER, "")
        call setline(linenum, newline)
    endfor
endfunction

nmap <silent>  ;=  :call AlignAssignments()<CR>

La función AlignsAssignments() determina primero dos expresiones regulares (ver :help pattern para los detalles necesarios de la sintaxis de expresiónes regulares de Vim):

let ASSIGN_OP   = ¨[-+*/%|&]\?=\@<!=[=~]\@!¨
let ASSIGN_LINE = ¨^(.\{-}\)\s*\(¨ ASSIGN_OP '\)'

El patrón en ASSIGN_OP iguala cualquiera de los operadores de asignación estándar: =, +=, -=, *=, etc. pero evita cuidadosamente la clasificación de otros operadores que contienen =, tales como == y =~ Si su lenguaje favorito tiene otros operadores de asignación (tales como = o ||= o ^=), usted podría extender el ASSIGN_OP expresiónes regulares para reconocer esos también. . Como alternativa, usted podría redefinir ASSIGN_OP para reconocer otros tipos de "alineables", tales como introductores de comentarios o marcadores de columnas y los alinea en vez de eso.

El patrón en ASSIGN_LINE iguala sólo al comienzo de una línea (^), coincidiendo un número mínimo de caracteres (\{-}), luego cualquier espacio en blanco (\s*), luego a un operador de asignación.

Note que el subpatrón y el subpatrón del operador iniciales "número mínimo de caracteres, están especificados en la captura de paréntesis: \(...\) Las subseries capturadas por esos dos componentes de expresiónes regulares, serán extraídos más tarde al utilizar llamadas para la función submatch(); específicamente al llamar submatch(1) para extraer todo antes del operador y submatch(2) para extraer al operador.

Luego

AlignAssignments() ubica la gama de líneas en las que operará:

let indent_pat = '^' . matchstr(getline('.'), '^\s*') . '\S'
let firstline  = search('^\%('. indent_pat . '\)\@!','bnW') + 1
let lastline   = search('^\%('. indent_pat . '\)\@!', 'nW') - 1
if lastline < 0
    let lastline = line('$')
endif

En ejemplos anteriores, las funciones han dependido de una gama específica de comandos o una selección de modo Visual para determinar qué líneas operan, pero esta función computa su propia gama directamente. Específicamente, primero llama la función integrada matchstr() para determinar qué espacio en blanco destacado ('^\s*') aparece al comienzo actual de la línea actual (getline('.')). Eso Luego compila una expresión regular nueva en indent_pat que iguala exactamente la misma secuencia de espacio en blanco al comienzo de cualquier línea no vacía (por lo tanto, el restante '\S')

Luego

AlignAssignments() llama la función search() incorporada para buscar hacia arriba (utilizando los distintivos 'bnW') y ubica la primera línea arriba del cursor que no tiene precisamente la misma hendidura. Al agregar 1 a este número de línea se da el inicio de la gama de interés, a saber, la primera línea contigua con la misma hendidura que la línea actual.

Una segunda llamada al search() luego busca hacia abajo ('nW') para determinar lastline: el número de la línea contígua final con la misma hendidura. En este segundo caso, la búsqueda podría alcanzar el fin del archivo sin encontrar una línea hendida diferentemente, en cuyo caso search() retornaría -1. Para manejar este caso correctamente, la siguiente sentencia if configuraría explícitamente lastline para el número de línea del final del archivo (por lo tanto, al número de línea devuelto por line('$')).

El resultado de estas dos búsquedas es que AlignAssignments() ahora conoce la ama completa de líneas inmediatamente arriba o abajo de la línea actual que todas tienen precisamente la misma hendidura que la línea actual. Utiliza esta información para asegurar que alinea sólo aquellas sentencias de asignación en el mismo nivel de alcance en el mismo bloque de código. A menos que, por supuesto, la hendidura de su código no refleje correctamente su alcance, en cuyo caso usted merece completamente la catástrofe de formato que le pueda suceder.

La primera curva for en AlignAssignments() determina la columna en la que los operadores de asignación deberían estar alineados. Esto se hace al recorrer la lista de líneas en la gama seleccionada (las líneas recuperadas por getline(firstline, lastline)) y verificar si cada línea contiene un operador de asignación (posiblemente precedido por un espacio en blanco):

let left_width = match(linetext, ´\s*`. ASSIGN_OP)

Si no hay operador en la línea, la función incorporada match() fallará para encontrar una coincidencia y retornará a -1. En ese caso, la curva simplemente salta para la próxima línea. Si hay un operador, match() volverá el índice (positivo) al que ese operador aparece. La sentencia if utiliza la función incorporada max() para determinar si esta última posición de la columna está más derecha que cualquier operador ubicado previamente, así rastreando la posición máxima de la columna requerida para alinear todas las asignaciones en la gama:

let max_align_col = max([max_align_col, left_width])

Las dos líneas restantes de if utilizan la función integrada matchstr() para recuperar al operador actual, luego el strlen() incorporado para determinar su extensión (la que será 1 para un "=" pero 2 para '+=', '-=', etc.) La variable max_op_width luego es utilizada para rastrear el ancho máximo requerido para alinear a los varios operadores en el intervalo:

let op_width     = strlen(matchstr(linetext, ASSIGN_OP))
let max_op_width = max([max_op_width, op_width+1])

Una vez que la localización y el ancho de la área de alineación se determinaron, todos esos restos son para repetir por medio de las líneas en la gama y reformatearlas en conformidad. Para hacerlo, la función utiliza la función incorporada printf(). Esta función es muy útil, pero también muy mal nombrada. No es lo mismo que en la función printf en C, Perl o PHP. De hecho, es lo mismo que la función sprintf en esos lenguajes. Por lo tanto, en Vimscript, printf no imprime una versión formateada de su lista de argumentos de datos; retorna una serie que contiene una versión formateada de su lista de argumentos de datos.

Idealmente, para formatear nuevamente cada línea, AlignAssignments() utilizaría la función incorporada substitute(), y sustituye todo hasta el operador con una printf reorganización de ese texto. Infelizmente, substitute() espera una serie fijada como su valor de reemplazo, no una llamada de función.

Entonces, para utilizar un printf() para reformatear cada texto de reemplazo, usted necesita utilizar el formulario de reemplazo especial incorporado: "\=expr". El destacado \= en la serie de reemplazo dice que substitute() evalúe la expresión que sigue y utilice el resultado como el texto de reemplazo. Note que esto es similar al mecanismo< C-R>= en el modo Insertar, excepto este comportamiento mágico que sólo funciona para la serie de reemplazo de la función incorporada substitute() (o en el comando estándar de Vim :s/.../.../ ).

En este ejemplo, la forma especial de reemplazo será la misma printf para cada línea, por lo tanto es almacenada en la variable FORMATTER antes que comience for la segunda curva:

let FORMATTER = '\=printf("%-*s%*s", max_align_col, submatch(1),
\                                    max_op_width,  submatch(2))'

Cuando eventualmente es llamada por substitute(), esta incorporación printf() justificará a la izquierda (utilizando un marcador de posición %-*s) todo a la izquierda del operador (submatch(1)) y ubicar el resultado en un campo que es amplio en caracteres max_align_col . Por lo tanto justificará a la derecha (utilizando un %*s) el operador (submatch(2)) a un segundo campo que es max_op_width amplio de caracteres. Ver :help printf() para detalles de cómo las opciones - y * modifican los dos especificadores de formato %s utilizados aquí.

Con este formateador disponible ahora, la segunda curva para puede finalmente repetir a través de la gama completa de números de líneas, al recuperar los contenidos del almacenamiento intermedio de texto correspondientes de una línea a la vez:

for linenum in range(firstline, lastline)
    let oldline = getline(linenum)

La curva luego utiliza substitute() para transformar esos contenidos al igualar todo hasta incluir cualquier operador de asignación (utilizando el patrón en ASSIGN_LINE) y reemplazar ese texto con el resultado de la llamada printf() (como especificado por FORMATTER):

    let newline = substitute(oldline, ASSIGN_LINE, FORMATTER, "")
    call setline(linenum, newline)
endfor

Una vez que la curva for ha repetido todas las líneas, cualquier operador de asignación en ellos ahora estará alineado correctamente. Todo lo que sobra es para crear una correlación de teclas para invocar AlignAssignments(), así como:

nmap <silent>  ;=  :call AlignAssignments()<CR>

Mirando hacia adelante

Las funciones son una herramienta esencial para descomponer una aplicación en componentes correctos y sostenibles para gestionar la complejidad de las tareas de programación Vim del mundo real.

Vimscript le permite definir funciones con listas de parámetros fijos o variados y tenerlos para relacionarse automáticamente o en formas controladas por el usuario, con gamas de líneas en el almacenamiento intermedio de texto del editor. Las funciones pueden devolver la llamada a las características incorporadas de Vim (por ejemplo, para search() o substitute() texto), y también pueden acceder directamente a la información del estado del editor (tales como determinar la línea actual en que el cursor está en vía line('.')) o relacionarse con cualquier almacenamiento intermedio de texto que esté siendo editado actualmente (vía getline() y setline()).

Esta es, sin duda, una facilidad poderosa, pero nuestra habilidad para manipular de manera programable el estado y el contenido es siempre limitada por qué tan limpia y precisamente podemos representar los datos en los que nuestro código opera. Hasta el momento en esta serie de artículos, hemos sido restringidos al uso de valores escalares únicos (números, series y booleanos). En los próximos dos artículos, exploraremos el uso de estructuras de datos mucho más poderosas y convenientes: listas ordenadas y diccionarios de acceso aleatorio.

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

  • Envuélvase en el My developerWorks community; con su perfil personal y página de inicio personalizada, usted puede hacer developerWorks para sus intereses y relacionarse con otros usuarios de developerWorks.

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Lotus
ArticleID=427805
ArticleTitle=Programando el script del editor Vim, Parte 2: Funciones definidas por el usuario
publish-date=07072009