Criando Script do Editor Vim, Parte 2: Funções Definidas pelo Usuário

Criar os blocos de construção de automação fundamentais

Funções definidas pelo usuário são uma ferramenta essencial para decompor um aplicativo em componentes corretos e que podem passar por manutenção, para gerenciar a complexidade de tarefas de programação do mundo real. Este artigo (o segundo de uma série) explica como criar e implementar novas funções na linguagem Vimscript, fornecendo diversos exemplos práticos de porque você pode querer isso.

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

author photo - damian conwayDamian Conway é um Professor Associado Adjunto de Ciência da Computação na Monash University, Austrália, e CEO da Thoughtstream, uma empresa de treinamento de TI internacional. Ele é um usuário diário do vi há mais de vinte e cinco anos e quase não tem esperanças de acabar com essa compulsão.



07/Jul/2009

Sobre Vimscript e esta Série

Vimscript é uma linguagem de script poderosa que permite modificar e estender o editor de Vim. É possível usá-la para criar novas ferramentas, simplificar tarefas comuns e, até mesmo, retrabalhar recursos existentes do editor. Esta série de artigos supõe alguma familiaridade com o editor de Vim. Você deve ler também "Criando Script do Editor Vim, Parte 1", que cobre variáveis, valores e expressões, conhecimento obrigatório para construir funções.

Funções Definidas pelo Usuário

Pergunte aos programadores de Haskell ou Scheme e eles dirão que funções são o recurso mais importante de qualquer linguagem de programação séria. Pergunte a programadores de C ou Perl e eles dirão exatamente a mesma coisa.

As funções fornecem dois benefícios essenciais para o programador sério:

  1. Elas possibilitam que tarefas de computação complexas sejam subdivididas em partes pequenas o suficiente para se ajustarem de forma confortável em um único cérebro humano.
  2. Elas permitem que essas partes subdivididas recebam nomes lógicos e compreensíveis, de forma que possam ser manipuladas de modo competente por um único cérebro humano.

Vimscript é uma linguagem de programação séria, de forma que naturalmente suporta a criação de funções definidas pelo usuário. Na verdade, discutivelmente possui um melhor suporte para funções definidas pelo usuário do que Scheme, C ou Perl. Este artigo explora os diversos recursos de funções Vimscript e mostra como é possível usar esses recursos para aprimorar e estender a funcionalidade integrada a Vim de maneira que possam passar por manutenção.

Declarando Funções

As funções em Vimscript são definidas usando a palavra-chave function, seguida pelo nome da função e depois pela lista de parâmetros (que é obrigatória, mesmo se a função não aceitar nenhum argumento). O corpo da função inicia então na próxima linha e continua até que uma palavra-chave endfunction correspondente seja encontrada. Por exemplo:

Lista 1. Uma Função Estruturada Corretamente
functionExpurgateText (text)
    let expurgated_text = a:text

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

    return expurgated_text
endfunction

O valor de retorno da função é especificado com uma instrução return. É possível especificar tantas instruções return separadas quantas forem necessárias. Pode-se incluir nenhuma, mesmo se a função estiver sendo usada como um procedimento e não tiver nenhum valor de retorno útil. No entanto, as funções Vimscript sempre retornam um valor, portanto, se nenhum return for especificado, a função retorna zero automaticamente.

Os nomes das funções em Vimscript devem ser iniciados por uma letra maiúscula:

Lista 2. Nomes de Funções Iniciam com uma Letra Maiú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>

Esse exemplo define uma função que incrementa o valor da variável b:backup_count do buffer atual (ou a inicializa para 1, se ainda não existir). A função captura então cada linha do arquivo atual (getline(1,'$')) e chama a função writefile() integrada para gravá-las no disco. O segundo argumento para writefile() é o nome do novo arquivo a ser gravado; nesse caso, o nome do arquivo atual (bufname('%')) com o novo valor do contador anexado. O valor retornado é o valor de sucesso/falha da chamada a writefile(). Por fim, nmap configura CTRL-B para chamar a função para criar um backup numerado do arquivo atual.

Em vez de usar uma letra maiúscula inicial, as funções Vimscript também podem ser declaradas com um prefixo de escopo explícito (como as variáveis podem ser, conforme descrito na Parte 1). A opção mais comum é s:, o que torna a função local para o arquivo de script atual. Se o escopo de uma função for definido dessa forma, seu nome não precisa ser iniciado por uma letra maiúscula; pode ser qualquer identificador válido. No entanto, as funções com escopo definido explicitamente devem sempre ser chamadas com seus prefixos de definição de escopo. Por exemplo:

