Od jakiegoś czasu wymyślam 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 satysfakcjonowały mnie), teraz przyszedł czas na sprytne (w sensie: wygodne) uruchamianie właśnie edytowanego skryptu/programu :)
Do niedawna używałem prostego Pythonowego skryptu, który wywoływem z Vima:
!$ %
Wykrzyknik to polecenie wywołania programu z shella, $ to nazwa skryptu, a % jest rozwijany przez Vima do pełnej ścieżki bieżącego pliku. Było to o tyle wygodne, że nieważne w jakim języku pisałny był skrypt/program, $ uruchamiał odpowiedni interpreter, ze skonfigurowanymi parametrami i wyświetlał wyjście. Tą funkcjonalność oczywiście potrzebowałem zachować, ale wykonywanie zewnętrznego programu jest strasznie niewygodne ;) Więc zrobiłem sobie wygodniejszą, i nieco bardziej rozbudowaną wersję. Inne założenia to możliwość przekazywania parametrów do wykonywanego programu, oraz dla niektórych języków możliwość wcześniejszej, automatycznej kompilacji.
Założyłem taki format konfiguracji poszczególnych typów plików:
[wywolanie interpretera, przeksztalcenie pliku wykonywalnego]
Gdzie wywolanie interpretera
może być tuplą/listą wywołań lub zwykłym napisem z kluczami:
%(fname)s
- nazwa bieżącego pliku%(exe)s
- nazwa wersji wykonywalnej (nie jest wymagane)%(params)s
- dodatkowe parametry
Klucze te są zastępowane oczywiście właściwymi danymi z wykorzystaniem skłądni “procentowej”. Przykład takiej konfiguracji:
'java': [('javac -Xlint %(fname)s', 'java %(exe)s %(params)s'), lambda f: os.path.splitext(os.path.basename(f))[0], ],
W takim przypadku zostanie najpierw zostanie uruchomione polecenie javac -Xlint %(fname)s, następnie java %(exe)s %(params)s
, gdzie %(fname)s
zostanie zastąpione pełną ścieżką do bieżącego pliku, %(exe)s
rezultatem działania funkcji lambda f: os.path.splitext(os.path.basename(f))[0]
(która zwróci samą nazwę pliku bez ścieżki i rozszerzenia), a %(params)s
– dodatkowymi opcjami. Zamiast funkcji generującej wersję exe
można podać napis którym ma zostać zastąpione rozszerzenie pliku, lub None
jeśli nie ma być w ogóle zmieniane.
Największym problemem było wygodne uruchamianie zbudowanej funkcji – lepiej mi używać już zewnętrznego skryptu niż pisanie na przykład:
python MDP_Execute('arg1', 'arg2', 'arg3')
W tym celu stworzyłem sobie funkcję Vimową (nie pythonową!) o tej samej nazwie (czyli MDP_Execute ()
), a do tego dorzuciłem komendę którą nazwałem Exe
. Domyślnie komendy w Vimie nie pozwalają na używanie dodatkowych argumentów. Można jednak zmienić to zachowanie poprzez dodatkowy parametr -nargs
, który może przyjąć wartości:
- 0 - wartość domyślna, nie pozwalająca na argumenty
- 1 - musi być dokładnie jeden argument (może to być oczywiście inna cyfra/liczba)
- * - dowolna ilość argumentów
W moim przypadku odpowiednim parametrem była gwiazdka:
-nargs=*
Problemem jeszcze było odebranie tych parametrów wewnątrz funkcji, a także przekazanie ich do funkcji pythonowej. Rozwiązaniem była najpierw definicja funkcji Vimowej z “trzykropkiem”:
function! MDP_Execute (...)
dzięki czemu funkcja może przyjąć dowolną ilość argumentów (ale mniejszą lub równą 20 – ograniczenie Vima), a odbiera się je wewnątrz funkcji jako zmienne funkcyjne (tzn. takie które istnieją tylko wewnątrz funkcji) o nazwie takiej samej jak index tej funkcji. Jest to mało jasne wyjaśnienie, więc może przykład:
function! MDP_Execute (...)
echo a:1 " wydrukuje pierwszy z parametrów wywołania
echo a:2 " wydrukuje drugi z parametrów, etc
endfunction
Specjalne znaczenie mają dwie zmienne: a:0 i a:000 – pierwszy zawiera liczbę podanych (nienazwanych) parametrów, drugi zawiera listę tychże (wygodne gdy musimy po tym przeiterować).
Następnie trzeba było przekazać argumenty podane do komendy do funkcji. Robi się to za pomocą argumentu <args>, a konkretnie jego odmiany <q-args>, który automatycznie przekształca parametry komendy na odpowiednio sformatowaną wersję (czyli nie musimy sami wkładać poszczególnych parametrów w cudzysłowia jakby trzeba to było robic z wersją <args>). W tym momencie można już w funkcji Vimowej MDP_Execute()
odebrać wywołanie komendy:
Exe arg1 arg2 "long arg3"
Pozostaje przekazanie tych samych argumentów do funkcji Pythonowej, ale tutaj po części wyręcza nas sam Vim – po prostu z wewnątrz funkcji pobierzemy sobie wartość zmiennej a:000 za pomocą wbudowanej w moduł vim funkcji eval()
:
data['params'] = ' '.join(vim.eval('a:000'))
Całość skrypciku wygląda tak:
if !has('python')
echo "Error: Required vim compiled with +python"
finish
endif
python << EOF
import os.path
import subprocess
import types
import vim
def MDP_System (cmd):
p = subprocess.Popen (cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
ret = list (p.communicate ())
ret.insert (0, p.returncode)
return ret
def MDP_GetFileType (default=None):
ft = vim.eval ('&ft')
if not ft:
ft = os.path.splitext (vim.current.buffer.name)
if len (ft) > 1:
ft = ft[1][1:].lower ()
else:
ft = default
return ft
MDP_Run_Script_ftypes = {
'python': ['python -tt %(fname)s %(params)s', ],
'perl': ['perl %(fname)s %(params)s', ],
'sh': ['bash %(fname)s %(params)s', ],
'txt': ['bash %(fname)s %(params)s', ],
'php': ['php -f %(fname)s %(params)s', ],
'cs': [('mcs %(fname)s', 'mono %(exe)s %(params)s'), '.exe', ],
'd': [('rebuild -oqobj %(fname)s', '%(exe)s %(params)s'), '', ],
'java': [('javac -Xlint %(fname)s', 'java %(exe)s %(params)s'), lambda f: os.path.splitext (os.path.basename (f))[0], ],
}
def MDP_Execute ():
cmd_data = MDP_Run_Script_ftypes.get (MDP_GetFileType (), '')
if not cmd_data:
print 'Unknown filetype to execute'
return
if not isinstance (cmd_data[0], (list, tuple)):
cmd_data[0] = ( cmd_data[0], )
data = dict (
fname = vim.current.buffer.name,
params = ' '.join (vim.eval ('a:000')),
exe = vim.current.buffer.name,
)
# jesli trzeba, to wyszukujemy wersje exe
if len (cmd_data) >= 2:
if isinstance (cmd_data[1], str):
tmp = os.path.splitext (data['fname'])[0]
if cmd_data[1]:
tmp += '.' + cmd_data[1]
data['exe'] = tmp
elif isinstance (cmd_data[1], types.FunctionType):
data['exe'] = cmd_data[1] (data['fname'])
for c in cmd_data[0]:
res = MDP_System (c % data)
print res[1]
if res[0]:
print 'Błąd:'
print res[2]
EOF
function! MDP_Execute (...)
python MDP_Execute ()
endfunction
command! -nargs=* Exe call MDP_Execute (<q-args>)
map <C-e> <Esc>:call MDP_Execute ()<cr>
Ostatnia linijka odpowiada za wykonanie skryptu (bez żadnych parametrów) po wciśnięciu kombinacji klawiszy Ctrl+e. Jeśli wymagane są jakiekolwiek parametry, trzeba ręcznie wywołać komendę Exe podając po niej wymagane do poprawnego działania naszego programu argumenty :)