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:
- 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.
- 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.
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
function ExpurgateText (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>
|
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).
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.
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 & 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\+;\)\@!','&','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&.
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\+;\)\@!','&','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.
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> |
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.
Aprender
- Empezar con
"Scripting the Vim editor, Part 1: Variables, values,
and expressions"
(developerWorks, mayo de 2009) para aprender Vimscript, el lenguaje incorporado para
extender el editor de Vim. Crear nuevas herramientas, simplificar tareas comunes y
volver a diseñar y reemplazar funciones existentes del editor.
- Para saber más acerca del editor Vim y
sus diversos comandos, consulte:
- La Vim homepage
- El libro online A Byte of Vim
- Various hardcopy books on Vim
- Vim's own manual
- Vim Cookbook, de Steve Oualline
- Para ejemplos más amplios de programación de Vim,
ver:
- En la
developerWorks Linux zone,
encuentre más recursos para desarrolladores Linux, y escanee nuestros
most popular articles and
tutorials.
- Vea todo
Linux tips
y
Linux tutorials
en developerWorks.
- Esté al día con
developerWorks technical events and Webcasts.
Obtener los productos y tecnologías
- Empiece en la
página de descargas de distribuciones Vim
para actualizar a la versión más reciente de Vim para su plataforma.
- Con
IBM trial software,
disponible para descarga directamente desde developerWorks, compile su próximo proyecto
de desarrollo en Linux.
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.

Damian 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.