Lista 3. Chamando uma Função com seu Prefixo de Definição de Escopo
" 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>

Funções Redeclaráveis

As declarações de funções em Vimscript são funções de tempo de execução, de forma que se um script for carregado duas vezes, qualquer declaração de função nesse script será executada duas vezes, criando novamente as funções correspondentes.

Declarar uma função novamente é tratado como um erro fatal (para evitar conflitos onde dois scripts separados declaram funções com o mesmo nome acidentalmente). Isso dificulta a criação de funções em scripts que são projetados para serem carregados repetidamente, como scripts customizados de realce da sintaxe.

Portanto, Vimscript fornece um modificador de palavra-chave (function!) que permite indicar que uma declaração de função possa ser recarregada de forma segura tão frequentemente quanto necessário:

Lista 4. Indicando que uma Declaração de Função Pode Ser Recarregada 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

Nenhuma verificação de redeclaração é executada em funções definidas com essa palavra-chave modificada, de forma que será melhor se for usada com funções com escopo definido explicitamente (que nesse caso, a definição de escopo já assegura que a função não irá conflitar com uma de outro script).

Chamando Funções

Para chamar uma função e usar seu valor de retorno como parte de uma expressão maior, simplesmente denomine-a e anexe uma lista de argumentos entre parênteses:

Lista 5. Usando o Valor de Retorno de uma Função
"Clean up the current line...
let success = setline('.', ExpurgateText(getline('.')) )

Observe, no entanto, que, diferentemente de C ou Perl, Vimscript não permite eliminar o valor de retorno de uma função sem usá-lo. Portanto, se você tiver a intenção de usar a função como um procedimento ou sub-rotina e ignorar seu valor de retorno, deve colocar como prefixo da chamada o comando call:

Lista 6. Usando uma Função sem Usar seu Valor de Retorno
"Checkpoint the text...
call SaveBackup()

Caso contrário, Vimscript assumirá que a chamada da função é, na verdade, um comando Vim integrado e irá, mais provavelmente, reclamar que esse comando não existe. Vamos dar uma olhada na diferença entre funções e comandos em um artigo de futuro nesta série.

Listas de Parâmetros

Vimscript permite definir parâmetros explícitos e listas de parâmetros com aridade variável e, até mesmo, combinações dos dois.

É possível especificar até 20 parâmetros explicitamente denominados imediatamente após a declaração do nome da sub-rotina. Uma vez especificado, os valores de argumentos correspondentes para a chamada atual podem ser acessados na função colocando um prefixo a: no nome do parâmetro:

Lista 7. Acessando Valores de Argumentos na Função
function PrintDetails(name, title, email)
    echo 'Name:   '  a:title  a:name
    echo 'Contact:'  a:email
endfunction

Se você não souber quantos argumentos uma função pode receber, é possível especificar uma lista de parâmetros com aridade variável, usando reticências (...) em vez de parâmetros denominados. Nesse caso, a função pode ser chamada com tantos argumentos quanto desejar e esses valores são coletados em uma única variável: uma array denominada a:000. Argumentos individuais também recebem nomes de parâmetros posicionais: a:1, a:2, a:3, etc. O número de argumentos está disponível como a:0. Por exemplo:

Lista 8. Especificando e Usando uma Lista de Parâmetros com Aridade Variável
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

Observe que, neste exemplo, sum deve ser inicializado para valor de ponto flutuante explícito; caso contrário, todas as computações subsequentes serão realizadas usando aritmética de números inteiros.

Combinando Parâmetros Denominados e Aridade Variável

Parâmetros denominados e aridade variável podem ser usados na mesma função, simplesmente colocando as reticências com aridade variável após a lista de parâmetros denominados.

Por exemplo, suponhamos que você desejasse criar uma função CommentBlock() que teve uma cadeia de caracteres passada e que a formatou em um bloco de comentário apropriado para diversas linguagens de programação. Essa função sempre exigiria que o responsável pela chamada fornecesse a cadeia de caracteres a ser formatada, de forma que esse parâmetro devesse ser explicitamente denominado. Mas pode-se preferir que o introdutor do comentário, o caractere "boxing" e a largura do comentário sejam todos opcionais (com padrões sensíveis quando omitidos). Você poderia chamar então:

Lista 9. Uma Chamada de Função CommentBlock Simples
call CommentBlock("This is a comment")

e retornaria uma cadeia de caracteres com diversas linhas contendo:

Lista 10. O Retorno de CommentBlock
//*******************
// This is a comment
//*******************

Considerando que, se você tiver fornecido argumentos extras, eles especificariam valores não padrão para o introdutor do comentário, para o caractere "boxing" e para a largura do comentário. Portanto, essa chamada:

Lista 11. Uma Chamada de Função CommentBlock Mais Envolvida
call CommentBlock("This is a comment", '#', '=', 40)

retornaria a cadeia:

Lista 12. O Retorno de CommentBlock
#========================================
# This is a comment
#========================================

Essa função pode ser implementada como da seguinte forma:

Lista 13. A Implementação de CommentBlock
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

Se houver pelo menos um argumento opcional (a:0 >= 1), o parâmetro do introdutor tem essa primeira opção designada (ou seja, a:1); caso contrário, tem um valor padrão designado igual a "//". De forma semelhante, se houver dois ou mais argumentos opcionais (a:0 >= 2), a variável box_char tem a segunda opção designada (a:2) do contrário, um valor padrão igual a "*". Se três ou mais argumentos opcionais forem fornecidos, a terceira opção é designada à variável width. Se nenhum argumento de largura for fornecido, a largura apropriada é computada automaticamente a partir do próprio argumento (strlen(a:comment)+2).

Por fim, ter resolvido todos os valores de parâmetros, as linhas superior e inferior da caixa de comentário são construídas usando o introdutor de comentário de orientação, seguido pelo número apropriado de repetições do caractere boxing (repeat(box_char,width)), com o texto do comentário entre eles.

É claro que para usar essa função será necessário chamá-la de alguma maneira. Um mapa de inserção é provavelmente a maneira ideal de se fazer isso:

Lista 14. Chamando a Função Usando um Mapa de Inserção
"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>

Em cada um desses mapas, a função input() integrada é chamada primeiramente para solicitar que o usuário digite o texto do comentário. A função CommentBlock() é, então, chamada para converter esse texto em um bloco de comentário. Finalmente, a orientação <C-R>= insere a cadeia de resultados.

Observe que o primeiro mapeamento passa somente um único argumento, de forma que usa como padrão // como seu marcador de comentário. O segundo e o terceiro mapeamento passam um segundo argumento para especificar # ou -- como seus respectivos introdutores de comentário. O mapeamento final também passa um terceiro argumento, para que o caractere "boxing" corresponda seu introdutor de comentário.


Funções e Intervalos de Linhas

É possível chamar qualquer comando Vim padrão—incluindo call—com um intervalo de linhas preliminar, que faz com que o comando seja repetido uma vez para cada linha no 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

Você pode digitar :help cmdline-ranges em qualquer sessão Vim para aprender mais sobre esse recurso.

No caso do comando call, especificar um intervalo faz a função solicitada ser chamada repetidamente: uma vez para cada linha no intervalo. Para ver porque isso é útil, vamos considerar como gravar uma função que converta qualquer e comercial (símbolo) "bruto" na linha atual em entidades XML apropriadas &amp;, mas isso também é inteligente o suficiente para ignorar qualquer e comercial que já faça parte de alguma outra entidade. Essa função poderia ser implementada desta forma:

Lista 15. Função para Converter Es Comerciais
function DeAmperfy()
    "Get current line...
    let curr_line   = getline('.')

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

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

A primeira linha de DeAmperfy() captura a linha atual do buffer do editor (getline('.')). A segunda linha procura qualquer & nessa linha que não seja seguido por um identificador e dois pontos, usando o padrão lookahead negativo '&\(\w\+;\)\@!'(consulte :help \@! para obter detalhes). A chamada substitute() substitui então todos esses es comerciais "brutos" com a entidade XML &amp;. Por fim, a terceira linha de DeAmperfy() atualiza a linha atual com o texto modificado.

Se tiver chamado essa função a partir da linha de comando:

:call DeAmperfy()

executaria a substituição somente na linha atual. Mas se tiver especificado um intervalo antes de call:

:1,$call DeAmperfy()

então, a função seria chamada uma vez para cada linha no intervalo (nesse caso, para cada linha no arquivo).

Internalizando Intervalos de Linhas de Função

