Интегриране с външни програми

4 май 2023

Административни неща

Преговор

Преговор

Преговор

Преговор

Преговор

Преговор

Input

1 2 3 4 5 6 7 8
let answer = input("How do you feel today? ")
redraw

if answer =~? 'good\|okay\|alright'
  echomsg "Cool."
else
  echomsg "Yeah whatever"
endif

Input: допълнителни параметри

1 2 3
let new_name = input("Rename file: ", expand('%'), 'file')
redraw
echomsg $"Preview: call rename('{expand('%')}', '{new_name}')"

Input: варианти

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let answer = inputlist([
      \   "Коя от тези функции работи върху буфера?",
      \   "1) matchstr()",
      \   "2) searchpairpos()",
      \   "3) substitute()",
      \ ])
redraw

if answer == 2
  call sound_playevent('dialog-information')
  echohl String | echomsg "✅" | echohl None
else
  call sound_playevent('dialog-warning')
  echohl ErrorMsg | echomsg "❌" | echohl None
endif

Input: пароли

1
let cc = inputsecret("Enter your credit card, don't worry about it")

Странично: Vim може да криптира файлове:

Confirm: Y/N?

1 2 3
if confirm("Download spell file?", "&Yes\n&No", 2) == 1
  echomsg "No."
endif

Redir

Малко хакаво, ама не е за сефте:

1 2 3 4 5 6 7
redir => output
  silent digraphs
redir END

let umlaut_digraph = matchstr(output, '..\ze ü')
redraw
echomsg "Digraph for ü is: '" .. umlaut_digraph .. "'"

Redir

Малко хакаво, ама не е за сефте:

1 2 3 4 5 6 7
redir => output
  silent digraphs
redir END

let umlaut_digraph = matchstr(output, '..\ze ü')
redraw
echomsg "Digraph for ü is: '" .. umlaut_digraph .. "'"

Redir

Малко хакаво, ама не е за сефте:

1 2 3 4 5 6 7
redir => output
  silent digraphs
redir END

let umlaut_digraph = matchstr(output, '..\ze ü')
redraw
echomsg "Digraph for ü is: '" .. umlaut_digraph .. "'"

Redir

Има варианти за файл и за регистри

Вместо redir: execute()

Днешно време има по-удобен вариант, :help execute():

1 2 3 4 5
let output = execute('digraphs', 'silent')

let umlaut_digraph = matchstr(output, '..\ze ü')
redraw
echomsg "Digraph for ü is: '" .. umlaut_digraph .. "'"

Вместо redir: execute()

Днешно време има по-удобен вариант, :help execute():

1 2 3 4 5
let output = execute('digraphs', 'silent')

let umlaut_digraph = matchstr(output, '..\ze ü')
redraw
echomsg "Digraph for ü is: '" .. umlaut_digraph .. "'"

Вместо redir: execute()

Днешно време има по-удобен вариант, :help execute():

1 2 3 4 5
let output = execute('digraphs', 'silent')

let umlaut_digraph = matchstr(output, '..\ze ü')
redraw
echomsg "Digraph for ü is: '" .. umlaut_digraph .. "'"

Lambda функции

:help lambda

Можем да си дефинираме "анонимна функция" или "ламбда". Тези двете са горе-долу еквивалентни:

1 2 3 4 5 6 7 8 9 10
function Add1(x, y)
  return a:x + a:y
endfunction

let Add2 = { x, y -> x + y }

echomsg Add1(3, 4)
echomsg Add2(3, 4)
" 7
" 7

Ламбда функция с един аргумент е { input -> ... }, с нула е { -> ... }.

Lambda функции

Ламбдите могат да имат само един expression. Не можем да дефинираме променливи или да извикваме команди, макар че в случай на голяма нужда, винаги има execute().

Но удобството им е предимно за функции като map(), filter(), reduce().

Map, filter, reduce

1 2 3 4 5 6 7
let numbers = [1, 2, 3, 4, 5]
let doubled_numbers = map(copy(numbers), { i, n -> n * 2 })
" => [2, 4, 6, 8, 10]
let odd_numbers = filter(copy(numbers), { i, n -> n % 2 == 1 })
" => [1, 3, 5]
let numbers_sum = reduce(copy(numbers), { sum, n -> sum + n }, 0)
" => 15

Няколко особености/досадности:

Map, filter, reduce

