Follow me
RSS feed
My sources
My Viadeo

Développer des modules pour VIM

Greg | 05 Oct 2011

image-project Depuis que je suis (re)passé sous Vim pour développer, j’ai passé un peu de temps à me peaufiner une configuration aux petits oignons. En effet, quand on a pris l’habitude de développer avec un IDE, le moindre refactoring, la moindre recherche ou les imports automatiques nous font oublier que dans la vraie vie, tout n’est pas aussi luxueux ;)

Heureusement, il existe un grand nombre de plugins pour Vim qui peuvent grandement nous simplifier la vie. Parmi ceux-ci, j’utilise :

Une fois ces modules installés, il me manque trois petites choses:

  1. Afficher les différences entre le fichier en cours d’édition et sa dernière version sauvegardée.
  2. La possibilité de rechercher des fichiers.
  3. Pouvoir naviguer entre les fichiers d’un projet via les tags.

J’ai donc créé trois extensions permettant de répondre à ces besoins.

filediff.vim

L’idée est de pouvoir visualiser les différences entre le fichier en cour d’édition et sa dernière version sauvegardée.

filediff.vim
filediff.vim

Voici le code :

 1 " Original ! (see :help :DiffOrig)
 2 command DiffOrig vert new | set bt=nofile | r # | 0d_ | diffthis | wincmd p | diffthis
 3 
 4 let s:diff_buffer_number = -1
 5 
 6 function! s:diffToggle()                     
 7    if s:diff_buffer_number == -1
 8       exec 'vert new | set bt=nofile | set ma | r # | 0d_ | diffthis | set noma | wincmd p | diffthis'
 9       let s:diff_buffer_number = last_buffer_nr()   
10    else
11       exec s:diff_buffer_number . "bd"       
12       let s:diff_buffer_number = -1          
13    endif
14 endfunction
15 
16 command! DiffOrigToggle call s:diffToggle()

A la ligne 2 nous créons la commande DiffOrig. Elle est directement issue de la documentation de Vim (:help :DiffOrig). Cette commande remplit presque parfaitement le besoin. Cependant, si vous l’essayez, nous verrez qu’elle présente deux défauts :

  1. Pour fermer la fenêtre présentant le code sauvegardé, il faudra, soit se déplacer dans la fenêtre en question (Ctrl-w-w) et la fermer (:q), soit lister les buffers (:ls) et fermer celui correspondant à la fenêtre (:<n>bd). Il serait plus simple de pouvoir rappeler la même commande qui fermerait la fenêtre si elle est déjà ouverte. Ainsi, en mappant l’appel de la commande, nous aurions un effet de toggle.
  2. Ensuite, la fenêtre créée (via vert new) est éditable. Ce qui peut être dangereux.

Pour résoudre ces deux problèmes, nous allons créer la fonction s:diffToggle. Dans cette fonction nous utiliserons la variable s:diff_buffer_number afin de savoir si nous avons un différentiel affiché. Ainsi, si s:diff_buffer_number est égal à –1, alors nous allons utiliser le même enchaînement que celui proposé par :DiffOrig et nous récupèrerons le numéro du dernier buffer ouvert (via last_buffer_nr()). Si s:diff_buffer_number est différent de –1 nous fermons le buffer référencé dans s:diff_buffer_number. Pour éviter que la fenêtre contenant le code du fichier sauvegardé ne soit éditable, nous faisons une petite modification dans l’enchainement des commandes de :DiffOrig en utilisant set nomodifiable (set noma).

Vim-find

L’idée, ici, est de créer une commande permettant de rechercher des fichiers sur le même modèle que la commande find.

vim-find
vim-find

Le code utilisé est le suivant :

 1 " Find file in current directory and edit it.
 2 let g:findprg="find\\ .\\ -type\\ f\\ -name\\ "
 3 let g:findprgend="-exec bash -c \"echo '{}':0:0\" \\\;"
 4 function! Find(args)
 5     let grepprg_bak=&grepprg
 6     exec "set grepprg=" . g:findprg
 7     execute "silent! grep \"" . a:args . "\" " . g:findprgend
 8     botright copen
 9     let &grepprg=grepprg_bak
10     exec "redraw!"
11 endfunction
12 command! -nargs=* -complete=file Find call Find(<q-args>)

Pour faire cela, nous utilisons la commande :grep en remplaçant temporairement le contenu de grepprg par la commande suivante :

Si vous essayez cette commande depuis le shell, en remplacant <args> par une expression rationnelle correspondant à une liste de fichiers, vous devriez voir s’afficher une liste de fichier sous la forme :

Nous utilisons :copen pour afficher le résultat de notre recherche dans une fenêtre ouverte en bas de le fenêtre courante (via :botright).

cscope.vim

cscope est un outil permettant de naviguer dans du code. Il permet ainsi de retrouver (par exemple) le fichier de déclaration d’une méthode, et toutes les utilisations de cette méthode.

Il est possible de compiler Vim avec le support de cscope (--enable-cscope), à la suite de quoi nous pourrons utiliser la commande :cscope pour parcourir le code. Ce qui me dérange le plus, dans ce support de cscope, est la façon dons les résultats de recherche sont affichés. En effet, ils sont présentés en dehors de Vim alors qu’une présentation similaire à celle mise en place dans le cas de la recherche de fichiers proposée ci-dessus serait beaucoup plus agréable.

vim-cscope
vim-cscope