Esse comportamento chamar a função repetidamente para cada linha é um padrão conveniente. No entanto, às vezes, pode-se preferir especificar um intervalo, mas fazer com que a função seja chamada somente uma vez e tratar da semântica do intervalo na função em si. Isso também é fácil em Vimscript. Você simplesmente anexa um modificador especial (range) para a declaração da função:

Lista 16. Semântica do Intervalo em uma Função
function DeAmperfyAll() range"Step through each line in the range...
    for linenum in range(a:firstline, a:lastline)
        "Replace loose ampersands (as in DeAmperfy())...
        let curr_line   = getline(linenum)
        let replacement = substitute(curr_line,'&\(\w\+;\)\@!','&amp;','g')
        call setline(linenum, replacement)
    endfor

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

Com o modificador range especificado após a lista de parâmetros, qualquer hora que DeAmperfyAll() for chamado com um intervalo como:

:1,$call DeAmperfyAll()

a função será chamada somente uma vez e dois argumentos especiais, a:firstline e a:lastline, serão configurados para os números da primeira e da última linha do intervalo. Se nenhum intervalo for especificado, a:firstline e a:lastline são configurados para o número de linha atual.

A função constrói primeiro uma lista de todos os números de linhas relevantes (range(a:firstline, a:lastline)). Observe que essa chamada à função range() integrada não tem qualquer relação com o uso do modificador range como parte da declaração da função. A função range() é simplesmente um construtor de lista, muito semelhante à função range() em Python ou ao operador .. em Haskell ou Perl.

Tendo determinado a lista de números de linhas a serem processadas, a função usa um loop for para percorrer cada:

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

e atualiza cada linha conforme necessário (exatamente como o DeAmperfy() original fazia).

Por fim, se o intervalo cobrir mais de uma única linha (ou seja, se a:lastline > a:firstline), a função relata quantas linhas foram atualizadas.

Intervalos Visuais

Quando tiver uma chamada de função que possa operar em um intervalo de linhas, uma técnica especialmente útil é chamar essa função através do modo Visual (consulte :help Visual-mode para obter detalhes).

Por exemplo, se seu cursor estiver em alguma local em um bloco de texto, poderia codificar todos os es comerciais em qualquer local no parágrafo a seu redor com:

Vip:call DeAmperfyAll()

Digitar V no modo Normal alterna para o modo Visual. O ip faz então com que o modo Visual realce todo o parágrafo no qual você se encontra. Então, : alterna para o modo Comando e configura automaticamente o intervalo do comando para o intervalo de linhas que você acaba de selecionar no modo Visual. Neste ponto, você chama DeAmperfyAll() para efetuar deamperfy de todas elas.

Observe que, nessa instância, você poderia obter o mesmo efeito com apenas:

Vip:call DeAmperfy()

A única diferença é que a função DeAmperfy() seria chamada repetidamente: uma vez para cada linha que Vip realçou no modo Visual.


Uma Função para Ajudá-lo a Codificar

A maioria das funções definidas pelo usuário em Vimscript requer muito poucos parâmetros e frequentemente nenhum. Isso porque elas geralmente obtém seus dados diretamente do buffer do editor atual e de informações contextuais (como a posição do cursos atual, o tamanho do parágrafo atual, o tamanho da janela atual ou o conteúdo da linha atual).

Além do mais, as funções são frequentemente muito mais úteis e convenientes quando elas obtêm seus dados através de contexto, em vez de através de listas de argumentos. Por exemplo, um problema comum ao manter o código de origem é que os operadores de designação ficam fora de alinhamento à medida que são acumulados, o que reduz a capacidade de leitura do código:

Lista 16. Operadores de Designação Fora de Alinhamento
let applicants_name = 'Luke'
let mothers_maiden_name = 'Amidala'
let closest_relative = 'sister'
let fathers_occupation = 'Sith'

Realinhá-los manualmente toda vez que uma nova instrução for incluída pode ser tediosa:

Lista 17. Operadores de Designação Realinhada Manualmente
let applicants_name     = 'Luke'
let mothers_maiden_name = 'Amidala'
let closest_relative    = 'sister'
let fathers_occupation  = 'Sith'

Para reduzir o tédio dessa tarefa de codificação diária, pode-se criar um mapeamento de chave (como ;=) que seleciona o bloco de código atual, localiza quaisquer linhas com operadores de designação e alinha automaticamente esses operadores. Desta forma:

Lista 18. Função para Alinhar Operadores de Designação
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>