1 2 3 4 5 6 7
let numbers = [1, 2, 3, 4, 5]
let doubled_numbers = map(copy(numbers), { i, n -> n * 2 })
" => [2, 4, 6, 8, 10]
let odd_numbers = filter(copy(numbers), { i, n -> n % 2 == 1 })
" => [1, 3, 5]
let numbers_sum = reduce(copy(numbers), { sum, n -> sum + n }, 0)
" => 15

Няколко особености/досадности:

Map, filter, reduce

1 2 3 4 5 6 7
let numbers = [1, 2, 3, 4, 5]
let doubled_numbers = map(copy(numbers), { i, n -> n * 2 })
" => [2, 4, 6, 8, 10]
let odd_numbers = filter(copy(numbers), { i, n -> n % 2 == 1 })
" => [1, 3, 5]
let numbers_sum = reduce(copy(numbers), { sum, n -> sum + n }, 0)
" => 15

Няколко особености/досадности:

Map, filter, reduce

1 2 3 4 5 6 7
let numbers = [1, 2, 3, 4, 5]
let doubled_numbers = map(copy(numbers), { i, n -> n * 2 })
" => [2, 4, 6, 8, 10]
let odd_numbers = filter(copy(numbers), { i, n -> n % 2 == 1 })
" => [1, 3, 5]
let numbers_sum = reduce(copy(numbers), { sum, n -> sum + n }, 0)
" => 15

Няколко особености/досадности:

Map, filter, reduce

1 2 3 4 5 6 7
let numbers = [1, 2, 3, 4, 5]
let doubled_numbers = map(copy(numbers), { i, n -> n * 2 })
" => [2, 4, 6, 8, 10]
let odd_numbers = filter(copy(numbers), { i, n -> n % 2 == 1 })
" => [1, 3, 5]
let numbers_sum = reduce(copy(numbers), { sum, n -> sum + n }, 0)
" => 15

Няколко особености/досадности:

Map, filter, reduce

Map и filter съществуваха и преди да има ламбди -- eval all the way:

1 2 3 4 5
let numbers = [1, 2, 3, 4, 5]
let doubled_numbers = map(copy(numbers), 'v:val * 2')
" => [2, 4, 6, 8, 10]
let odd_numbers = filter(copy(numbers), 'v:val % 2 == 1')
" => [1, 3, 5]

Индекса на елемента е достъпен като v:key

Стартиране на външни програми

Стартиране на външни програми

Стартиране на външни програми

Стартиране на външни програми

Стартиране на външни програми без изчакване

Стартиране на външни програми без изчакване

Стартиране на външни програми без изчакване

Стартиране на външни програми без изчакване

System

:help system()

1 2 3 4 5 6
let files = system('cd ~/.vim/indent && ls')

echomsg string(files)
" => 'lua.vim^@php.vim^@yaml.vim^@'
echomsg string(split(files, "\n"))
" => ['lua.vim', 'php.vim', 'yaml.vim']

Systemlist

:help systemlist() -- директно го разбива на празни редове

1 2 3 4
let files = systemlist('cd ~/.vim/indent && ls')

echomsg string(files)
" => ['lua.vim', 'php.vim', 'yaml.vim']

Systemlist

Впрочем, Vim има cd, което може да смени текущата директория, в която се изпълняват команди.

1 2 3 4 5 6
cd ~/.vim/indent
let files = systemlist('ls')
cd -

echomsg string(files)
" => ['lua.vim', 'php.vim', 'yaml.vim']

Systemlist

Впрочем, Vim има cd, което може да смени текущата директория, в която се изпълняват команди.

1 2 3 4 5 6
cd ~/.vim/indent
let files = systemlist('ls')
cd -

echomsg string(files)
" => ['lua.vim', 'php.vim', 'yaml.vim']

Също има:

System -- с вход

:call system(<command>, <input>)

1 2 3 4 5 6 7 8 9
let vars = getbufline('%', 1, 4)
      \ ->map({ _, l -> matchstr(l, 'let \zs\k\+\ze =') })
let sorted_vars = systemlist('sort', join(vars, "\n"))
let dummy_var = 42

echomsg string(vars)
" ['vars', '', 'sorted_vars', 'dummy_var']
echomsg string(sorted_vars)
" ['', 'dummy_var', 'sorted_vars', 'vars']

Id3.vim demo

Id3.vim demo

Id3.vim demo

Id3.vim demo

