Textový editor Vim jako IDE (8.část)

Pavel Tišnovský 18. 8. 2011

Dnes dokončíme popis interního skriptovacího jazyka editoru a ukážeme si i několik více či méně užitečných příkladů jeho použití v praxi. Bude se například jednat o funkci, která do zdrojového kódu umožní vložit tělo počítané programové smyčky, popř. ze jména atributu vytvořit jeho getter a setter.

Obsah

1. Předávání parametrů funkcím

2. Parametry se při volání funkcí předávají hodnotou

3. Nepřímý přístup k proměnným přes jejich název reprezentovaný řetězcem

4. Využití uživatelských funkcí v praxi – vkládání připravených kusů programového kódu (code snippets) do editovaného textu

5. Použití funkce CreateForLoop()

6. Využití uživatelských funkcí v praxi – properties v C#

7. Přepis funkce CreateProperty() pro programovací jazyk Java

8. Vylepšení funkce pro automatické vytváření getterů a setterů

9. Odkazy na Internetu

1. Předávání parametrů funkcím

V předchozích dvou částech seriálu o textovém editoru Vim jsme si popsali většinu důležitých vlastností interního skriptovacího jazyka nazvaného Vim Script. Zbývá nám ještě popsat, jakým způsobem se předávají parametry do volaných funkcí a jak lze zařídit předávání parametrů odkazem (i když v tomto případě se jedná spíše o určité zneužití vlastností Vim Scriptu). Začněme jednoduchým příkladem – funkcí, která očekává dva parametry, kterými by měla být dvě čísla (nebo řetězce převeditelné na čísla). Funkce obě čísla sečte a na pozici kurzoru v aktivním bufferu vloží výsledek tohoto součtu. Název funkce by měl začínat velkým písmenem, tj. například Add a nikoli add. Dále si povšimněte, že k parametrům funkce se přistupuje s využitím prefixu a: a že je možné vkládat nový obsah i do speciálního pracovního registru ", jehož název musí začínat prefixem @ – viz též předminulá část tohoto seriálu.

Posledním novým příkazem je execute s parametrem začínajícím na „normal“. Tento příkaz zajistí, že se další sekvence znaků provede v normálním režimu. Ona sekvence znaků se v našem případě rovná pouze jedinému znaku p, což v normálním režimu odpovídá editačnímu příkazu put. Pokud není explicitně specifikován žádný pracovní registr, umístí tento příkaz za pozici kurzoru obsah speciálního pracovního registru ", který jsme však naplnili součtem parametrů funkce. Podívejme se na zdrojový kód:

function! Add(parametr1, parametr2)
    " nejprve se provede soucet obou parametru
    let soucet = a:parametr1 + a:parametr2

    " soucet se ulozi do specialniho pracovniho
    " registru "
    let @" = soucet

    " nasledne se pomoci prikazu execute
    " vykona editacni prikaz 'p' = put
    execute "normal p"
endfunction

Obrázek 1: Obarvený zdrojový kód prvního demonstračního příkladu s funkcí Add().

Funkci lze otestovat následovně – samotná funkce se uloží do souboru pojmenovaného test_add.vim. Posléze se spustí Vim a zadá se příkaz:

:source test_add.vim

popř. pouze:

:so test_add.vim

který zajistí načtení funkce. Následně je možné v normálním režimu funkci zavolat příkazem:

:call Add(1,2)

Popř. lze použít i poněkud složitější test využívající znalostí, které jsme si ozřejmili v předchozí části tohoto seriálu:

for i in range(1,10)
    call Add(i,i)
    execute "normal o\<Esc>
endfor

V předchozím testu je příkaz execute použit pouze pro odřádkování, aby se jednotlivá čísla nezapisovala přímo za sebou.

Obrázek 2: Spuštění demonstrační funkce Add() ve smyčce.

2. Parametry se při volání funkcí předávají hodnotou

