sábado, 11 de abril de 2009

Distribuindo um script

Os usuários do Vim procuram scripts no site do Vim, http://www.vim.org.
Os scripts Vim podem ser usados em qualquer sistema. Pode não existir nenhum comando tar ou
gzip. Se quiser compactar seus arquivos o utilitário 'zip' é recomendado.

Para a maior portabilidade use o próprio Vim para compactar os scripts.
Isto pode ser feito com o utilitário Vimball.
Será bom se você adicionar uma linha para permitir atualização automática.

Criando uma script library

Certas funcionalidades serão necessárias em vários lugares.
Podemos coloca-las em um script e usa-las a partir de muitos outros scripts.
Esse script será chamado de script library (biblioteca).

Carregar manualmente uma script library é possível, desde que você evite de
carrega-la quando já estiver carregada. Você pode fazer isso com a função 'exists()':
if !exists('*MyLibFunction')
   runtime library/mylibscript.vim
endif
call MyLibFunction(arg)
Aqui você precisa saber que 'MyLibFunction()' está definido em um script
'library/mylibscript.vim' em um dos diretórios de 'runtimepath'.

Para fazer isso mais simples Vim oferece o mecanismo de autoload. Então o
exemplo se parecerá com isso:
call mylib#myfunction(arg)
Isto é muito mais simples. O Vim reconhecerá o nome da função e quando não
estiver definida procura pelo script 'autoload/mylib.vim' no 'runtimepath'.
Este script deve definir a função 'mylib#myfunction()'.

Você pode por muitas outras funções no script 'mylib.vim'. Mas você deve usar
nomes de função aonde a parte antes de '#' corresponde com o nome do script.
Caso contrário o Vim não saberá qual script carregar.

Se tiver muitas script libraries, você poderia querer usar subdiretórios:
call netlib#ftp#read('somefile')
No Unix, o script library usado para isto poderia ser:
~/.vim/autoload/netlib/ftp.vim
Aonde a função é definida assim:
function netlib#ftp#read(fname)
      " Read the file fname through ftp
endfunction

Note que o nome da função usado é o mesmo nome usada na chamada da função. E a
parte antes do último '#' corresponde exatamente ao subdiretório e ao nome do
script.

Você pode usar o mesmo mecanismo para variáveis:
let weekdays = dutch#weekdays
Isto carregará o script 'autoload/dutch.vim', o qual deveria conter algo como:
let dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',
        \ 'donderdag', 'vrijdag',
'zaterdag']

Criando um plugin que carrega rapidamente

Um plugin pode crescer e se tornar muito grande.
O tempo de inicialização pode se tornar perceptível, mesmo que você raramente use o
plugin.

É o momento para um plugin de carga rápida.

A ideia básica é que o plugin seja carregado duas vezes.

A primeira vez os comandos do usuário e mapeamentos são definidos para que
ofereçam a funcionalidade.
A segunda vez as funções que implementam a funcionalidade são definidas.

Pode parecer surpreendente que para carregar rápido um plugin signifique
carregar o script duas vezes.
O que se busca é que ele carregue rapidamente a primeira vez, postergando o
grosso do script para a segunda vez, o que acontece quando você realmente o
utiliza.
Sempre que você utilizar a funcionalidade ele fica na realidade mais lento!

Note que desde do Vim 7 existe uma alternativa para o explicado aqui: use a funcionalidade 'autoload'.

O exemplo mostra como é feito:
" Vim global plugin for demonstrating quick loading
" Last Change: 2005 Feb 25
" Maintainer: Bram Moolenaar <Bram@vim.org>
" License: This file is placed in the public domain.
if !exists("s:did_load")
      command −nargs=* BNRead call BufNetRead(<f−args>)
      map <F19> :call BufNetWrite('something')<CR>
      let s:did_load = 1
      exe 'au FuncUndefined BufNet* source ' . expand('<sfile>')
      finish
endif
function BufNetRead(...)
      echo 'BufNetRead(' .
      string(a:000) . ')'
      " read functionality here
endfunction
function BufNetWrite(...)
      echo 'BufNetWrite(' . string(a:000) . ')'
      " write functionality here
endfunction
Quando o script é carregado pela primeira vez 's:did_load' não está definido. Os
comandos entre 'if' e 'endif' serão executados. Isto acaba em um comando
':finish', assim o resto do script não é executado.

A segunda vez que o script é carregado 's:did_load' existe e os comandos depois
de 'endif' são executados. Estes definem (possivelmente longas) funções
'BufNetRead()' e 'BufNetWrite()'.

Se você jogar esse script no seu diretório plugin Vim o executará na
inicialização. Esta é a sequencia de eventos que acontecem:

1. O comando 'BNread' é definido e a tecla <F19> é mapeada quando o script é
sourced na inicialização. Um autocomando 'FuncUndefined' é definido. O comando
':finish' faz com que o script termine antes.

2. O usuário digita o comando 'BNRead' ou pressiona a tecla <F19>. A função
'BufNetRead()' ou 'BufNetWrite()' será chamada.

3. O Vim não acha a função e dispara o evento do autocomando 'FuncUndefined'.
Desde que o padrão 'BufNet*' corresponde à função invocada, o comando 'source
fname' será executado. 'fname' será o nome do script, não importando ao está
localizado, pois ele vem da expansão de '<sfile>.

4. O script é novamente sourced, a variável 's:did_load' existe e as funções
estão definidas.

Note que funções que são carregadas depois correspondem ao padrão no autocomando
'FuncUndefined'. Você deve estar certo que nenhum outro plugin define funções
que correspondem a esse padrão.

Criando um plugin compilador

Um plugin compilador define as opções para o uso com um compilador específico.
O usuário pode carrega-lo com o comando ':compiler'.

Seu uso principal é definir as opções 'errorformat' e 'makeprg'.