Справяне с грешки

1 2 3 4 5
let command_output = system('...')
if v:shell_error
  echoerr "There was an error: ".command_output
  return
endif

Id3.vim demo

Справяне с грешки

1 2 3 4 5
let command_output = system('...')
if v:shell_error
  echoerr "There was an error: ".command_output
  return
endif

Id3.vim demo

Справяне с грешки

1 2 3 4 5
let command_output = system('...')
if v:shell_error
  echoerr "There was an error: ".command_output
  return
endif

BufRead/WriteCmd

This club has everything:

BufRead/WriteCmd

This club has everything:

BufRead/WriteCmd

This club has everything:

BufRead/WriteCmd

This club has everything:

Четене и писане на файлове

Четене и писане на файлове

1 2 3 4
TOhtml
exe 'saveas ' .. tempname() .. '.html'
silent !firefox %
redraw! | quit

Jobs & Channels

За дългосрочни процеси

1 2 3 4 5
let job = job_start(command, {
      \ 'out_cb':  { job, line -> HandleOutput(line) },
      \ 'err_cb':  { job, line -> HandleError(line) },
      \ 'exit_cb': { job, status -> HandleExit(status) },
      \ })

Jobs

Не е нужно да са ламбда функции, разбира се:

1 2 3 4 5 6 7
function! HandleOutput(job, line)
  echomsg "Got line: " .. a:line
endfunction

let job = job_start('ls', { 'out_cb': function('HandleOutput') })
echomsg "Info: " .. string(job_info(job))
" => Status: {'status': 'run', 'cmd': ['ls'], 'termsig': '', 'stoponexit': 'term', 'tty_out': '', 'exitval': 0, 'exit_cb': '', 'tty_in': '', 'channel': 'channel 11 open', 'process': 24095}

Jobs

Jobs

Jobs

Jobs

Jobs

Channels

Job-а си стартира с някакъв "канал", по който тече комуникацията, който е в "nl" режим. Т.е. всяко съобщение се завършва с \n символ.

Channels

Job-а си стартира с някакъв "канал", по който тече комуникацията, който е в "nl" режим. Т.е. всяко съобщение се завършва с \n символ.

Може да го вземем с job_getchannel, но може и да викаме примерно ch_sendraw(job) директно върху job-а, Vim ще се оправи.

Channels

Job-а си стартира с някакъв "канал", по който тече комуникацията, който е в "nl" режим. Т.е. всяко съобщение се завършва с \n символ.

Може да го вземем с job_getchannel, но може и да викаме примерно ch_sendraw(job) директно върху job-а, Vim ще се оправи.

Засега няма да задълбавам в канали, защото са малко странни и можем и без директно ръчкане в тях.

Gnugo

Clientserver

Clientserver

Clientserver

Clientserver

Clientserver

Clientserver

Vimrunner

https://github.com/AndrewRadev/vimrunner

Vimrunner

https://github.com/AndrewRadev/vimrunner

Също интересно, но тепърва ще говорим повече за quickfix прозореца: https://andrewra.dev/2021/02/01/sending-build-output-to-vim/

JSON

Функцията json_encode превръща Vim-ски обект в JSON, json_decode прави обратното. Примерно:

1
echo json_encode({ 'key': [1, 2, 3], 'flag': v:true, 'nothing': v:null })
1
{"nothing":null,"key":[1,2,3],"flag":true}

JSON

Функцията json_encode превръща Vim-ски обект в JSON, json_decode прави обратното. Примерно:

1
echo json_encode({ 'key': [1, 2, 3], 'flag': v:true, 'nothing': v:null })
1
{"nothing":null,"key":[1,2,3],"flag":true}

Забележете, Vim няма "null", затова има специалната променлива v:null точно за тая цел. Същото за v:true и v:false.

JS ???

Функцията js_encode превръща Vim-ски обект в едно такова като JSON ама не баш, js_decode прави обратното. Примерно:

1
echo js_encode({ 'key': [1, v:none, 3], 'flag': v:true, 'nothing1': v:null, 'nothing2': v:none })
1
{nothing1:null,nothing2:null,key:[1,,3],flag:true}

JS ???

Функцията js_encode превръща Vim-ски обект в едно такова като JSON ама не баш, js_decode прави обратното. Примерно:

