Stwierdziłem niedawno, że brakuje mi w Vimie takiego drobiazgu jak wyświetlanie listy funkcji z edytowanego właśnie pliku. Można z jednej strony wykorzystać ctags, ale (pewien nie jestem, nie jestem znawcą tagsów ani ich obsługi w Vimie) po dodaniu funkcji/metody/czegokolwiek innego co można by nazwać tagiem trzeba by od nowa generować listę tagów. Nie jest to problemem takim bardzo dużym, do momentu gdy ciągle pracuje się nad różnymi projektami, z czego sporej części nie mam na dysku (tylko wykorzystując plugin netrw pracuję via ftp). W tym momencie moja znajomość systemu ctags podpowiada mi że robi się ciężko…
Postanowiłem więc napisać sobie prosty skrypt do tego, a że język wewnętrzny Vima mnie nieco odrzuca, postawiłem na Pythona ;) Żeby jakikolwiek skrypt pythona (jak ktoś lubi, można użyć też np perla) mógł zadziałać w Vimie, ten ostatni musi być skompilowany z jego obsługą. W Ubuntu wystarczy zainstalować pakiet vim-full. Łatwo sprawdzić czy Vim ma wkompilowaną obsługę pythona:
vim --version|gi '+python'
Jeśli wyświetliła Ci się podobna linijka:
+python +quickfix +reltime +rightleft +ruby +scrollbind +signs +smartindent
to znaczy że możesz śmiało używać pythona do oskryptowania Vima :)
Skrypty Pythona w Vimie przekazywane są do funkcji Vimowej o zaskakującej, nietypowej nazwie python
. Tradycyjne Hello world wyglądałoby tak:
:python print "Hello world"
Interakcję z samym Vimem umożliwia nam moduł o kolejnej zaskakującej nazwie: vim
, który powinniśmy zaimportować na początku funkcji/skryptu. Szczegółowa jego dokumentacja jest na stronach Vima: vim.org/htmldoc/if_pyth.html.
Pierwszym krokiem jest wczytanie zawartości całego bufora. Do jego zawartości dostajemy się poprzez obiekt vim.current.buffer:
fc = "\n".join(line for line in vim.current.buffer)
Samo wyszukanie deklaracji funkcji w plikach pythona można by zrobić mniej więcej tak:
re.findall(r'^\s*def\s+(\w+)', fc, re.M)
Jednak nam najwygodniej pobrać będzie jednocześnie z nazwą funkcji, także całą linijkę w której ta została zdefiniowana. Przyda to się przy przechodzeniu do miejsca w kodzie w którym funkcja jest zdefiniowana. Ja końcowo zrobiłem to tak:
re.findall(r'^(\s*def\s+(\w+\s*\(.*?\))\s*:)', fc, re.M)
Teraz, gdy mamy listę funkcji, trzeba je wyświetlić userowi, a następnie pobrać informację do deklaracji jakiej funkcji w kodzie chcemy się przenieść. Najpierw chciałem skorzystać ze standardowego pythonowego raw_input(), ale ta funkcja w Vimie nie działa. Użyłem wobec tego Vimowego input()
. Aby skorzystać z funkcji Vimowych, moduł vim udostępnia nam dwie funkcje: vim.eval()
i vim.command()
. Tutaj odpowiedni będzie vim.eval()
:
choice = vim.eval('input("Which one: ")')
W tej chwili mamy już praktycznie napisany cały skrypt. Pozostało tylko znalezienie offsetu w jakim się zaczyna deklaracja wybranej przez usera funkcji w buforze, a następnie za pomocą vim.command()
przenieść kursor w wybrane miejsce:
vim.command('goto '+str(pos+1))
Całość w moim wykonaniu jest nieco bardziej rozbudowana (minimalnie), jako że staram się zrobić sobie mały framework do skryptowania Vima. Wycinek realizujący odpowiednią funkcjonalnośc w moim przypadku wygląda tak:
" importy ogólne, przydatne funkcje, etc
python << EOF
import re
import sys
import vim
def MyDevPlugins_GetFileType(default=None):
ft = vim.eval('synIDattr(synID(line("."), col("."), 1), "name")')
if ft:
m = re.findall(r'^([a-z]+)', ft)
if m:
return m[0]
if not ft:
ft = default
return ft
EOF
" FindSubs - wyszukiwanie funkcji
python << EOF
FindSubs_ftypes = {
'perl': r'^(\s*sub\s+(\w+(?:\s*\(.*?\))?))',
'python': r'^(\s*def\s+(\w+\s*\(.*?\))\s*:)',
'php': r'^(\s*function\s+(\w+\s*\(.*?\)))',
}
FindSubs_ftypes['java'] = FindSubs_ftypes['html'] = FindSubs_ftypes['php']
def FindSubs_Find(ft='perl'):
try:
rxp = FindSubs_ftypes[ft]
except KeyError:
print 'Unknown filetype: "%s"' % ft
return
# read buffer content
fc = "\n".join(line for line in vim.current.buffer)
# find all functions names
m = re.findall(rxp, fc, re.M)
if not m:
print 'Not found'
return
# print them for user
for i, sub in enumerate(m):
print '%d. %s' % (i+1, sub[1])
# ask for function he want to go
choice = vim.eval('input("Which one: ")')
try:
choice = int(choice) - 1
except:
print 'Incorrect value'
return
# find file offset
pos = fc.find(m[choice][0].strip())
if pos < 0:
print >>sys.stderr, "Some error occured."
return
# and go there
vim.command('goto '+str(pos+1))
def FindSubs_Main():
FindSubs_Find(MyDevPlugins_GetFileType())
EOF
function! FindSubs()
python FindSubs_Main()
endfunction
map <F5> :call FindSubs()<CR>
Ostatnia linijka odpowiada za podmapowanie wywołania funkcji pod klawisz F5.
Natomiast problemy sprawia opcja rozpoznawania typu pliku. Nie doszukałem się nigdzie w dokumentacji lepszego sposobu, a ten czasem zawodzi :( Nic na to chwilowo nie poradzę. Oczywiście przydałoby się rozpoznawanie większej ilości języków, ale to co jest obecnie wystarcza mi w zupełności, w razie czego można sobie dorobić odpowiednie regexpy do słownika FindSubs_ftypes.