Ve Vim Scriptu se všechny parametry předávají hodnotou, což znamená, že se před zavoláním nějaké funkce všechny její parametry nejprve vyhodnotí (může se jednat o výrazy obsahující i volání jiných funkcí) a teprve poté je funkce zavolána a jsou jí předány hodnoty vypočtených parametrů. V praxi to znamená to, že není možné, aby funkce měnila hodnoty proměnných, které jsou jí (alespoň zdánlivě) předány jako parametry; Vim Script jde dokonce ještě dále a neumožňuje vůbec měnit hodnoty proměnných začínajících prefixem a:, což je poměrně velký rozdíl oproti mnoha dalším programovacím jazykům. Tuto vlastnost Vim Scriptu si můžeme poměrně jednoduše vyzkoušet na následujícím demonstračním příkladu, který po svém spuštění pomocí příkazu:

:source TestVar.vim

Vypíše chybové hlášení při pokusu o změnu proměnné a:variable ve funkci ChangeValue. Následuje výpis zdrojového kódu tohoto příkladu. Povšimněte si, že se jedná o „samospouštěcí“ příklad, protože na svém konci obsahuje volání call TestVar():

function! PrintValue(variable)
    echo "variable=" . a:variable
endfunction

function! ChangeValue(variable)
    let a:variable = 6502
endfunction

function! TestVar()
    let g:x = 42
    call PrintValue(g:x)
    call ChangeValue(g:x)
    call PrintValue(g:x)
endfunction

call TestVar()

Obrázek 3: Zdrojový kód demonstrační funkce Add() a počítané programové smyčky, z níž se tato funkce volá.

3. Nepřímý přístup k proměnným přes jejich název reprezentovaný řetězcem

V některých případech by však bylo vhodné a užitečné umět změnit hodnotu proměnné ve funkci a přitom nepoužívat přímý přístup ke globálním proměnným. Vim Script sice tuto možnost přímo neobsahuje, ale je možné použít malý trik – pokud se ve zdrojovém kódu vyskytuje zápis:

{"jméno_proměnné"}

popř.

{proměnná_obsahující_jméno_proměnné_jako řetězec}

nebo taktéž:

{výraz,_který_se_vyhodnotí_na_řetězec_obsahující_jméno_proměnné}

… je na místo tohoto zápisu přímo dosazena proměnná daného jména. To znamená, že výše uvedený demonstrační program je možné upravit tak, aby se funkci ChangeValue předávalo jméno proměnné a nikoli její hodnota, tj. namísto g:x se použije zápis „g:x“:

function! PrintValue(variable)
    echo "variable=" . a:variable
endfunction

function! ChangeValue(variableName)
    let {a:variableName} = 6502
endfunction

function! TestVar()
    let g:x = 42
    call PrintValue(g:x)
    call ChangeValue("g:x")
    call PrintValue(g:x)
endfunction

call TestVar()

4. Využití uživatelských funkcí v praxi – vkládání připravených kusů programového kódu (code snippets) do editovaného textu

Interní skriptovací jazyk Vim Script je možné využít mnoha různými způsoby, jak pro psaní jednoduchých pomocných funkcí umisťovaných většinou do konfiguračního souboru .vimrc (což ovšem není nic jiného, než plnohodnotný skript a nikoli pouze jednoduchý soubor s konfigurací), tak i pro programování rozsáhlých přídavných modulů, mezi něž patří například i v příští části tohoto seriálu popsaný modul Netrw. V této kapitole si ukážeme, jak lze napsat poměrně jednoduchou funkci určenou pro vložení připraveného bloku programového kódu (code snippet) do editovaného souboru. Tato funkce bude sice velmi jednoduchá, ale jedná se o prazáklad složitějších a pro práci velmi užitečných plnohodnotných přídavných modulů, mezi něž patří především modul snipMate, jehož popisem se budeme zabývat v následující části tohoto seriálu. Naše ukázková funkce bude sloužit pro vložení zdrojového kódu představujícího počítanou programovou smyčku typu for zapsanou podle syntaktických pravidel céčka nebo Javy. Před vyvoláním této funkce musí být na aktivním řádku zapsána počáteční a koncová hodnota smyčky, tj. například dvě číslice, názvy konstant atd. oddělené od mezerou

Obrázek 4: Obarvený zdrojový kód funkce CreateForLoop().

