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.


Liczba komentarzy: 5

  1. 1 Był poniedziałek, 18 Luty 2008 roku gdy o godzinie 00:21 przyszedł pybar i stwierdził:

    Natomiast problemy sprawia opcja rozpoznawania typu pliku. Nie doszukałem się nigdzie w dokumentacji lepszego sposobu, a ten czasem zawodzi

    Właśnie trafiłem na tego posta, więc podpowiem ci rozwiązanie – może się przyda:


    :py import vim
    :py print vim.eval("&ft")
    python

    ww kod po prostu wyrzuca to, co znajduje się w zmiennej ‘filetype’.
    Long live to VIM! :)

  2. 2 Był poniedziałek, 18 Luty 2008 roku gdy o godzinie 10:11 przyszedł MySZ i stwierdził:

    Na ten sposób trafiłem jakiś czas temu, i zacząłem go używać. Niestety, też ma pewne wady: zwraca typ kolorowania dla całego pliku. A co z plikami takimi jak .php, gdzie częściowo można mieć treść HTML, dla której (mówię tu na przykładzie skryptu do zakomentowania linii) zakomentowanie wygląda inaczej niż dla php? Kompletnie nie ma jak tego obejść :(

  3. 3 Był poniedziałek, 17 Marzec 2008 roku gdy o godzinie 20:36 przyszedł Radek i stwierdził:

    Można używać zewnętrznych poleceń powłoki? Jeżeli tak, typ pliku można pobrać przy pomocy polecenia “file”. Analogicznie:

    radek@:~ # file www/skrypt.php
    www/skrypt.php: PHP script text

    radek@:~ # file www/auth.py
    www/auth.py: python script text executable

    Pozdrawiam.

  4. 4 Był poniedziałek, 17 Marzec 2008 roku gdy o godzinie 22:56 przyszedł MySZ i stwierdził:

    @Radek: No tutaj jest ten sam problem co z &ft: pobiera typ całego pliku. Poza tym wynik file jest dość ciężko w sumie parsować. Zrobiłem inaczej: dla skryptu który znaduje mi funkcje w pliku sprawdzam najpierw zmienną &ft, jeśli to nic nie da, pobieram rozszerzenie pliku.
    Dla drugiego skryptu, który komentuje mi linie, sprawdzam kombinacją synIDattr (synID ()). Jak na razie w większości wypadków działa :)

  5. 5 Była niedziela, 06 Kwiecień 2008 roku gdy o godzinie 01:12 przyszedł myGeeBlog » Blog Archive » Skryptowanie Vima w Pythonie - cz.2 i stwierdził:

    [...] sobie różne udoskonalenia dla Vima, i piszę je zazwyczaj w Pythonie :) Niedawno opisywałem jak wyświetlić listę funkcji z edytowanego pliku, później zrobiłem sobie wygodne komentowanie kodu (wersje które znalazłem na sieci nie [...]

A Ty? Co o tym myślisz?

Możesz używać w komentarzach następujących znaczników:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Sblam! Antyspam