A função AlignAssignments() configura primeiramente duas expressões regulares (consulte :help pattern para obter os detalhes necessários da sintaxe regex de Vim):

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

O padrão em ASSIGN_OP corresponde qualquer um dos operadores de designação padrão: =, +=, -=, *=, etc., mas evita cuidadosamente correspondente a outros operadores que contenham =, como == e =~. Se sua linguagem favorita tiver outros operadores de designação (como .= ou ||= ou ^=), você poderia estender regex ASSIGN_OP para reconhecê-los também. Como alternativa, você poderia redefinir ASSIGN_OP para reconhecer outros tipos de "alinháveis", como introdutores de comentários ou marcadores de colunas, e alinhá-los em vez disso.

O padrão em ASSIGN_LINE corresponde somente no início de uma linha (^), correspondendo a um número mínimo de caracteres (.\{-}), então, qualquer espaço em branco (\s*), então, um operador de designação.

Observe que o subpadrão inicial de "número mínimo de caracteres" e o subpadrão do operador são especificados entre parênteses: \(...\). As subcadeias capturadas por esses dois componentes de regex serão extraídas posteriormente usando-se chamadas à função submatch() integrada; especificamente, chamando submatch(1) para extrair tudo antes do operador e submatch(2) para extrair o próprio operador.

AlignAssignments() localiza então o intervalo das linhas no qual irá 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