Tělo funkce nazvané CreateForLoop vypadá následovně:

" velmi jednoduchy snippet - smycka for
function! CreateForLoop()
    " presunout kurzor na zacatek pocatecni hodnoty pocitadla
    execute "normal bb"
    " prvni radek pocitane smycky for
    execute "normal ifor (int i = \<Esc>ea; i <\<Esc>ea; i++) {"
    " prazdne telo smycky
    execute "normal o\<Esc>"
    " konec tela a presunuti kurzoru do prikazoveho bloku
    execute "normal o}\<CR>\<CR>\<Esc>kkka    "
endfunction

Obrázek 5: Obsah textového souboru před zavoláním funkce CreateForLoop().

Povšimněte si především způsobu použití příkazu execute (zkracovaného taktéž na exe), který v podstatě simuluje příkazy zapisované přímo z klávesnice uživatelem. Za příkazem execute následuje řetězec, v němž je napsáno, ve kterém režimu (normální, vkládací…) má příkaz pracovat a poté již následuje text, který odpovídá sekvenci znaků zapisovaných na klávesnici. Jak je ze zápisu funkce patrné, je nutné některé znaky se speciálním významem uvozovat pomocí zpětného lomítka. Jedná se především o znak <, ale též o znak uvozovek (ten však není v příkladu použit).

Obrázek 6: Obsah textového souboru po zavolání funkce CreateForLoop().

5. Použití funkce CreateForLoop()

Výše uvedenou funkci CreateForLoop() je sice možné kdykoli zavolat pomocí následujícího příkazu zadávaného v příkazovém režimu, tj. s dvojtečkou na začátku:

:call CreateForLoop()<CR>a

ale to je samozřejmě příliš komplikované, a to i při použití tabulátoru pro doplnění celého jména funkce (dokonce bych řekl, že je to komplikovanější než samotný přímý zápis počítané smyčky).

Obrázek 7: Funkci CreateForLoop() lze použít i v případě, že jsou meze smyčky zadány pomocí jmen proměnných nebo konstant.

Z tohoto důvodu je většinou vhodnější využít mapování, například na kombinaci znaků ,f zapsaných ve vkládacím režimu. Zde se využívá především toho faktu, že za čárkou buď následuje ve zdrojových kódech číslice nebo mezera, nikoli písmeno (přesněji řečeno písmeno se může po čárce vyskytovat, programátoři však za tímto znakem většinou zapisují mezeru, neboť se tím zvyšuje čitelnost):

:imap ,f <Esc>:call CreateForLoop()<CR>a

Obrázek 8: Výsledek zavolání funkce CreateForLoop() pro dvojici konstant.

Mnoho uživatelů preferuje doplňování kódu klávesou Tab přímo ve vkládacím režimu, což je taktéž relativně snadné zařídit. Následující mapování zaručí, že po zápisu znaku f ihned za koncovou hodnotou smyčky a po stlačení klávesy Tab dojde k automatickému vložení snippetu:

:imap f<Tab> <Esc>:call CreateForLoop()<CR>a

Obrázek 9: Zobrazení menu s možností zavolání funkce CreateForLoop().

Pokud namísto vytváření nových mapování (které je nutné si zapamatovat nebo někam zapsat) preferujete volání funkcí z menu, je řešení poměrně snadné:

amenu 300.110 &Insert.&For\ loop<TAB>,f :call CreateForLoop()

S přesným významem předchozího příkazu se seznámíme příště.

Obrázek 10: Výsledek zavolání funkce.

6. Využití uživatelských funkcí v praxi – properties v C#

Funkce CreateForLoop uvedená v předchozích kapitolách byla poměrně jednoduchá. Zkusme si tedy vytvořit funkci nepatrně složitější a možná taktéž užitečnější. Programátoři vyvíjející aplikace v programovacích jazycích C#, C++ či Java velmi často přidávají do svých tříd atributy, k nimž vytváří takzvané settery a gettery, tj. metody určené pro nastavování resp. čtení hodnoty těchto atributů. Na této stránce je kromě dalších zajímavých informací uvedena i funkce, která umožňuje vytvoření setterů a getterů v programovacím jazyce C#, v němž je možné použít zkrácenou syntaxi zápisu. Originální – poněkud nečitelný – tvar funkce CreateProperty i s příslušným mapováním na klávesové zkratky (navržené autorem této funkce) je následující:

function! CreateProperty(type)
  execute "normal bim_\<Esc>b\"yywiprivate ".a:type." \<Esc>A;\<CR>public ".a:type.
        \ " \<Esc>\"ypb2xea\<CR>{\<Esc>oget\<CR>{\<CR>return " .
        \ "\<Esc>\"ypa;\<CR>}\<CR>set\<CR>{\<CR>\<Tab>\<Esc>\"yPa = value;\<CR>}\<CR>}\<CR>\<Esc>"
  normal! 12k2wi
endfunction

Obrázek 11: Zdrojový kód původní varianty funkce CreateProperty() určené pro programovací jazyk C#.

imap <C-c><C-p><C-s> <Esc>:call CreateProperty("string")<CR>a
imap <C-c><C-p><C-i> <Esc>:call CreateProperty("int")<CR>a

Obrázek 12: Ukázka kódu vytvořeného původní variantou funkce CreateProperty().

7. Přepis funkce CreateProperty() pro programovací jazyk Java

Zkusme si nyní funkci CreateProperty() z předchozí kapitoly v rámci procvičování Vim Scriptu upravit takovým způsobem, aby byla vhodná pro použití v programovacím jazyce Java, v němž se (alespoň prozatím!) žádná zkrácená syntaxe pro zápis setterů a getterů nepoužívá. Funkce při svém zavolání očekává, že jí bude předán textový parametr s názvem datového typu vytvářeného atributu (int, String, Color, Customer, FooBarClass atd.) a navíc se před kurzorem bude nacházet název atributu, ideálně začínající malým písmenem (to není nutnou podmínkou, jen to odpovídá štábní kultuře dodržované programátory v Javě). Funkce pracuje takovým způsobem, že nejprve jméno atributu uloží do pracovního registru x pomocí operátoru yw (yank word). Posléze změní první znak atributu na velké písmeno operátorem gU a atribut vymaže (!) do pracovního registru y. Zbylý kód již pouze správně vypíše typ atributu, jeho název i settery a gettery.

Obrázek 13: Funkce CreateProperty() upravená tak, aby byla použitelná v programovacím jazyku Java.

Následuje zdrojový kód „javovské“ varianty funkce CreateProperty(), která již obsahuje mnoho vysvětlujících komentářů:

" funkce, ktera pro slovo, na nemz se nachazi kurzor
" vytvori property daneho typu
function! CreateProperty(type)
    " ulozit slovo nachazejici se pod kurzorem
    " do registru 'x'
    execute "normal b\"xyw"

    " zmena prvniho znaku slova na velke pismeno
    " a ulozeni zmeneneho nazvu do registru 'y'
    " (slovo - nazev atributu - je vymazano!)
    execute "normal gUl\"ydw"

    " prvni radek - typ a jmeno property jako
    " privatniho atributu objektu
    execute "normal iprivate " . a:type . " \<Esc>\"xpa;\<CR>\<CR>"

    " vytvoreni getteru
    execute "normal ipublic " . a:type . " get\<Esc>\"ypa() {\<CR>"
    execute "normal i    return this.\<Esc>\"xpa;\<CR>"
    execute "normal i}\<CR>\<CR>"

    " vytvoreni setteru
    execute "normal ipublic void set\<Esc>\"ypa(" . a:type . " value) {\<CR>"
    execute "normal i    this.\<Esc>\"xpa=value;\<CR>"
    execute "normal i}\<CR>"
endfunction

Obrázek 14: Ukázka programového kódu vytvořeného upravenou variantou funkce CreateProperty()

8. Vylepšení funkce pro automatické vytváření getterů a setterů

Funkci CreateProperty je opět možné volat přímo z příkazového režimu:

:call CreateProperty("String")

Ovšem mnohem lepší je použít vhodná mapování, například:

" cps = create property string
map ,cps :call CreateProperty("String")<CR>
" cpi = create property integer
map ,cpi :call CreateProperty("int")<CR>
" cpf = create property float
map ,cpf :call CreateProperty("float")<CR>
" cpd = create property double
map ,cpd :call CreateProperty("double")<CR>

Rychlejší však bude vytvoření pomocné funkce, která při svém zavolání (většinou opět s využitím mapování) očekává na editovaném řádku dvojici slov. Prvním slovem bude typ atributu, druhým slovem jeho název. Nově vytvořená funkce se bude jmenovat CreatePropertyWithType a bude se volat například přes následující mapování:

imap ,cp <Esc>:call CreatePropertyWithType()<CR>a

Obrázek 15: Funkce CreateProperty() upravená tak, aby byla použitelná v programovacím jazyku Java. Tato funkce může být volaná z další funkce CreatePropertyWithType(), která na editovaném programovém řádku očekává jak typ atributu, tak i jeho název.

widgety

Možná bude zajímavé se podívat na způsob, jakým je vyřešena „přeměna“ prvního zapsaného slova na parametr funkce CreateProperty. Ve skutečnosti to jde docela jednoduše, protože se toto slovo smaže pomocí příkazu bde (go to Begin of word, Delete to End), což mj. znamená, že se toto slovo uloží do pracovního registru . Hodnota tohoto registru je přístupná přes pseudoproměnnou @“, jejíž hodnota je uložena (jen pro přehlednost) do proměnné propertyType a následně je tato proměnná předána jako parametr funkci CreateProperty:

function! CreatePropertyWithType()
    " prechod kurzoru na zacatek prvniho slova
    " a smazani celeho slova s ulozenim do registru "
    execute "normal bbde"

    " ziskani hodnoty registru " a nasledne ulozeni
    " do promenne propertyType
    let propertyType=@"

    " smazani mezery a presun na konec druheho slova
    execute "normal xe"

    " zavolani puvodni funkce CreateProperty
    call CreateProperty(propertyType)
endfunction

" funkce, ktera pro slovo, na nemz se nachazi kurzor
" vytvori property daneho typu
function! CreateProperty(type)
    " ulozit slovo nachazejici se pod kurzorem
    " do registru 'x'
    execute "normal b\"xyw"

    " zmena prvniho znaku slova na velke pismeno
    " a ulozeni zmeneneho nazvu do registru 'y'
    execute "normal gUl\"ydw"

    " prvni radek - typ a jmeno property jako
    " privatniho atributu objektu
    execute "normal iprivate " . a:type . " \<Esc>\"xpa;\<CR>\<CR>"

    " vytvoreni getteru
    execute "normal ipublic " . a:type . " get\<Esc>\"ypa() {\<CR>"
    execute "normal i    return this.\<Esc>\"xpa;\<CR>"
    execute "normal i}\<CR>\<CR>"

    " vytvoreni setteru
    execute "normal ipublic void set\<Esc>\"ypa(" . a:type . " value) {\<CR>"
    execute "normal i    this.\<Esc>\"xpa=value;\<CR>"
    execute "normal i}\<CR>"
endfunction

Obrázek 16: Ukázka programového kódu vytvořeného upravenou variantou funkce CreateProperty() volané z funkce CreatePropertyWithType(). Nejprve byla ručně zapsána kostra třídy, posléze poloautomaticky vytvořeny atributy i s příslušnými settery a gettery a na závěr byl zdrojový kód zformátován pomocí příkazu =% (kurzor se musel nacházet na jedné ze složených závorek omezující třídu Customer). Čas vytvoření tohoto souboru: 15 sekund.