Voici le code que j’ai donc mis en place pour arriver à ce résultat :

 1 if &cp || exists('g:loaded_cscope')          
 2    finish                                    
 3 endif
 4 
 5 if !exists('g:cscope_bin')                   
 6    if executable('cscope')                   
 7       let g:cscope_bin = 'cscope'            
 8    elseif executable('cscope.exe')           
 9       let g:cscope_bin = 'cscope.exe'        
10    else
11       echomsg 'cscope: cscope command not found, skipping plugin'
12       finish
13    endif
14 else
15    let g:cscope_bin = expand(g:cscope_bin)   
16    if !executable(g:cscope_bin)              
17       echomsg 'cscope: cscope command not found in specified place,'
18                \ 'skipping plugin'           
19       finish                                 
20    endif
21 endif
22 
23 let g:loaded_cscope = 1 
24 
25 if !exists('g:cscope_autoregen')             
26    let g:cscope_autoregen=1                  
27 end
28 
29 let g:cscope_files_exist=0
30 if !exists('g:cscope_file') 
31    let g:cscope_file = ".cscope"
32 end
33 let g:cscope_reffile = g:cscope_file . ".out"
34 let g:cscope_sourcefile = g:cscope_file . ".files"
35 if filereadable(g:cscope_reffile) && filereadable(g:cscope_sourcefile)
36    let g:cscope_reffile=expand(g:cscope_reffile) 
37    let g:cscope_sourcefile=expand(g:cscope_sourcefile)
38    let g:cscope_files_exist=1
39 end
40 
41 function! CscopeAdd(...)
42    let remove_sourcefile=system("rm -f " . g:cscope_sourcefile)
43    for ext in a:000
44       let find_command="find . -name \"*." . ext . "\" >> " . g:cscope_sourcefile
45       let find_result=system(find_command)
46    endfor
47 
48    let cscope_command=g:cscope_bin . " -b -f " . g:cscope_reffile . " -i " . g:cscope_sourcefile
49    let cscope_result=system(cscope_command)
50 
51    let g:cscope_files_exist=1
52 
53    echomsg "cscope reference and source files created : " . g:cscope_reffile . ", " . g:cscope_sourcefile
54 endfunction
55 command! -nargs=* CscopeAdd call CscopeAdd(<f-args>)
56 
57 if exists('g:cscope_autogen')
58    call CscopeAdd(g:cscope_autogen)
59 end
60 if g:cscope_autoregen == 1 && g:cscope_files_exist == 1
61    " TODO: regenerate cscope files!
62 end
63 
64 function! CscopeFind(index, symbol)
65    if g:cscope_files_exist == 0
66       echomsg "cscope: no reference and source files found. Use :CscopeAdd to generate them"
67    else
68       let cscope_command=g:cscope_bin . " -f " . g:cscope_reffile . " -i " . g:cscope_sourcefile .  " -L -" . a:index . " " . a:symbol . " | awk '{ printf(\"%s|%s|\", $1, $3); for(i=4; i<NF; i++) { printf(\" %s\", $i) }; printf(\"\\n\") }'"
69       cexpr system(cscope_command)
70       botright copen
71    end
72 endfunction
73 command! -nargs=* CscopeFind call CscopeFind(<f-args>)
74 nmap <leader>a :CscopeFind 0 <c-r>=expand("<cword>")<CR><CR>
75 nmap <leader>d :CscopeFind 1 <c-r>=expand("<cword>")<CR><CR>
76 nmap <leader>t :CscopeFind 4 <c-r>=expand("<cword>")<CR><CR>
77 nmap <leader>g :CscopeFind 6 <c-r>=expand("<cword>")<CR><CR>

Pour commencer, nous vérifions que nous le sommes pas en mode compatible et que le plugin n’a pas déjà été chargé (lignes 1 à 3).

Les lignes 5 à 13 servent à rechercher la présence d’un exécutable cscope (ou cscope.exe) dans le PATH, et a stocker cette information dans la variable g:cscope_bin. Si la variable g:cscope_bin a été positionnée par l’utilisateur (dans son .vimrc, par exemple), nous vérifions bien qu’elle pointe vers un exécutable (lignes 14 à 21).

Pour fonctionner, cscope a besoin de deux fichiers :

Le premier fichier peut simplement être créé avec un find. Par exemple, pour lister tous les fichiers dans un projet en C nous utiliserons la commande suivante :

find . -name "*.[c|h]" > cs.files

Pour créer le second fichier, nous utilisons cscope :

cscope -b -f cs.out -i cs.files

Ces deux fichiers étant créés, nous pouvons rechercher des références dans le projet :

cscope -f cs.out -i cs.files -L -0 Init_rosxauth

Nous devons donc faire la même chose dans notre module VIM.

Nous allons utiliser les variables g:cscope_sourcefile et g:cscope_reffile pour stocker les chemins vers les fichiers de sources et références. Ceci est mis en place lignes 29 à 39. Au passage, nous positionnons la valeur 1 dans la variable g:cscope_files_exist si ces fichiers existent déjà.

Nous mettons ensuite en place la commande CscopeAdd, associé à la fonction du même nom. Cette fonction prend en paramètre la liste des extensions de fichiers à faire analyser par cscope, et crée les fichiers de sources et de références (lignes 41 à 55).

Pour faire la recherche, nous créons la commande (et la fonction) CscopeFind qui prend en paramètre la référence recherchée et affiche les résultats trouvés (lignes 64 à 73). La fonction CscopeFind prend en paramètre le périmètre de recherche et le terme recherché. Pour simplifier ces recherches, j’ai ajouté des mappings vers certains appels de cette commande (lignes 74 à 77). Ainsi, en nous positionnant sur un terme et en tapant <leader>a nous retrouvons toutes ses définitions…

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.