Em exemplos anteriores, funções dependiam de um intervalo de comandos explícito ou de uma seleção do modo Visual para determinar em quais linhas elas operavam, mas esta função computa seu próprio intervalo diretamente. Especificamente, chama primeiro a função matchstr() integrada para determinar qual espaço em branco de orientação ('^\s*') aparece no início da linha atual (getline('.'). Constrói então uma nova expressão regular em indent_pat que corresponde exatamente à mesma sequência de espaços em branco no início de qualquer linha não vazia (portanto, '\S').

AlignAssignments() chama então a função search() integrada para procurar acima (usando os sinalizadores 'bnW') e localiza a primeira linha acima do cursor que não possui precisamente o mesmo deslocamento. Somar 1 ao número dessa linha fornece o início do intervalo de interesse, especificamente, a primeira linha contígua com o mesmo deslocamento que a linha atual.

Uma segunda chamada a search() procura abaixo então ('nW') para determinar lastline: o número da linha contígua final com o mesmo deslocamento. Nesse segundo caso, a procura pode atingir o final do arquivo sem localizar uma linha deslocada de forma diferente, que nesse caso search() retornaria -1. Para tratar desse caso corretamente, a instrução if a seguir configuraria explicitamente lastline para o número da linha do final do arquivo (ou seja, para o número de linha retornado por line('$')).

O resultado dessas duas procuras é que AlignAssignments() agora conhece o intervalo de linhas integral imediatamente acima ou abaixo da linha atual que têm precisamente o mesmo deslocamento que a linha atual. Usa essas informações para assegurar que alinha somente as instruções de designação no mesmo nível de definição de escopo no mesmo bloco de código. A menos que, é claro, o deslocamento de seu código não reflita corretamente seu escopo, mas nesse caso você merece completamente a catástrofe de formatação que está prestes a ocorrer.

O primeiro loop for em AlignAssignments() determina a coluna na qual os operadores de designação deveriam ser alinhados. Isso é feito percorrendo a lista de linhas no intervalo selecionado (as linhas recuperadas por getline(firstline, lastline)) e verificando se cada linha contém um operador de designação (possivelmente precedida por espaço em branco):

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

Se não houver nenhum operador na linha, a função match() integrada falhará em localizar uma correspondência e retornará -1. Nesse caso, o loop simplesmente vai para a próxima linha. Se houver um operador, match() retornará o índice (positivo) no qual esse operador aparece. A instrução if usa então a função max() integrada para determinar se a posição dessa coluna mais recente está mais à direita do que qualquer operador localizado anteriormente, acompanhando, assim, a posição máxima da coluna necessária para alinhar todas as designações do intervalo.

let max_align_col = max([max_align_col, left_width])

As duas linhas remanescentes de if usam a função matchstr() integrada para recuperar o operador real, então, strlen() integrado para determinar seu comprimento (que será 1 para um "=" mas 2 para '+=', '-=', etc.). A variável max_op_width é usada então para controlar a largura máxima necessária para alinhar os diversos operadores no intervalo:

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

Quando o local e a largura da zona de alinhamento tiverem sido determinados, tudo que permanece é iterar através das linhas no intervalo e reformatar as mesmas conforme necessário. Para realizar essa reformatação, a função usa a função printf() integrada. Essa função é muito útil, mas também muito mal denominada. Não é o mesmo que a função printf em C, Perl ou PHP. É, na verdade, igual à função sprintf nessas linguagens. Ou seja, em Vimscript, printf não imprime uma versão formatada de sua lista de argumentos de dados; retorna uma cadeia de caracteres contendo uma versão formatada de sua lista de argumentos de dados.

O ideal, para reformatar cada linha, AlignAssignments() usaria a função substitute() integrada e substituiria tudo até o operador por uma reorganização de printf desse texto. Infelizmente, substitute() espera uma cadeia de caracteres fixa como seu valor de substituição, não uma chamada de função.

Portanto, para usar printf() para reformatar cada texto de substituição, é necessário usar o formato de substituição integrado especial: "\=expr". \= de orientação na cadeia de caracteres de substituição indica a substitute() para avaliar a expressão que segue e usar o resultado como o texto de substituição. Observe que isso é semelhante ao mecanismo <C-R>= no modo de Inserção, exceto por esse comportamento mágico funcionar somente para a cadeia de caracteres de substituição da função substitute() integrada (ou no comando Vim :s/.../.../ padrão).

Neste exemplo, o formulário de substituição especial será o mesmo printf para cada linha, portanto, é pré-armazenado na variável FORMATTER antes do segundo loop for começar:

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

Quando for eventualmente chamado por substitute(), esse printf() integrado irá alinhar pela esquerda (usando um marcador %-*s ) tudo à esquerda do operador (submatch(1)) e colocará o resultado em um campo que tenha max_align_col caracteres de largura. Será, então, alinhar pela direita (usando um %*s) o próprio operador (submatch(2)) em um segundo campo que tenha max_op_width caracteres de largura. Consulte :help printf() para obter detalhes sobre como as opções - e * modificam os dois especificadores de formato %s usados aqui.

Com esse formatador agora disponível, o segundo loop for pode iterar finalmente através do intervalo integral de números de linhas, recuperando o conteúdo do buffer de texto correspondente, uma linha por vez:

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

O loop usa então substitute() para transformar esse conteúdo, correspondendo tudo até e inclusive qualquer operador de designação (usando o padrão em ASSIGN_LINE) e substituindo esse texto pelo resultado da chamada printf() (conforme especificado por FORMATTER):

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

Quando o loop for tiver iterado todas as linhas, quaisquer operadores de designação nas mesmas agora serão alinhados corretamente. Tudo que resta é criar um mapeamento de chave para chamar AlignAssignments(), assim:

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

Verificando Adiante

Funções são uma ferramenta essencial para decompor um aplicativo em componentes corretos e que podem passar por manutenção, para gerenciar a complexidade de tarefas de programação Vim do mundo real.

Vimscript permite definir funções com listas de parâmetro fixos ou com aridade variável e fazer com que interajam automaticamente ou de maneiras controladas pelo usuário com intervalos de linhas no buffer de texto do editor. Funções podem fazer retorno de chamada a recursos integrados de Vim (por exemplo, para texto de search() ou substitute() ) e também podem acessar informações de estado do editor diretamente (como determinando a linha atual na qual o cursor está através de line('.')) ou interagir com qualquer buffer que esteja sendo editado atualmente (através de getline() e setline()).

Isso é, sem dúvida, um recurso poderoso, mas nossa capacidade de manipular o estado e o conteúdo de forma programática é sempre limitado pelo nível de limpeza e precisão com que podemos representar os dados nos quais nosso código opera. Até o momento nesta série de artigos, ficamos restritos ao uso de valores escalares singulares (números, cadeias de caracteres e booleanos). Nos dois próximos dois artigos, vamos explorar o uso de estruturas de dados muito mais poderosas e convenientes: listas ordenadas e dicionários de acesso aleatório.

Recursos

Aprender

Obter produtos e tecnologias

Discutir

  • Envolva-se com a comunidade My developerWorks; com seu perfil pessoal e página inicial customizada, é possível padronizar o developerWorks para seus interesses e interagir com outros usuários do developerWorks.

Comentários

developerWorks: Conecte-se

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


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

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

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=415939
ArticleTitle=Criando Script do Editor Vim, Parte 2: Funções Definidas pelo Usuário
publish-date=07072009