9. Odkazy na Internetu

  1. snipMate : TextMate-style snippets for Vim
    http://www.vim.org/scripts/scrip­t.php?script_id=2540
  2. msanders / snipmate.vim
    https://github.com/msander­s/snipmate.vim
  3. snipMate.vim Introductory Screencast
    http://vimeo.com/3535418
  4. Clewn home page
    http://clewn.sourceforge.net/
  5. How to connect vim with gdb – using clewn
    http://chunhao.net/blog/how-to-connect-vim-with-gdb-using-clewn
  6. yavdb : Yet Another (Generic) Vim Debugger Integration
    http://www.vim.org/scripts/scrip­t.php?script_id=1954
  7. Vim home page
    http://www.vim.org/
  8. Exuberant ctags
    http://ctags.sourceforge.net/
  9. xxd (man page)
    http://www.linux-tutorial.info/modules.php?na­me=ManPage&sec=1&manpage=xxd
  10. vim (man page)
    http://www.linux-tutorial.info/modules.php?na­me=ManPage&sec=1&manpage=vim
  11. ctags (man page)
    http://www.linux-tutorial.info/modules.php?na­me=ManPage&sec=1&manpage=ctags
  12. cscope (man page)
    http://www.linux-tutorial.info/modules.php?na­me=ManPage&sec=1&manpage=csco­pe
  13. Tutorial: Make Vim as Your C/C++ IDE Using c.vim Plugin
    http://www.thegeekstuff.com/2009/01/tu­torial-make-vim-as-your-cc-ide-using-cvim-plugin/
  14. c.vim : C/C++ IDE
    http://vim.sourceforge.net/scrip­ts/script.php?script_id=213
  15. c.vim : C/C++ IDE key mappings
    http://lug.fh-swf.de/vim/vim-c/c-hotkeys.pdf
  16. Základní základy editoru Vim
    http://www.root.cz/clanky/zakladni-zaklady-editoru-vim/
  17. Jak si přizpůsobit Vim
    http://www.root.cz/serialy/jak-si-prizpusobit-vim/
  18. Novinky ve VIM 7: Úvodní část – speller
    http://www.root.cz/vim-sedm-prvni-cast/
  19. Novinky ve VIM 7: Skriptovací jazyk
    http://www.root.cz/vim-sedm-druha-cast/
  20. vim2elvis: Přednosti a nedostaky Elvise v porovnání s Vimem
    http://www.root.cz/clanky/vim2elvis-1/
  21. vim2elvis: Shodné znaky mezi Elvisem a Vimem, nastaveníeditoru
    http://www.root.cz/clanky/vim2elvis-2/
  22. Nej… VIM pluginy (1)
    http://www.root.cz/clanky/nej-vim-pluginy/
  23. Taglist (plugin)
    http://www.vim.org/scripts/scrip­t.php?script_id=273
  24. The NERD tree: A tree explorer plugin for navigating the filesystem
    http://www.vim.org/scripts/scrip­t.php?script_id=1658
  25. JavaBrowser : Shows java file class, package in a tree as in IDEs. Java source browser.
    http://www.vim.org/scripts/scrip­t.php?script_id=588
  26. snippetsEmu : An attempt to emulate TextMate's snippet expansion
    http://www.vim.org/scripts/scrip­t.php?script_id=1318
  27. Scroll Lock (Necyklopedie)
    http://necyklopedie.wikia­.com/wiki/Scroll_lock
  28. Caps Lock (Necyklopedie)
    http://necyklopedie.wikia­.com/wiki/Caps_Lock
  29. Avoid the escape key
    http://vim.wikia.com/wiki/A­void_the_escape_key
  30. Map caps lock to escape in XWindows
    http://vim.wikia.com/wiki/VimTip166
Našli jste v článku chybu?
Root.cz: Hořící telefon Samsung Note 7 zapálil auto

Hořící telefon Samsung Note 7 zapálil auto

Vitalia.cz: Tesco nabízí desítky tun jídla zdarma

Tesco nabízí desítky tun jídla zdarma

Lupa.cz: Proč jsou firemní počítače pomalé?

Proč jsou firemní počítače pomalé?

120na80.cz: Pálení žáhy: která jídla ne a co nás uzdraví?

Pálení žáhy: která jídla ne a co nás uzdraví?

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

DigiZone.cz: Ginx TV: pořad o počítačových hráčích

Ginx TV: pořad o počítačových hráčích

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Podnikatel.cz: Byla finanční manažerka, teď cvičí jógu

Byla finanční manažerka, teď cvičí jógu

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

DigiZone.cz: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Podnikatel.cz: Tyto pojmy k #EET byste měli znát

Tyto pojmy k #EET byste měli znát