gU
, minúsculas gu
o intercambiarlosg~
.totitle-vim
. ¡Espero arrojar algo de luz sobre el proceso y tal vez inspirarte a crear tu propio complemento único!g/^#/ s/\<./\u\0/g
. Para un uso básico, este comando puede ser suficiente, pero no es lo suficientemente potente como el tener una forma de convertir a mayúscula la primera letra de un título. En inglés las palabras "The" o "Of" en "Capitalize The First Letter Of Each Word" deberían escribirse en mayúsculas. Sin una regla adecuada de capitalización de palabra, la frase tendría un aspecto extraño.gt
, el complemento no convertirá en mayúsculas la selección. Este comportamiento no sigue el comportamiento que tienen gu
, gU
y g~
. Así que decidí trabajar a partir del repositorio del complemento y utilizarlo para solucionar ese comportamiento y que fuera igual a gu
, gU
y g~
! Una vez más, el complemento vim-titlecase en sí mismo es un complemento excelente y digno de ser utilizado por sí solo (la verdad es que tal vez en el fondo solo quería escribir mi propio complemento de Vim. Realmente no puedo imaginar en qué casos la función de título en bloques pueda ser usado a menudo en la vida real que no sean casos extremos).a an and at but by en for in nor of off on or out per so the to up yet vs via
.gu
, gU
, y g~
. Al ser un operador, podrá aceptar tanto movimientos como objetos de texto (gtw
debería poner en mayúscula la palabra siguiente, gtiw
debería poner en mayúsculas la palabra donde se encuentra el cursor, gt$
debería poner en mayúsculas las primeras letras de las palabras desde la ubicación actual del cursor hasta el final de la línea, gtt
debería poner en mayúsculas la línea actual, gti(
debería convertir a mayúsculas el texto dentro de los paréntesis, etc.) También quiero que tenga la combinación de teclas gt
para recordarlo más fácilmente. Además, también debería funcionar en todos los modos visuales: v
, V
y Ctrl-V
. Debería ser capaz de resaltar el texto en cualquier modo visual, pulsar gt
, y después el complemento convertir a mayúsculas las primeras letras de todo el texto resaltado.plugin/
y doc/
. Cuando arrancas Vim, este busca archivos especiales y directorios dentro del directorio ~/.vim
y ejecuta todos los archivos de scripts dentro del directorio. Para más información, revisa el capítulo 24 de esta guía que trata sobre este tema.doc/
y plugin/
. doc/
es el lugar donde poner la ayuda de la documentación (para así poder buscar palabras claves más adelante, como :h totitle
). Trataré sobre cómo crear la página de ayuda más adelante. Por ahora, vamos a enfocarnos en la carpeta plugin/
. La carpeta plugin/
es ejecutada una sola vez al inicio de Vim. Hay un solo archivo dentro de este directorio: totitle.vim
. El nombre no importa (podría haber llamado el nombre loquesea.vim
y seguiría funcionando). Todo el código responsable del funcionamiento del complemento está dentro de este archivo.g:totitle_default_keys
todavía no existirá, así que !exists(...)
devuelve un valor verdadero. En ese caso, define g:totitle_default_keys
igual a 1. En Vim, 0 es análogo a falso y cualquier valor distinto de cero es verdadero (en este caso utilizo el 1 para indicar verdadero).gt
es definido. En este caso, al llegar a los if
condicionales al final del archivo, if g:totitle_default_keys
devolverá un 1 (verdadero), así que Vim realizará los siguientes mapeados:nnoremap <expr> gt ToTitle()
mapea el operador del modo normal. Esto te permite ejecutar el operado + un movimiento u objeto de texto como gtw
para convertir a mayúsculas la primera letra del título de la siguiente palabra, o gtiw
para hacerlo de la palabra en la que se encuentra el cursor. Trataremos más en detalle los mapeados del operador más adelante.xnoremap <expr> gt ToTitle()
mapea los operadores del modo visual. Esto te permite convertir a mayúsculas la primera letra del texto que está resaltado en el modo visual.nnoremap <expr> gtt ToTitle() .. '_'
mapea el operador en el modo de selección de línea (de manera análoga a guu
y gUU
). Quizás te preguntes qué hace .. '_'
al final de esa línea. ..
En Vim es el operador de interpolación de cadenas de Vim. _
es utilizado como un movimiento con un operado. Si miras en :help _
, verás que el guión bajo es utilizado para contar 1 línea hacia abajo. realiza un operador en la línea actual (pruebalo con otros operadores, intenta ejecutar gU_
o d_
, verás que hace lo mismo que gUU
o dd
).<expr>
te permite especificar el contaje, así podrás ejecutar por ejemplo 3gtw
para ejecutar el comando en las siguientes 3 palabras.gt
? Después de todo, estás sobreescribiendo el mapeado predeterminado que ya existe en Vim de gt
(pestaña siguiente). ¿Qué pasa si quieres utilizar gz
en vez de gt
? Recuerdas cómo anteriormente se tomó la molestia de comprobar if !exists('g:totitle_default_keys')
o if g:totitle_default_keys
? Si pones let g:totitle_default_keys = 0
en tu archivo vimrc, entonces g:totitle_default_keys
ya existiría cuando el complemente esté ejecutándose (el código en tu vimrc son ejecutados antes de los archivos ejecutables plugin/
), así !exists('g:totitle_default_keys')
devolvería un falso. Además, if g:totitle_default_keys
sería falso (porque tendría el valor de 0), ¡así que tampoco se realizaría el mapeado de gt
! Esto te permitirá de una forma efectiva definir tus propios mapeados en el archivo Vimrc.gz
, añade estas líneas en tu vimrc:ToTitle()
es seguramente la función más larga en este archivo.opfunc
? ¿Por qué está devolviendo [email protected]
?[email protected]
. Este operador te permite utilizar cualquier función asignada a la opción opfunc
. Si tengo la función Foo()
asignada a opfunc
, entonces ejecutaré [email protected]
, estoy ejecutando Foo()
en la siguiente palabra. Si ejecuto [email protected](
, entonces estoy ejecutando Foo()
entre los paréntesis interiores. Esta función de operador es crítica para crear tus propios operadores de Vim.opfunc
a la función ToTitle
.[email protected]
:[email protected]
?gtw
(para poner en mayúsculas la primera letra de la siguiente palabra). La primera vez que ejecutas gtw
, Vim llama al método ToTitle()
. Pero justo ahora opfunc
todavía está en blanco. Tampoco estás pasando ningún argumento a ToTitle()
, así que a:type
tendrá un valor de ''
. Esto causa que la expresión condicional compruebe el argumento a:type
, if a:type ==# ''
, para ser cierto. Dentro, asignas opfunc
a la función ToTitle
con set opfunc=ToTitle
. Ahora opfunc
está asignada a ToTitle
. Finalmente, después de asignar opfunc
a la función ToTitle
, devolverás [email protected]
. Explicaré porque devuelve el valor [email protected]
a continuación.gtw
. Al pulsar gt
hizo todo el proceso que se ha detallado anteriormente, pero todavía tienes que procesar w
. Al devolver [email protected]
, en este punto, técnicamente tienes [email protected]
(por eso es que tienes return [email protected]
). Ya que [email protected]
es un operador de función, le estás pasando el movimiento w
. Así que Vim, una vez que recibe [email protected]
, llama a ToTitle
una vez más (no te preocupes, no acabarás en un bucle infinito como verás en breve).gtw
, Vim comprueba si opfunc
está vacía o no. Si está vacía, entonces Vim la asignará a ToTitle
. Y entonces devolverá [email protected]
, esencialmente llamando a ToTitle
de nuevo una vez más para ahora poder utilizarla como un operador. Esta es la parte más enrevesada de crear un operador personalizado ¡y lo hiciste! A continuación, necesitas crear la lógica para ToTitle()
para que haga su cometido con la entrada.gt
funcionando como un operador que ejecuta ToTitle()
. Pero ¿qué es lo próximo a hacer? ¿Cómo realmente conviertes a mayúsculas la primera letra de las palabras del título?[email protected]
(palabra) es un ejemplo de una operación de carácter. [email protected]
(una línea inferior) es un ejemplo de operación de línea. La operación de bloque es inusual, pero se produce cuando ejecutas la operación Ctrl-V
(bloque visual). Las operaciones que tienen como objetivo unos cuantos caracteres hacia adelante/atrás son básicamente consideradas operaciones de caracteres (b
, e
, w
, ge
, etc). Las operaciones que tienen como objetivo unas cuantas líneas superiores/inferiores son consideradas generalmente operaciones de línea (j
, k
). Las operaciones que tienen como objetivo columnas o bloques hacia adelante/atrás arriba/abajo son consideradas operaciones de bloque (por lo general, son un modo de movimiento forzado en columna o un modo visual en bloque, para más información consultar: :h forced-motion
).[email protected]
, [email protected]
pasará una cadena literal "char"
como un argumento a ToTitle()
. Si ejecutas [email protected]
, [email protected]
pasará una cadena literal "line"
como argumento a ToTitle()
. Esta cadena es lo que se le pasará a la función ToTitle
como argumento type
.[email protected]
escribiendo una función un poco tonta:opfunc
ejecutando:[email protected]
ejecutará Test(some_arg)
y pasará tanto "char"
, "line"
, o "block"
dependiendo de la operación que realices. Ejecuta diferentes operaciones como [email protected]
(dentro de una palabra), [email protected]
(una línea por debajo), [email protected]$
(hasta el final de la línea), etc. Comprueba los diferentes valores qu se muestran. Para comprobar una operación de bloque, puedes utilizar el movimiento forzado para operaciones de bloque: [email protected]
(operación de bloque de una columna por debajo).v
, V
, y Ctrl-V
y después pulsa [email protected]
(te aviso que mostrará la salida muy rápido, así que tendrás que tener una vista muy rápida, pero la salida del comando definitivamente está ahí. También como hemos utilizado echom
, puedes comprobar los mensajes mostrados con :messages
).ToTitle()
, sino para habilitarlo en una función TitleCase invocable (sí, sé que estoy violando el Principio de Responsabilidad Única). La motivación es, que Vim tiene unas funciones nativas llamadas toupper()
y tolower()
que convierten a mayúsculas y minúsculas respectivamente cualquier cadena de texto dada. Por ejemplo: :echo toupper('hola')
devolverá 'HOLA'
y :echo tolower('HOLA')
devolverá 'hola'
. Yo quiero que este complemento tenga la posibilidad de ejecutar ToTitle
para poder hacer lo siguiente :echo ToTitle('érase una vez')
y obtener 'Érase Una Vez'
como valor de retorno.ToTitle(type)
con [email protected]
, el argumento type
tendrá un valor de retorno como 'block'
, 'line'
, o 'char
'. Si el argumento no es ninguna de esas opciones, puedes asumir con total seguridad que ToTitle()
está siendo llamada fuera de [email protected]
. En ese caso, divídelos con espacios en blanco (\s\+
) con:capitalize()
la veremos más adelante.selection
para que sea inclusiva y clipboard
para que esté vacío. El atributo de selección es normalmente utilizado con el modo visual y tiene tres posibles valores: old
, inclusive
y exclusive
. Al establecerlo como inclusive
esto significa que el último carácter de la selección está incluido. No lo trataré aquí, pero la clave es que al escogerlo como inclusive
esto hace que tenga un comportamiento consistente en el modo visual. De manera predeterminada Vim activa esta opción, pero también la establecemos aquí por si alguno de tus complementos lo estableces a un valor diferente. Echa un vistazo a :h 'clipboard'
y :h 'selection'
si tienes curiosidad que qué hacen realmente.#{}
es el tipo de datos del diccionario de Vim. La variable local l:commands
es un hash con 'lines', 'char' y 'block' como sus teclas. El comando silent exe '...'
ejecuta cualquier comando dentro de la cadena de manera silenciosa (de lo contrario se mostraría una notificación en la parte inferior de la pantalla).'noautocmd keepjumps normal! ' .. get(l:commands, a:type, '')
. El primero, noautocmd
, ejecutará el siguiente comando cuando sin ser lanzado por cualquier comando automático. El segundo, keepjumps
, es para no guardar el movimiento del cursor mientras se está moviendo. En Vim, ciertos movimientos son grabados de manera automática en la lista de cambios y la lista de marcas. Esto, previene ese comportamiento. La razón de tener noautocmd
y keepjumps
es para prevenir efectos secundarios. Finalmente, el comando normal
ejecuta las cadenas como comandos normales. Esto ..
en Vim es una sintaxis de interpolación de cadena. get()
es una método de obtención que acepta tanto una lista, un blob o un diccionario. en este caso, se le está pasando el diccionario l:commands
. La clave es a:type
. Has aprendido anteriormente que a:type
puede ser cualquiera de estos tres valores char
, line
o block
. Así que si a:type
es una line
, ejecutarás "noautocmd keepjumps normal! '[V']y"
(para más información, echa un vistazo a :h silent
, :h :exe
, :h :noautocmd
, :h :keepjumps
, :h :normal
y :h get()
).'[V']y
. Primero vamos a asumir que tienes el siguiente texto:[email protected]
(ejecuta la función operados, [email protected]
, una línea por debajo, mediante j
). '[
mueve el cursor al comienzo del texto anteriormente cambiado o copiado. Aunque técnicamente no has cambiado o no has copiado ningún texto con [email protected]
, Vim recuerda la ubicación del inicio y final de los movimientos del comando [email protected]
con '[
y ']
(para más información, consulta la ayuda :h [email protected]
). En tu caso, al pulsar '[
mueve el cursor a la primera línea porque ahí es donde empezaste a ejecutar [email protected]
. V
es el comando del modo visual de selección de línea. Finalmente ']
mueve tu cursor al final del texto previamente cambiado o copiado, pero en este caso, mueve el cursor al final de la última operación de [email protected]
. Finalmente, y
copia el texto seleccionado.[email protected]
.[email protected]
."
) y lo almacena dentro de la variable l:selected_phrase
. Espera un minuto... ¿no habías copiado ya el cuerpo del texto? El registro sin nombre contiene el texto que habías copiado. Así es como el complemento es capaz de obtener la copia del texto.\<
y \>
son patrones de límites de palabras. El carácter que sigue a \<
marca el comienzo de una palabra y el carácter precedente \>
marca el final de una palabra. \k
es el patrón de palabra clave. Puedes comprobar qué caracteres acepta Vim como palabras clave mediante :set iskeyword?
. Recuerda que el movimiento w
en Vim mueve tu cursor una palabra hacia adelante. Vim viene con una noción preconcebida de lo que es una "palabra clave" (incluso puede editarla modificando la opción iskeyword
). Echa un vistazo a :h /\<
, :h /\>
, y :h /\k
, y :h 'iskeyword'
para saber más al respecto. Finalmente *
significa cero o más del patrón siguiente.'\<\k*\>'
encuentra una palabra. Si tienes la siguiente cadena de texto:\={tu-expresión}
. Por ejemplo, si quieres poner en mayúsculas la cadena de texto "donut" en la línea actual, puedes utilizar la función de Vim toupper()
. Puedes realizar esto ejecutando :%s/donut/\=toupper(submatch(0))/g
. submatch(0)
es una expresión especial utilizada en el comando de sustitución. Devuelve todo el texto encontrado.line()
devuelve un número de línea. Aquí se lo pasas con la marca '<
, que representa la primera línea de la última área visual seleccionada. Recuerda que utilizaste el modo visual para copiar el texto. '<
devuelve el número de línea del principio de esa área de selección visual. La expresión virtcol()
devuelve el número de columna de la posición actual del cursor. Dentro de un rato moverá el cursor por todas partes, por lo que debe almacenar la ubicación del cursor para poder volver aquí más tarde.a:type
que puede ser tanto 'char', 'line', o 'block'. En la mayoría de los casos, lo más probable es que tengas 'char' o 'line'. Pero quizás alguna vez también pueda ser un bloque. No es habitual, pero puede ocurrir. Desafortunadamente, la gestión de un bloque no es tan sencillo como es para un carácter o una línea. Llevará un poco más de esfuerzo extra el hacerlo, pero es factible.Ctrl-V
) para seleccionar hacia abajo y la derecha la palabra "cake" en las tres líneas. Aquí está representada la selección visual con corchetes:gt
, esto es lo que obtendrás. Convierte en mayúscula la primera letra de la palabra seleccionada:sil!
sirve para ejecutar de manera silenciosa (silently) y keepj
guarda el historial de saltos mientras te estás moviendo. Después ejecutas el comando normal gv"ad
. gv
selecciona el último texto resaltado visualmente (en el ejemplo, los tres 'cakes'). "ad
borra el texto resaltado visual y lo almacena en el registro a. Como resultado de todo eso, ahora tienes:$
mueve el cursor a la última línea del fichero. pu_
inserta una línea debajo de donde está el cursor. Quieres ejecutar todo esto con keepj
ya que no quieres alterar el historial de saltos que guarda Vim.line("quot;)
) en la variable local lastLine
.norm "ap
.VG
las resalta en el modo visual de selección de línea, desde la línea actual hasta el final del archivo. Así que aquí estás resaltando los tres bloques del texto 'cake' con la selección de línea (recuerda que es distinto el modo de selección de bloque y de selección de línea). Ten en cuenta que la primera vez que pegaste los tres textos "cake", los pegaste como bloques. Ahora estás resaltando el texto, pero como líneas. Visto desde fuera puede parecer lo mismo, pero internamente, Vim sabe la diferencia entre uno y otro procedimiento a la hora de pegar texto.[email protected]
es la función operador, así que esencialmente estás haciendo una llamada recursiva a ella misma. ¿Por qué? ¿Qué logra esto?[email protected]
y pasándole las tres líneas (después de ejecutar lo anterior con V
, ahora tienes líneas, no bloques) del texto 'cake' así será gestionado por la otra parte del código (que veremos más adelante). El resultado de ejecutar [email protected]
es tener tres líneas de texto con la primera letra de la palabra en mayúsculas:0
), utiliza el resaltado del modo visual de selección de bloque para ir a la última línea y al último carácter en esa línea (<c-v>G$
). La h
es para ajustar el cursor (cuando ejecutas $
Vim mueve una línea extra a la derecha). Finalmente, eliminas el texto resaltado y lo almacenas en el registro a ("ad
).startLine
.startLine
, ahora saltas a la columna marcada por startCol
. \<bar>\
es el movimiento de barra |
. El movimiento de barra en Vim mueve el cursor a la columna dada con un número (digamos que startCol
vale 4. Al ejecutar 4|
hará que el cursor salte a la posición de la columna de 4). Recuerda que startCol
era la ubicación donde se almacenó la posición de la columna del texto que querías poner en mayúscula. Finalmente, "aP
pega los textos almacenados en el registro a. Esto pega el texto de nuevo de donde fue eliminado anteriormente.exe "keepj " . l:lastLine
mueve de nuevo el cursor a la ubicación de lastLine
anterior. sil! keepj norm! "_dG
elimina el(los) espacio(s) extra que fueron creado utilizando el registro de agujero negro ("_dG
) así tu registro sin nombre permanece limpio. exe "keepj " . l:startLine
mueve el cursor de nuevo a startLine
. Finalmente, exe "sil! keepj norm! " . l:startCol . "\<bar>"
mueve el cursor a la columna startCol
.gt
en un bloque de textos. Todavía hay que ver cómo trata las operaciones de línea 'line' y caracteres 'char'. Echemos un vistazo al código else
para ver cómo se hace esto.@@
contiene el texto obtenido del registro sin nombre para ser procesado por el complemento. l:WORD_PATTERN
es la coincidencia individual de la palabra clave. l:UPCASE_REPLACEMENT
es la llamada al comando capitalize()
(que veremos más adelante). La 'g'
es la opción global que indica al comando de sustitución que sustituya todas las palabras dadas, no solo la primera palabra encontrada.l:titlecased
."
).l:commands
. En vez de copiar, aquí utilizas el pegado (p
). Echa un vistazo a la sección previa donde ya se explicó l:commands
para refrescar conceptos.[email protected]
, después pegas el texto desde el registro sin nombre (lo que reemplaza el texto sin mayúsculas con el texto procesado). Y finalmente mueves el cursor de nuevo a la posición a la que estaba originalmente.<
y >
.'clipboard'
y 'selection'
.s:capitalize()
. Este es el aspecto que tiene dicha función:capitalize()
, a:string
, es una palabra individual que ofrece el operador [email protected]
. Así que si ejecuto gt
con el texto "pancake for breakfast", ToTitle
llamará a capitalize(string)
tres veces, una para "pancake", otra para "for", y otra más para "breakfast".toupper(a:string) ==# a:string
) comprueba si la versión en mayúsculas del argumento es la misma que la cadena y si la propia cadena es "A". Si estos son verdaderos, devuelva esa cadena. Esto se basa en la suposición de que si una palabra dada ya está totalmente en mayúsculas, entonces es una abreviatura. Por ejemplo, la palabra "CEO" se convertiría de otro modo en "CEO". Hmm, tu director ejecutivo no estará contento. Por lo tanto, es mejor dejar en paz cualquier palabra en mayúsculas. La segunda condición a:string != 'A'
, aborda un caso límite para un carácter "A" en mayúscula. Si a:string
ya es una "A" mayúscula debería pasar el test toupper(a:string) ==# a:string
. Ya que "a" es un artículo indefinido en Inglés, y necesita ponerse en minúscula.l:exclusions
). Si lo es, no la convierte a mayúscula. Después comprueba si la cadena de texto es parte de la lista local de exclusión (s:local_exclusion_list
). Esta lista de exclusión es una lista personalizada que el usuario puede añadir en vimrc (en este caso el usuario tiene requisitos adicionales para palabras espaciales).substitute()
, la palabra inicial "an" en la frase de ejemplo quedaría en minúscula entera. Necesitas forzar que el primer carácter sea convertido a mayúscula.capitalizeFirstWord
, el argumento a:string
no es una palabra individual como en a:string
dentro de la función capitalize
, si no todo el texto. Así que si tienes "pancake for breakfast", el valor de a:string
es "pancake for breakfast". Solo se ejecuta capitalizeFirstWord
una vez para todo el texto."an apple a day\nkeeps the doctor away"
. En este caso quieres poner en mayúsculas los primeros caracteres de todas las líneas. Si no tienes nuevas líneas, simplemente pon en mayúsculas el primer carácter.docs/
. Es aconsejable el ofrecer junto con el complemento la documentación de uso. En esta sección, trataremos brevemente de repasar cómo hacer la documentación para tu propio complemento.docs/
es una de las rutas especiales para los ejecutables. Vim lee todos los archivos dentro de docs/
así que cuando buscas una palabra especial y esa palabra es encontrada en uno de los archivos del directorio docs/
, lo mostrará en la página de ayuda. Aquí tienes el archivo totitle.txt
. Lo he nombrado de esa manera porque ese es el nombre del complemento, pero puedes ponerle el nombre que quieras.help
. Si quieres que Vim interprete este totitle.txt
como un fichero de ayuda, ejecuta :set ft=help
(:h 'filetype'
para más ayuda). Por cierto, si quieres decir a Vim que interprete este totitle.txt
como un archivo de texto normal, ejecuta :set ft=txt
.totitle
cuando busques :h totitle
, escríbelo así *totitle*
en el archivo de ayuda.*totitle*
y *totitle-toc*
para marcar la sección del índice de contenidos de la ayuda. Puedes utilizar tantas palabras clave como quieras. Esto significa que cada vez que busques tanto :h totitle
como :h totitle-toc
, Vim te llevará a esta sección.:h totitle-usage
, Vim te llevará a esta sección.|
. En la sección del índice de contenidos, ves que se usan las palabras clave rodeadas con esas barras, como |totitle-intro|
, |totitle-usage|
, etc.|totitle-intro|
y pulsas Ctrl-]
, Vim saltará a la definición de ese término. En este caso, saltará a donde se ubica *totitle-intro*
. Así es como puedes enlazar a diferentes palabras clave en un documento de ayuda.~/.vim/docs/
esto no hará que Vim pueda buscar automáticamente las palabras claves que hayas marcado. Necesitas decir a Vim que añada tu página de documentación. Ejecuta el comando: :helptags ~/.vim/doc
para crear los archivos de etiquetas. Ahora ya sí podrás empezar a buscar por tus palabras clave.