Olhe nos exemplos instalados pela distribuição. O comando seguinte editará todos
plugins compiladores default:
:next $VIMRUNTIME/compiler/*.vim
Use o comando ':next' para ir para o próximo arquivo de plugin.

Existe dois itens especiais sobre estes arquivos.
Primeiro é um mecanismo para permitir que um usuário regrave ou adicione ao
arquivo default. Os arquivos default começam com:
:if exists("current_compiler")
: finish
:endif
:let current_compiler = "mine"
Quando você grava um arquivo compilador e coloca-o em seu diretório runtime
pessoal (no Unix, ~/.vim/compiler), você define a variável 'current_compiler'
para fazer que o arquivo default pule as definições.

O segundo mecanismo é usar ':set!' para ':compiler!' e ':setlocal' para
':compiler'.
O vim define o comando do usuário ':CompilerSet' para isto. Todavia, versões
mais antigas do Vim não fazem, assim seu plugin deveria defini-lo.
Eis um exemplo:
if exists(":CompilerSet") != 2
    command −nargs=* CompilerSet setlocal <args>
endif
CompilerSet errorformat& " use the default 'errorformat'
CompilerSet makeprg=nmake
Quando você escreve um compilador plugin para a distribuição Vim ou para o
diretório runtime a nível de sistema, use o mecanismo mencionado acima.
Quando 'current_compiler' já foi definido pelo plugin do usuário nada será
feito.

Quando você escreve um compilador plugin para sobrepor as definições de um
plugin default, não verifique o 'current_compiler'. Este plugin se supõe que
seja carregado por último, assim ele deveria estar em um diretório no final de
'runtimepath'.
No Unix, isto poderia ser '~/.vim/after/compiler'.

Definindo o nome do arquivo do filetype plugin

O filetype (tipo do arquivo) deveria estar incluído no nome do arquivo do
filetype plugin.
Use uma dessas três formas:
.../ftplugin/stuff.vim
.../ftplugin/stuff_foo.vim
.../ftplugin/stuff/bar.vim

Desfazendo mudanças feitas nas opções pelo filetype plugin

Quando o usuário faz ':set filetype xyz' o efeito do tipo do arquivo anterior
deveria ser desfeito.

Defina o valor da variável 'b:undo_ftplugin' com os comandos que irão desfazer as
definições em seu filetype plugin:
let b:undo_ftplugin = "setlocal fo< com< tw< commentstring<"
        \ . "| unlet b:match_ignorecase
b:match_words b:match_skip"
Ao usar 'setlocal' com '<' depois do nome da opção define a opção com seu valor
global. Este é na maioria das vezes a melhor maneira de voltar o valor de uma
opção.
Isto requer remover a flag 'C' de 'cpoptions' para permitir a continuação de
linha como mencionado em salvando 'cpoptions'.

Funções nos filetype plugin

A função só precisa ser definida uma vez.
Mas o filetype plugin será sourced a cada vez que um arquivo deste tipo for aberto.

Esta construção certifica que a função é definida somente uma vez:
:if !exists("*s:Func")
: function s:Func(arg)
: ...
: endfunction
:endif

As variáveis de um filetype plugin

Um filetype plugin será sourced para cada buffer do tipo dele.

As variáveis de script locais, 's:var', serão compartilhadas entre todas
invocações.
Use variáveis locais de buffer, 'b:var' se você quiser uma variável
específica para um buffer.

Comandos de usuários para um filetype plugin

Para adicionar um comando de usuário para um tipo específico de arquivo, para
que ele possa somente ser usado em um buffer, use o argumento '-buffer' do
comando ':command':
:command −buffer Make make %:r.s

sexta-feira, 10 de abril de 2009

Mapeamentos no filetype plugin

Certifique que os mapeamentos somente funcionarão no buffer corrente. Para isto use:
:map <buffer>
Isso tem que ser combinado com o mapeamento em dois passos explicado abaixo:
if !hasmapto('<Plug>JavaImport')
    map <buffer> <unique> <LocalLeader>i <Plug>JavaImport
endif
noremap <buffer> <unique> <Plug>JavaImport oimport ""<Left><Esc>
A função interna 'hasmapto()' é usada para verificar se o usuário já definiu o
mapeamento para '<Plug>JavaImport'. Se não definiu, o filetype plugin define o
mapeamento default.

Isso começa com um <LocalLeader>. Ele será substituído pelo valor que o usuário puser na variável 'maplocalleader'. Isso permitirá que o usuário selecione as teclas que ele quer que comece o mapeamento do filetype plugin. O default é o backslash
('\').

'<unique>' é usado para dar uma mensagem de erro se o mapeamento já existe ou
sobrepõe um mapeamento existente.

':noremap' é usado para evitar que outros mapeamentos que o usuário definiu
interfiram. Poderia também usar ':noremap <script>' para permitir somente
remapeamentos de mapeamentos definidos dentro do script e que comecem com '<SID>'.

O usuário deveria ter a chance de desabilitar os mapeamentos dentro do filetype
plugin sem desabilitar tudo. Eis um exemplo de um filetype plugin para email:
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_mail_maps")
" Quote text by inserting "> "
if !hasmapto('<Plug>MailQuote')
    vmap <buffer> <LocalLeader>q <Plug>MailQuote
    nmap <buffer> <LocalLeader>q <Plug>MailQuote
endif
vnoremap <buffer> <Plug>MailQuote :s/^/> /<CR>
nnoremap <buffer> <Plug>MailQuote :.,$s/^/> /<CR>
endif
Duas variáveis globais são usadas:
'no_plugin_maps' que desabilita mapeamentos para todos filetype plugins.
'no_mail_maps' que desabilita mapeamentos para um tipo de arquivo específico.

Alterando opções no filetype plugin

Para ter certeza que o filetype plugin afeta somente o buffer corrente use o
comando:
:setlocal
para definir opções.

E somente defina opções que são locais para o buffer (veja o help da opção para
verificar isto). Quando se usa 'setlocal' em opções globais ou em opções locais
para janela, o valor mudará em muitos buffers, e isto não é o que o filetype plugin
deveria fazer.

Quando uma opção é uma lista de flags ou itens, considere usar '+=' ou '-=' para
manter o valor existente.
Fique atento que o usuário já poderia ter mudado o valor de uma opção.
Voltar o valor default de uma opção e então muda-la é uma boa idéia:
:setlocal formatoptions& formatoptions+=ro

Permitindo desabilitar o filetype plugin

Você deveria permitir que as pessoas possam desabilitar o seu plugin.
Coloque isto no topo do plugin:
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
    finish
endif
let b:did_ftplugin = 1
Isto também é necessário para evitar que o mesmo plugin seja executado duas
vezes para o mesmo buffer (acontece quando usando um comando ':edit' sem
argumentos).

Agora os usuários podem desabilitar a carga do filetype plugin default somente
com esta linha:
let b:did_ftplugin = 1
Isto requer que o diretório do filetype plugin venha antes do $VIMRUNTIME no
'runtimepath'.

Se você quiser usar o plugin default, mas regravar um das opções, você definir
uma opção diferente em um script:
setlocal textwidth=70
Agora grave isto no diretório 'after', para que ele seja sourced depois do
ftplugin 'vim.vim' distribuído.

No Unix isso seria '~/.vim/after/ftplugin/vim.vim'.
Note que o plugin default teria definido 'b:did_ftplugin', mas isto é ignorado
aqui.

O filetype plugin

Um filetype plugin é como um plugin global exceto que ele define as opções e
mapeamentos para o buffer corrente somente.

Detectando o tipo do arquivo através do nome do arquivo

Se o seu tipo de arquivo não é detectado automaticamente pelo Vim, você deveria escrever um
snippet de detecção em um arquivo separado.
É normalmente na forma de um autocomando que define o tipo do arquivo quando o
nome do arquivo corresponde a um padrão.
Exemplo:
au BufNewFile,BufRead *.foo set filetype=foofoo
Grave esse arquivo de uma linha como 'ftdetect/foofoo.vim' no primeiro diretório
que aparece em 'runtimepath'.
No Unix seria '~/.vim/ftdetect/foofoo.vim'.
A convenção é usar o nome do tipo do arquivo como o nome do script.

Você pode fazer verificações mais complicadas como inspecionar o conteúdo do
arquivo para reconhecer a linguagem.

Escrevendo a documentação de um plugin

É uma boa idéia escrever documentação para o plugin.
Especialmente quando seu comportamento pode ser mudado pelo usuário.

Eis um exemplo de um arquivo de help de plugin 'typecorr.txt':
1     *typecorr.txt*  Plugin for correcting typing mistakes
2
3 If you make typing mistakes, this plugin will have them corrected
4 automatically.
5
6 There are currently only a few corrections. Add your own if you like.
7
8 Mappings:
9 <Leader>a or <Plug>TypecorrAdd
10 Add a correction for the word under the cursor.
11
12 Commands:
13 :Correct {word}
14 Add a correction for {word}.
15
16 *typecorr−settings*
17 This plugin doesn't have any settings.
A primeira linha é a única em que o formato importa. Ela será extraída do
arquivo de help para ser colocada na seção 'LOCAL ADDITIONS:" do help.txt'.
O primeiro '*' deve estar na primeira coluna da primeira linha.
Depois de adicionar seu arquivo de help faça ':help' e verifique que as entradas
alinham adequadamente.

Você pode adicionar mais tags '*...*' dentro de seu arquivo help. Mas seja
cuidadoso de não de usar tags de help existentes. Você irá provavelmente usar
nome do plugin na maioria deles, como 'typecorr-settings' no exemplo.

Usar referencias para outras partes do help em '|...|' é recomendado. Isto torna
fácil para o usuário achar ajuda associada.

Use o formato 'unix' para gravar o script

Use a opção 'fileformat' com o valor 'unix', mesmo no Windows, para gravar o script. Desse modo ele poderá ser lido em qualquer lugar.

Usando variáveis de script em um plugin

Quando uma variável começa com 's:' ela é uma variável de script. Ela só pode
ser usada dentro de um script. Do lado de fora do script ela não é visível.

Isto evita problemas quando usando um mesmo nome de variável em scripts
diferentes.

As variáveis serão mantidas enquanto o vim estiver rodando.

E as mesmas variáveis serão usadas quando fazendo um sourcing do mesmo script
novamente.

O legal é que essas variáveis podem ser usadas também em funções, autocomandos e
comandos de usuário que estão definidos dentro do script.

Vamos usa-las no plugin para contar o número de correções:
19 let s:count = 4
..
30 function s:Add(from, correct)
..
34 let s:count = s:count + 1
35 echo s:count . " corrections now"
36 endfunction
Primeiro 's:count' é inicializada com '4' no próprio script. Quando mais tarde a
função 's:Add' é chamada, ela incrementa 's:count'. Não importa aonde a função
foi chamada, desde que ela foi definida no script, ela usará as variáveis locais
deste script.

Definindo um comando do usuário no plugin

Defina um comando do usuário para adicionar um correção dessa maneira:
38 if !exists(":Correct")
39 command −nargs=1 Correct :call s:Add(<q−args>, 0)
40 endif
O comando do usuário é definido somente se um outro comando com o mesmo nome
existe. Caso contrário teríamos um erro aqui.
Sobrepondo um comando de usuário existente com ':command!' não é um boa
ideia pois faria o usuário questionar porque o comando que ele criou não
funciona mais.

Os usos e diferenças de <SID> e <Plug> no plugin

Os strings <SID> e <Plug> são usados para evitar que mapeamento das teclas pressionadas interfiram com mapeamentos que são somente para serem usados a partir de outros mapeamentos.

As diferenças entre os dois:
<Plug>

Visível do lado de fora do script. Ele é usado para mapeamentos que o usuário
poderia querer mapear para um sequencia de teclas.

<Plug> é um código especial que uma tecla pressionada nunca produzirá.

Para tornar muito improvável que outros plugins usem a mesma sequencia de
caracteres, use esta estrutura: <Plug> nome-do-script nome-do-mapa

No nosso exemplo o nome do script é 'Typecorr' e o nome do mapa é 'Add'.
Isto resulta em '<Plug>TypecorrAdd'. Somente o primeiro carácter do nome do
plugin e do nome do mapa é maiúsculo, para que possamos ver aonde o nome do mapa
começa.

<SID>
É o script ID, um identificador único para o script.

Internamente o vim traduz <sid> para '<SNR>123_', aonde '123' pode ser qualquer
número. Assim uma função '<SID>Add()' terá o nome de fato '<SNR>11_Add()' em um script e, em um outro script, '<SNR>22_Add()'.
Você pode ver isso se você usa o comando ':function' para obter uma lista de funções.

A tradução de <SID> em mapeamentos é exatamente a mesma, isto é como você pode chamar uma função local
de script a partir de um mapeamento.

Evitando que um remapeamento externo quebre o plugin

Note que na linha 28 ':noremap' é usado para evitar que outros mapeamentos
causem problemas. Alguém poderia ter remapeado ':call' por exemplo.

Na linha, 24 usamos também ':noremap', mas nos queremos '<SID>Add()' ser remapeado. Isto é porque '<script>' foi usado aqui. Isto somente permite mapeamentos que são locais para o script.

O mesmo é feito na linha 26 para ':noremenu'.
24 noremap <unique> <script> <Plug>TypecorrAdd    <SID>Add
26 noremenu <script> Plugin.Add\ Correction <SID>Add
28 noremap <SID>Add :call <SID>Add(expand("<cword>"),1)<CR>

Adicionando um menu de plugin

Você pode adicionar um item no menu para fazer o mesmo que um mapeamento do
plugin faz.

Neste caso somente um item é usado. Quando tiver mais itens, criar um sub-menu
é recomendado. Por exemplo, 'Plugin.CVS' poderia ser usado para um plugin que
oferece os serviços de CVS: 'Plugin.checkin', 'Plugin.checkout', etc.
26 noremenu <script> Plugin.Add\ Correction <SID>Add

quinta-feira, 9 de abril de 2009

Dividindo um plugin grande em funções e mapeamentos

Se um plugin fica grande você vai querer dividi-lo em partes.
Você pode usar funções ou mapeamentos para isto.

Mas você não irá querer que essas funções e mapeamentos interfiram com os dos
outros scripts.

Para evitar isso definimos a função como local ao script prefixando-a com 's:':
Por exemplo, nos definiremos uma função que adiciona um novo tipo de correção:
30 function s:Add(from, correct)
31 let to = input("type the correction for " . a:from . ": ")
32 exe ":iabbrev " . a:from . " " . to
..
36 endfunction
Agora chamaremos a função 's:Add()' de dentro do script. Se um outro script
definir um 's:Add()' também ela será local para aquele script e só pode ser
chamado de dentro do script em que ele foi definido.
Pode também existir uma função 'Add()', a qual novamente é uma outra função.

<SID> pode ser usado com mapeamentos. Ele gera um script ID, o qual identifica o
script corrente. Em nosso plugin de correção de digitação nos usamos ele assim:
24 noremap <unique> <script> <Plug>TypecorrAdd  <SID>Add
..
28 noremap <SID>Add :call <SID>Add(expand("<cword>"),1)<CR>
Assim quando um usuário digita '\a', esta sequencia é invocada:
\a −> <Plug>TypecorrAdd −> <SID>Add −> :call <SID>Add()
Se um outro script também mapear <SID>Add, ele obterá um outro script ID e assim
definirá outro mapeamento.

Note que ao invés de 's:Add()' nos usamos '<SID>Add()' aqui. Isto é porque o
mapeamento é digitado pelo usuário, assim do lado de fora do script. O <SID> é
traduzido para o script ID, para que o vim saiba em qual script procurar pela
função 'Add()'.

Isto é um pouco complicado, mas necessário para o plugin funcionar com outros
plugins. A regra básica é que você use '<SID>Add()' em mapeamentos e 's:Add()'
em outros lugares (no script em si, autocomandos e comandos do usuário).

Tornando flexível a escolha das teclas num mapeamento do plugin

O plugin irá definir um mapeamento que permite adicionar uma correção para
a palavra sob o cursor. Poderia pegar um sequencia de teclas, mas o usuário
poderia já te-la pega para outra coisa.

Para permitir que o usuário defina quais teclas o plugin usará no mapeamento use
o item <Leader>:
22 map <unique> <Leader>a <Plug>TypecorrAdd
O usuário pode definir a variável 'mapleader' com a sequencia de teclas que ele
quer que esse mapeamento comece. Assim se o usuário tiver:
let mapleader = "_"
O mapeamento será definido como '_a'. Se o usuário não fizer isso, o valor
default será usado '\' (backslash) e o mapeamento será '\a'.

Mas se o usuário quiser definir a sua própria sequencia completa de teclas? Pode
permitir isso com:
21 if !hasmapto('<Plug>TypecorrAdd')
22 map <unique> <Leader>a <Plug>TypecorrAdd
23 endif
Isto verifica se um mapeamento para '<Plug>TypecorrAdd' existe e somente define
o mapeamento para '<Leader>a' se ele não existe. O usuário então tem a chance de
por isso em seu 'vimrc':
map ,c <Plug>TypecorrAdd
Então sequencia de teclas mapeada será ',c' ao invés de '_a' ou '\a'.

Permitindo carregar ou não o plugin

Dê a possibilidade do plugin ser carregado ou não. É possível que o usuário não queira carregar o pluign ou o administrador o colocou no diretório plugin a nível de sistema mas o usuário quer carregar seu próprio plugin.

Esse código torna isso possível:
6 if exists("loaded_typecorr")
7 finish
8 endif
9 let loaded_typecorr = 1
Isto também evita que o plugin seja carregado duas vezes causando erros nas
definições de funções e problemas com autocomandos adicionados duas vezes.

Lidando com opções do vim através do plugin

O plugin tem que rodar em ambientes variados, com configurações diversas que podem interferir com seu funcionamento:
14 iabbrev teh the
15 iabbrev otehr other
16 iabbrev wnat want
17 iabbrev synchronisation
18 \ synchronization
19 let s:count = 4
O uso de uma continuação de linha, como na linha 18 acima, pode causar problemas
com usuários com 'compatible' ligado.
Não podemos simplesmente desligar o 'compatible' pois tem uma série de efeitos
secundários.
Para evitar isso vamos definir a opção 'cpoptions' para seu valor default e
restaurar mais tarde. Isto permitirá usar a continuação de linha e fazer o
script trabalhar para a maioria das pessoas:
11 let s:save_cpo = &cpo
12 set cpo&vim
..
42 let &cpo = s:save_cpo
Primeiro salvamos o valor antigo de 'cpoptions' em s:save_cpo. No final o valor
é restaurado.
Note que uma variável local do script é usada. Uma variável global já poderia
ter estado em uso por alguma outra coisa. Sempre use variáveis locais de script
para coisas que são somente usadas no script.

Criando um header para o plugin

Use um cabeçalho que diga quem escreveu o plugin, quando foi a última alteração,
licença de uso:
1 " Vim global plugin for correcting typing mistakes
2 " Last Change: 2000 Oct 15
3 " Maintainer: Bram Moolenaar <Bram@vim.org>
4 " License: This file is placed in the public domain.

O nome do plugin

O nome deve fornecer a finalidade do plugin.
Procure limitar a 8 caracteres para suportar versões antigas do Windows.

O plugin e seus tipos

Você pode escrever um script vim de tal modo que muitas pessoas possam usa-lo.
Isto é chamado de plugin.
Usuários do vim podem baixar seu script no diretório plugin deles e usa-lo
imediatamente.

Existem dois tipos de plugins:
Plugin global - para todos tipos de arquivo.
Plugin filetype - somente para arquivos de um determinado tipo.

Eis um exemplo de um plugin global:
  1     " Vim global plugin for correcting typing mistakes
  2 " Last Change: 2000 Oct 15
  3 " Maintainer: Bram Moolenaar <Bram@vim.org>
  4 " License: This file is placed in the public domain.
  5
  6 if exists("loaded_typecorr")
  7 finish
  8 endif
  9 let loaded_typecorr = 1
 10
 11 let s:save_cpo = &cpo
 12 set cpo&vim
 13
 14 iabbrev teh the
 15 iabbrev otehr other
 16 iabbrev wnat want
 17 iabbrev synchronisation
 18 \ synchronization
 19 let s:count = 4
 20
 21 if !hasmapto('<Plug>TypecorrAdd')
 22 map <unique> <Leader>a <Plug>TypecorrAdd
 23 endif
 24 noremap <unique> <script> <Plug>TypecorrAdd <SID>Add
 25
 26 noremenu <script> Plugin.Add\ Correction <SID>Add
 27
 28 noremap <SID>Add :call <SID>Add(expand("<cword>"), 1)<CR>
 29
 30 function s:Add(from, correct)
 31 let to = input("type the correction for " . a:from . ": ")
 32 exe ":iabbrev " . a:from . " " . to
 33 if a:correct | exe "normal viws\<C−R>\" \b\e" | endif
 34 let s:count = s:count + 1
 35 echo s:count . " corrections now"
 36 endfunction
 37
 38 if !exists(":Correct")
 39 command −nargs=1 Correct :call s:Add(<q−args>, 0)
 40 endif
 41
 42 let &cpo = s:save_cpo

Evitando colisão em nomes de função no vim script

Para evitar que seus nomes de função colidam com os nomes de função de terceiro
use o seguinte esquema:

Prefixe um string único antes de cada nome de função. Use uma abreviação, por
exemplo 'OW_' é usado para as funções da janela de opções.

Coloque a definição de suas funções juntas em um arquivo. Defina uma variável
global para indicar que as funções foram carregadas. Quando sourcing o arquivo
de novo, primeire descarregue as funções.

Exemplo:
" Este é o pacote XXX
if exists("XXX_loaded")
delfun XXX_one
delfun XXX_two
endif
function XXX_one(a)
... corpo da função ...
endfun
function XXX_two(b)
... corpo da função ...
endfun
let XXX_loaded = 1

Mapeamento para copiar uma linha e voltar para a mesma posição de tela anterior

Algumas vezes você quer fazer uma mudança em outro ponto do texto e voltar para
a posição aonde o cursor estava.
Restaurar a posição relativa também é bom, para que a mesma linha apareça no
topo da janela.

Este exemplo copia (yank) a linha corrente, coloca-a acima da primeira linha do
arquivo e restaura a janela:
map ,p ma"aYHmbgg"aP`bzt`a
O que isto faz:
  ma"aYHmbgg"aP`bzt`a
  < ma define a marca 'a' na posição do cursor.
   "aY copia a linha corrente dentro do registrador 'a'.
   Hmb vai para a linha do topo na janela e define a marca 'b' lá.
   gg salta para a primeira linha de um arquivo.
   "aP coloca a linha copiada (yanked) acima dele.
   `b volta para a linha de cima na tela
   zt posicione o texto na janela como antes
   `a volte para a posição de cursor salva.

quarta-feira, 8 de abril de 2009

Comentários no vim script e suas armadilhas

O caracter '"' inicia um comentário. Tudo depois e incluindo este carácter até o
fim de linha é considerado um comentário e é ignorado, exceto por comandos
que não consideram comentários, como mostrado a seguir.

Um comentário pode iniciar em qualquer posição de caracter da linha. Existe um
pequeno problema com comentários para alguns comandos:
:abbrev dev development           " shorthand
:map <F3> o#include " insert include
:execute cmd " do it
:!ls *.c " list C files
A abreviação 'dev' será expandida para será expandida para:
'development " shorthand'
O mapeamento de <F3> será na realidade a linha inteira após 'o#...' incluido:
'" insert include'
O comando 'execute' dará um erro.
O comando '!' enviará tudo após ele para o shell, causando um erro caracter '"'
sem correspondente.

Não pode haver nenhum comentário após os comandos :map, :abbreviate, :execute, e
! (existem mais uns poucos comandos com essa restrição). Para os 3 primeiros há
um truque:
:abbrev dev development|" shorthand
:map <F3> o#include|" insert include
:execute cmd |" do it
Com o caracter '|' o comando é separado do próximo. E o próximo comando é
somente um comentário. Para o último comando você precisa fazer duas coisas:
':execute' e usar '|':
:exe '!ls *.c' |" list C files
Note que não há espaços antes do '|' nos comandos de abreviação e mapeamento. Para estes
comandos, qualquer carácter até o final da linha é incluído. Como consequência
deste comportamento você nem sempre vê que espaços no final estão incluídos.
:map <F4> o#include         
Para detectar o problema ative a opção 'list' quando editando o arquivo vimrc.

No Unix, existe um modo especial de comentar uma linha, que permite fazer um
script Vim executável:
#!/usr/bin/env vim −S
echo "this is a Vim script"
quit
O comando '#' por si só lista a linha com o número da linha. Adicionando um '!'
muda para que ela não faça nada, para que você possa adicionar o comando de
shell para executar o resto do arquivo.

A seguir temos um comando 'unmap' que não funciona porque ele tenta desmapear ',ab', mas a
sequencia mapeada foi definida com ',ab ' com um espaço extra no final.
:map  ,ab o#include
:unmap ,ab
E isto é o mesmo que acontece quando se usa um comentário depois do comando
'unmap':
:unmap ,ab " comment
Aqui o comentário será ignorado, todavia o vim tentará mapear ',ab "', o
qual não existe. Reescreva como:
:unmap ,ab|        " comment

Caracteres em branco dentro de um vim script

Linhas em branco são permitidas e ignoradas.

Caracteres em branco (espaço e tabs) são sempre ignoradas. Os brancos entre
parâmetros (por exemplo entre 'set' e 'cpotions') são reduzidas para um branco e
cumprem o papel de separador, os brancos após o último caracter visível podem ou
não ser ignorados dependendo da situação.
:set cpoptions     =aABceFst
os brancos antes de '=' são ignorados. Mas não pode haver nenhum branco depois
do sinal '='! Para incluir caracter em branco no valor de uma opção, ele dever ser escapado
com backslash ('\):
:set tags=my\ nice\ file
Se fosse em os backslashes:
:set tags=my nice file
daria erro, pois seria interpretado como:
:set tags=my
:set nice
:set file

Capturando as exceções de execução de um vim script

O vim script permite capturar exceções e dar um tratamento para elas.

Por exemplo:
:try
: read ~/templates/pascal.tmpl
:catch /E484:/
: echo "Sorry, the Pascal template file cannot be found."
:endtry
O comando ':read' falhará se o arquivo de template não for encontrado. Ao invés
de gerar uma mensagem de erro, o código captura o erro e dá uma mensagem mais
clara.

Para os comandos entre ':try' e ':except' os erros são tornados exceções.
Uma exceção é um string. No caso de um erro o string contém a mensagem de erro e
cada mensagem de erro tem um número.
Neste caso, a mensagem capturada contém 'E484:'. Este número é garantido ser
sempre o mesmo (o texto pode mudar, por exemplo, ser traduzido).

Quando o comando ':read' causa outra erro, o padrão 'E484:' não terá ocorrência.
Assim essa exceção não será capturada e resulta na mensagem de erro usual.

Você pode ser tentado a fazer isso:
:try
: read ~/templates/pascal.tmpl
:catch
: echo "Sorry, the Pascal template file cannot be found."
:endtry
Isto significa que todos erros serão capturados. Mas você não verá erros que são
úteis, tais como: "E21: Cannot make changes, 'modifiable' is off".

Outro mecanismo útil é o comando ':finally':
:let tmp = tempname()
:try
: exe ".,$write " . tmp
: exe "!filter " . tmp
: .,$delete
: exe "$read " . tmp
:finally
: call delete(tmp)
:endtry
Isto filtra as linhas do cursro até o final do arquivo através do comando
'filter', o qual leva um argumento de nome de arquivo.
Não importa se a filtragem funciona, se algo vai errado entre ':try' e
':finally' ou se o usuário cancela a filtragem pressionando <Ctrl-C>, o 'call
delete(tmp)') é sempre executado. Isto assegura que o arquivo temporário não
fica para trás.

Usando um Dicionário com objeto

Como podemos colocar valores e funções dentro de um Dicionário, você pode
usa-lo como um objeto.

Vamos usar um Dicionário para traduzir para qualquer linguagem que se deseje.

Primeiro defina um objeto (isto é, um Dicionário) que tem a função de tradução,
mas nenhum para palavra para traduzir.
:let transdict = {}
:function transdict.translate(line) dict
: return join(map(split(a:line), 'get(self.words, v:val, "???")'))
:endfunction
Esta função usa 'self.words' para procurar as traduções de palavras. Porém não
existe 'self.words'. Assim você poderia chama-la de classe abstrata.

Agora podemos instanciar um objeto tradutor para o holandês,'Dutch':
:let uk2nl = copy(transdict)
:let uk2nl.words = {'one': 'een', 'two': 'twee', 'three': 'drie'}
:echo uk2nl.translate('three one')
< drie een ~
E um objeto tradutor para o alemão, 'German':
:let uk2de = copy(transdict)
:let uk2de.words = {'one': 'ein', 'two': 'zwei', 'three': 'drei'}
:echo uk2de.translate('three one')
< drei ein ~
A função 'copy()' foi usada para fazer um cópia do dicionário 'transdict' ('a
classe abstrata') e então a cópia é alterada adicionando palavras. A versão
original permanece a mesma.

Agora podemos ser mais flexíveis usando seu tradutor preferido:
:if $LANG =~ "de"
: let trans = uk2de
:else
: let trans = uk2nl
:endif
:echo trans.translate('one two three')
< een twee drie ~
Aqui 'trans' refere a um dos dois objetos (Dicionários). Nenhuma cópia é feita.

Você poderia usar uma linguagem que não é suportada. Você pode sobrepor
'translate()' para não fazer nada.
:let uk2uk = copy(transdict)
:function! uk2uk.translate(line)
: return a:line
:endfunction
:echo uk2uk.translate('three one wladiwostok')
< three one wladiwostok ~
Note que '!' foi usada para regravar a referencia de função existente. Agora use
'uk2uk' quando nenhuma linguagem conhecida é encontrada:
:if $LANG =~ "de"
: let trans = uk2de
:elseif $LANG =~ "nl"
: let trans = uk2nl
:else
: let trans = uk2uk
:endif
:echo trans.translate('one two three')
< one two three ~

Definindo uma função e salvando uma referencia para ela em um Dicionário

Vamos usar o dicionário abaixo, com chaves com palavras em inglês e valores com a tradução em holandês:
:echo uk2nl
< {'three': 'drie', 'four': 'vier', 'one': 'een', 'two': 'twee'} ~
Agora vamos definir uma função que irá traduzir palavras em inglês para o holandês.
Uma referencia para a função será armazenada no dicionário:
:function uk2nl.translate(line) dict
: return join(map(split(a:line), 'get(self, v:val, "???")'))
:endfunction
Testando:
:echo uk2nl.translate('three two five one')
< drie twee ??? een
A primeira coisa de especial a notar é o 'dict' no final de ':function'. Isto diz que a função é usada a partir de um dicionário.
A variável local 'self' irá ter uma referencia para esse dicionário.

Agora vamos analisar a complicada expressão no comando ':return':
A função 'split(a:line)' divide um string formado de palavras separadas por espaços e retorna uma lista dessas palavras.

Assim, no exemplo, ela retorna:
:echo split('three two five one')
< ['three', 'two', 'five', 'one'] ~
Esta lista é o primeiro argumento da função 'map()'. Ela percorre a lista e a cada item avalia o seu segundo argumento que tem 'v:val' com o valor de cada item. Isto é uma versão reduzida de um loop 'for'.
Este código:
:let alist = map(split(a:line), 'get(self, v:val, "???")')
É equivalente a este:
:let alist = split(a:line)
:for idx in range(len(alist))
: let alist[idx] = get(self, alist[idx], "???")
:endfor
A função 'get()' verifica se um chave está presente em um dicionário. Se estiver
retorna o valor. Se não, retorna o valor default, no caso '???'.
Este é um modo conveniente de ver se a chave está presente sem que ocorra uma mensagem de erro se não estiver.

A função 'join()' faz o oposto de 'split()': ele junta um lista de palavras,
colocando um espaço entre elas.

Esta combinação de 'split()', 'map()' e 'join()' é um modo fácil de filtrar uma
linha de palavras em um modo compacto.

terça-feira, 7 de abril de 2009

Usando uma variável para conter o primeiro carácter de um mapeamento

Use a palavra-chave <Leader> para que o valor de uma variável seja usado como o primeiro caracter de um mapeamento:

map <Leader>a ...

O default é '\'.
Se quiser outro caracter use:
let mapleader = ",'

Isso é útil para os programadores de plugins, pois permite que o usuário use o mapeamento que for adequado.

Opção para aumentar o timeout da digitação de um mapeamento

Suponha voce tem um mapeamento usando caracteres:
:map \pre ...

O vim, por default, espera por 1 segundo pelo próximo caracter da sequencia. Se
não for digitado o vim considera como caracteres comuns e não executa o
mapeamento.

Para aumentar esse valor use essas opções para aumentar o timeout:
:set timeout timeoutlen=3000 ttimeoutlen=100
agora o vim espera 3 segundos pelo caracter.

Mapeamento para colocar textos em volta de uma seleção visual

Esse mapeamento pode ser usado para colocar tags, marcas de comentários,
qualquer texto torno da área visualmente selecionada:
vnoremap <Leader>pre <Esc>`>a</PRE><Esc>`<i<PRE><Esc>
<Esc> - sai do modo inserção se estiver.
`> - salta para o último caracter da última seleção visual.
a - entra em modo inserção após um cursor.
<\PRE> - tag de fechamento
<Esc> - sai do modo inserção.
`< - salta para o primeiro caracter da última seleção visual.
i - entra em modo inserção antes do cursor.
<PRE> - tag de início.
<Esc> - sai do modo inserção.

Repetindo uma seleção visual

Use:
gv
a área visual anterior é re-selecionada.
O comando 'gv' depois de usar 'p' ou 'P', faz com que o texto original que foi colado seja re-selecionado.

segunda-feira, 6 de abril de 2009

Opção para definir a aparência do cursor conforme o modo do vim

É comum, o vim estar em modo inserção e achar que está em modo normal e começar a digitar comandos.
Resultado : texto alterado indevidamente.

Para amenizar isso use a opção 'guicursor'. Com ela você poderá definir a aparência do cursor em cada modo.

Ela é composta de uma lista de 'partes' separadas por vírgulas.
Cada parte consiste de uma lista de modos e uma lista de argumentos.

O primeiro 'set' define o cursor para os modos normal, visual e linha de comando (inserção no final):
cursor bloco, sem piscar, azul

O segundo define para o modo inserção:
cursor bloco, sem piscar, vermelho.
highlight Cursor guifg=white guibg=Blue
highlight iCursor guifg=white guibg=Red
set guicursor=n-v-c:block-blinkon0-Cursor
set guicursor+=i:block-blinkon0-iCursor
As cores vem de grupos de colorizaçao (highlight) definidos antes.

Lendo itens de um Dicionário no vim script

Os itens de um dicionário podem ser obtidos assim:
:echo uk2nl['one']
< een ~

Um método que faz o mesmo, porém sem as pontuações é este:
:echo uk2nl.one
< een ~

Isto só funciona para uma chave feita de letras ASCII, dígitos e underscore.

Classificando um dicionário em um vim script

Você pode classificar a lista para obter em uma ordem específica:
:for key in sort(keys(uk2nl))
: echo key
:endfor
< one ~
three ~
two ~
Mas você nunca poderá voltar para a ordem na qual os itens estão definidos. Para
isso você precisa usar uma lista, ela armazena os itens em uma sequencia
ordenada.

Percorrendo um dicionário em um vim script

Existem vários usos para os dicionários e muitas funções para trabalhar com
elas.

Percorrendo uma lista de chaves:
:for key in keys(uk2nl)
: echo key
:endfor
< three ~
one ~
two ~

Criando e adicionando itens de um dicionário em um vim script

Um dicionário é um tipo de dados composto do vim que armazena pares chave-valor.
Você pode pesquisar um valor rapidamente se conhece a sua chave.

Um dicionário é criado com chaves:
:let uk2nl = {'one': 'een', 'two': 'twee', 'three': 'drie'}

Agora você pode procurar palavras colocando a chave dentro de colchetes:
:echo uk2nl['two']
< twee ~

A forma genérica para definir um dicionário é:
{ : , ...}

Um dicionário vazio:
:let emptydict = {}

Adicionando um valor para a chave 'four':
:let uk2nl.four = 'vier'

Percorrendo uma lista no vim script

Use:
:let alist = ['one', 'two', 'three']
:for n in alist
: echo n
:endfor
< one ~
two ~
three ~
O loop irá trazer cada elemento, assinalando-o para a variável 'n'.
A forma geral é:
:for {variável} in {expressão-lista}
: {comandos}
:endfor

Para fazer um loop um certo número de vezes crie uma lista de um tamanho
específico. Use a função 'range()' para criar um para você:
:for a in range(3)
: echo a
:endfor
< 0 ~
1 ~
2 ~
Note que o primeiro item da lista que o 'range()' produz é zero, assim o último
item é um menos que o tamanho da lista.

Você pode especificar o valor máximo, o número de passos e mesmo para voltar
para trás:
:for a in range(8, 4, −2)
: echo a
:endfor
< 8 ~
6 ~
4 ~

Um exemplo mais útil é percorrer sobre as linhas em buffer:
:for line in getline(1, 20)
: if line =~ "Date: "
: echo matchstr(line, 'Date: \zs.*')
: endif
:endfor
isto percorre as linhas de 1 a 20, inclusive, e imprime qualquer data que
encontre.

Criando e adicionando itens para uma Lista no vim script

Lista é um tipo de dados (composto) do vim.
É uma sequencia ordenada de coisas: lista de números, lista de listas ou lista
de itens misturados.

Uma lista com três strings:
:let alist = ['aap', 'mies', 'noot']

Os itens da lista são agrupados por colchetes e separados por vírgulas.

Uma lista vazia:
:let alist = []

Você pode adicionar itens para uma lista com a função add():
:let alist = []
:call add(alist, 'foo')
:call add(alist, 'bar')
:echo alist
< ['foo', 'bar']

A concatenação de listas é feita com:
:echo alist + ['foo', 'bar']
< ['foo', 'bar', 'foo', 'bar'] ~

Ou se quiser estender a lista diretamente:
:let alist = ['one']
:call extend(alist, ['two', 'three'])
:echo alist
< ['one', 'two', 'three']

Note que usando 'add()' terá um efeito diferente:
:let alist = ['one']
:call add(alist, ['two', 'three'])
:echo alist
< ['one', ['two', 'three']]
o segundo argumento de 'add()' é adicionado como um único item.

domingo, 5 de abril de 2009

Usando referências de função em um script

Uma referencia de função é uma variável que aponta para um função.

Você pode cria-la com a função function(). Ele transforma o nome de uma função
em referencia:
:let result = 0        " or 1
:function! Right()
: return 'Right!'
:endfunc
:function! Wrong()
: return 'Wrong!'
:endfunc
:
:if result == 1
: let Afunc = function('Right')
:else
: let Afunc = function('Wrong')
:endif
:echo call(Afunc, [])
< Wrong! ~
Note que o nome da função que guarda a referencia da função deve começar com
maiúsculo. Caso contrário pode se confundir com o nome de uma função interna.

O modo que uma função que uma variável armazena é chamada é através da função
'call()'. Seu primeiro argumento é a referencia para a função, o segundo
argumento é uma Lista com argumentos.

Referencias para funções são mais úteis quando combinadas com um Dicionário.

Deletando funções da memória

Use:
:delfunction Show
para deletar a função 'Show'.

Debugando funções

Use o número da linha nas mensagens de erro.

Defina em 12 ou maior a opção 'verbose' para ver todas as chamadas de função.

Listando as funções definidas pelo usuário

O comando ':function' lista os nomes e argumentos de todas funções definidas
pelo usuário.
  :function
< function Show(start, ...) ~
function GetVimIndent() ~
function SetSyn(name) ~
Para ver o que a função faz, use o seu nome como argumento:
  :function SetSyn
< 1 if &syntax == '' ~
2 let &syntax = a:name ~
3 endif ~
endfunction ~

Substituindo todas ocorrências de um carácter, mas só no inicio da linha

Comando para substituir espaços no início da linha pela entidade html 'non-break
space':

:s/^\s\+/\=substitute(submatch(0),'\s','\&nbsp;','g')/eg

Descrevendo:

:s - substitute
/^\s\+ - o padrão: o início da linha seguido de um ou mais espaços.
/\= - o string de troca. O '\=' indica que é para interpretar o string como uma
expressão ao invés de uma literal.

A seguir vem a expressão que retorna o string que substituirá a ocorrência.
Ela usa a função 'substitute()' que irá fazer a mudança na ocorrência trazida pelo primeiro comando ':substitute' :

substitute(submatch(0),'\s','\&nbsp;','g')/e
submatch(0) - retorna a ocorrência completa do ':substitute' mais externo.
\s - o padrão da ocorrência que será substituída: espaço em branco.
\&nbsp; - o string que o substituirá.
g - flag para repetir para todas ocorrências.

Colorizando mensagens de um vim script

O comando:
:echohl {grupo-de-colorização}
fará que os comandos seguintes ':echo' sejam colorizados.

Não esqueça de voltar o grupo para 'None' senão todos ':echo' seguinte serão
colorizados.
: echohl Title
: echo "Show is " . a:start
: echohl None

Função com número variável de argumentos

O vim permite definir uma função com um número variável de argumentos.

A seguinte função pode ter de 1 até 20 argumentos:
:function Show(start, ...)

A variável 'a:1' contém o primeiro argumento, e dos demais estão em 'a:2',
'a:3', etc.
A variável 'a:0' contém o número de argumentos extra.

Por exemplo:
:function Show(start, ...)
: echohl Title
: echo "Show is " . a:start
: echohl None
: let index = 1
: while index <= a:0
: echo " Arg " . index . " is " . a:{index}
: let index = index + 1
: endwhile
: echo ""
:endfunction
Pode-se usar também a variável 'a:000' que é uma lista de todos argumentos
'...'.

Chamando uma função com um intervalo de linhas

O comando ':call' pode receber um intervalo de linhas.

Quando a função foi definida com a palavra-chave <range>, ela tratará o
intervalo de linhas ela mesma:
:function Count_words() range
: let lnum = a:firstline
: let n = 0
: while lnum <= a:lastline
: let n = n + len(split(getline(lnum)))
: let lnum = lnum + 1
: endwhile
: echo "found " . n . " words"
:endfunction
Agora você pode chamar esta função com:
:10,30call Count_words()
a função será chamada uma vez e ecoará o número de palavras.

Outra forma é definir a função sem <range>. A função será chamada a cada linha
do intervalo, com o cursor naquela linha:
:function Number()
: echo "line " . line(".") . " contains: " . getline(".")
:endfunction
Se você chamar a função com:
:10,15call Number()
a função será chamada seis vezes.

Corrigindo erros detectados pelo corretor ortográfico do vim

O corretor ortográfico depois de ativado mostra as palavras erradas em vermelho
e as palavras que deviam começar com maiúsculo em azul.

O vim tem alguns comandos para ajudar na correção.

Para saltar para palavras erradas:
]s - para a próxima.
[s - para a anterior.

Para adicionar uma palavra sob o cursor no spellfile (arquivo de palavras):
zg - marca como palavra correta.
zw - marca como palavra incorreta.
:spellgood {palavra} - marca como palavra correta.
:spellwrong {palavra} - marca como palavra incorreta.

Para remover uma palavra adicionada no spellfile:
zuw
zug

Para ver sugestões para palavras incorretas:
z=
A palavra sob o cursor.
1z=
Usa diretamente a primeira sugestão.

Para repetir a troca de um 'z=' em todas ocorrências na janela corrente:
:spellrepall

Obtendo sugestões no modo inserção:
<Ctrl-X>
use <Ctrl-N> ou <Ctrl-P> para percorrer a lista.