1
echo js_encode({ 'key': [1, v:none, 3], 'flag': v:true, 'nothing1': v:null, 'nothing2': v:none })
1
{nothing1:null,nothing2:null,key:[1,,3],flag:true}

Ключовете не са в кавички, има v:none, което е еквивалент на undefined???

JS ???

Функцията js_encode превръща Vim-ски обект в едно такова като JSON ама не баш, js_decode прави обратното. Примерно:

1
echo js_encode({ 'key': [1, v:none, 3], 'flag': v:true, 'nothing1': v:null, 'nothing2': v:none })
1
{nothing1:null,nothing2:null,key:[1,,3],flag:true}

Ключовете не са в кавички, има v:none, което е еквивалент на undefined???
Може би просто не го използвайте.

Id3, отново

И как интерактва със https://github.com/AndrewRadev/id3-json.

Terminal

Terminal

Terminal

1 2 3 4 5
function! HandleOutput(job, line)
  echomsg "Got line: " .. a:line
endfunction

let job = term_start('irb', { 'out_cb': function('HandleOutput') })

Prompt buffer

Ужасно интересна идея: :help prompt-buffer

Prompt buffer

Ужасно интересна идея: :help prompt-buffer

Prompt buffer

Ужасно интересна идея: :help prompt-buffer

Prompt buffer

Demo: за systemd интеракция

Side note: https://github.com/vim-scripts/AnsiEsc.vim

Идеи за проект

Идеи за проект

Идеи за проект

Други езици

Ruby, Python, Perl, Lua, могат да се използват директно във Vim… ако е компилиран за целта

1 2 3 4 5 6 7 8 9
python3 print("Nobody expects the Spanish Inquisition!")
py3file some_python_script.py

python3 << EOF
# The Zen of python:
import this

# More code here...
EOF

Други езици

Интеграцията в python става със специалния модул "vim", и е сравнително проста. :help python-vim. Проверявате дали има със has('python3')

Други езици

Интеграцията в python става със специалния модул "vim", и е сравнително проста. :help python-vim. Проверявате дали има със has('python3')

Като цяло най-смисления подход е да пишем максимума логика в python, и после да върнем някакъв резултат като низ и да използваме "интеграцията" за базови неща като четене на променливи или вземане на съдържанието на буфера.

Други езици

Интеграцията в python става със специалния модул "vim", и е сравнително проста. :help python-vim. Проверявате дали има със has('python3')

Като цяло най-смисления подход е да пишем максимума логика в python, и после да върнем някакъв резултат като низ и да използваме "интеграцията" за базови неща като четене на променливи или вземане на съдържанието на буфера.

Същото за всички останали. Guide-овете са if_ruby.txt, if_python.txt etc, така че ако напишете :help if_ и натиснете <tab>, ще получите пълен списък.

Други езици

Рядко се използват, защото изискват компилация с този език (и то коя версия?). Най-често срещана е python3 интеграция, понякога заради употребата на thread-ове.

Два python плъгина, които ползвам: ultisnips, autotag.

Други езици

Рядко се използват, защото изискват компилация с този език (и то коя версия?). Най-често срещана е python3 интеграция, понякога заради употребата на thread-ове.

Два python плъгина, които ползвам: ultisnips, autotag.

Под neovim, вградените интерпретатори са махнати и python3 работи като намира външно exe и изпълнява кода през него, комуникирайки с MsgPack протокол, стига обаче да има инсталиран "neovim" пакет. :help provider-python, :help remote-plugin.

Това не е лоша идея, но може да е доста по-бавно, в зависимост от това какво правите.

Други езици

Рядко се използват, защото изискват компилация с този език (и то коя версия?). Най-често срещана е python3 интеграция, понякога заради употребата на thread-ове.

Два python плъгина, които ползвам: ultisnips, autotag.

Под neovim, вградените интерпретатори са махнати и python3 работи като намира външно exe и изпълнява кода през него, комуникирайки с MsgPack протокол, стига обаче да има инсталиран "neovim" пакет. :help provider-python, :help remote-plugin.

Това не е лоша идея, но може да е доста по-бавно, в зависимост от това какво правите.

Препоръчвам да си ползвате външни скриптове, които комуникират с текст или JSON.

Други езици

Side note: :help libcall(

Бих казал, за такива неща: https://github.com/mattn/vim-particle. Но всъщност този плъгин просто си вика exe със job_start.

Компилация на Vim

Ужасно лесна под unix, възможна под windows

Въпроси