Работа с проекти 2, организация на плъгини

30 май 2023

Преговор

Преговор

Преговор

Преговор

Скелет на един плъгин

Скелет на един плъгин

Скелет на един плъгин

Скелет на един плъгин

Скелет на един плъгин

Организация на данните

Ако имаме една купчина b: променливи, защо да не ги съберем в един обект?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
function! fluffy#finder#New()
  let job = job_start('rg --files', ...)

  return #{
        \   statusline:    '',
        \   all_paths:     [],
        \   start_time:    reltime(),
        \   last_update:   reltime(),
        \   duration:      0,
        \   loading:       ['⢿', '⣻', '⣽', '⣾', '⣷', '⣯', '⣟', '⡿'],
        \   loading_index: 0,
        \   job:           job,
        \ }
endfunction

let b:finder = fluffy#finder#New()

Организация на данните: (нещо като) ООП

:help func-dict

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
function! fluffy#finder#New()
  return #{
        \   ...
        \   Update: function('s:Update'),
        \   Stop:   function('s:Stop'),
        \ }
endfunction

autocmd TextChangedI <buffer> call b:finder.Update()
autocmd QuitPre      <buffer> call b:finder.Stop()

function s:Update() dict
  " ...
endfunction

function s:Stop() dict
  " ...
endfunction

Организация на данните: (нещо като) ООП

Няма наследяване или класове, но пък има начин да съберете данни и поведение, и това може да е напълно достатъчно.

Организация на данните: (нещо като) ООП

Няма наследяване или класове, но пък има начин да съберете данни и поведение, и това може да е напълно достатъчно.

При достатъчно желание, може и да се постигне някаква форма на наследяване, примерно парсърите в splitjoin.

Организация на данните: (нещо като) ООП

Няма наследяване или класове, но пък има начин да съберете данни и поведение, и това може да е напълно достатъчно.

При достатъчно желание, може и да се постигне някаква форма на наследяване, примерно парсърите в splitjoin.

Внимавайте с функциите -- b:finder.Update() ще извика функцията със b:finder прикачено като self. Но ако я подадете като аргумент или прикачите някъде, ще гръмне:

1 2 3 4 5 6 7
" Ще хвърли грешка:
let Update = b:finder.Update
call Update()

" Ще бъде ок 👍
let Update = { -> b:finder.Update() }
call Update()

Грешки във функция

Странно качество на Vimscript: ако някой ред хвърли грешка, скрипта смело ще си продължи напред.

1 2 3 4 5 6 7 8 9
function! Test()
  if nonexistent_var
    echomsg "OK1"
  endif

  echomsg "OK2"
endfunction

call Test()
1 2 3 4
Error detected while processing /tmp/vHfVvdg/9[9]..function Test:
line    1:
E121: Undefined variable: nonexistent_var
OK2

Грешки във функция

Това е изключително безсмислено за general-purpose език, но има смисъл за конфигурационен език, което Vimscript частично е. Ако имате в конфигурацията си една грешка, не искате редактора да откаже да boot-не… Защото няма с какво да оправите грешката.

Грешки във функция

Това е изключително безсмислено за general-purpose език, но има смисъл за конфигурационен език, което Vimscript частично е. Ако имате в конфигурацията си една грешка, не искате редактора да откаже да boot-не… Защото няма с какво да оправите грешката.

В контекста на функция това няма много смисъл. Затова има атрибута abort (:help func-abort):

1 2 3 4 5 6 7 8 9
function! Test() abort
  if nonexistent_var
    echomsg "OK1"
  endif

  echomsg "OK2"
endfunction

call Test()
1 2 3
Error detected while processing /tmp/vHfVvdg/10[9]..function Test:
line    1:
E121: Undefined variable: nonexistent_var

Грешки във функция

Докато сме на тази тема -- забележете малко странния backtrace -- "line 1"? Това е поредния ред във функцията 🤷.

Nest-ването също е малко странно:

1 2 3 4 5 6 7 8 9 10
function! Test1() abort
  echomsg "OK0"
  if nonexistent_var | echomsg "OK1" | endif
  echomsg "OK2"
endfunction

function! Test2() abort
  echomsg "OK3"
  call Test1()
endfunction
1 2 3 4 5
OK3
OK0
Error detected while processing function Test2[2]..Test1:
line    2:
E121: Undefined variable: nonexistent_var

Грешки във функция

Един плъгин, който се опитва да направи този backtrace по-лесен: exception.vim. Но е малко бъгав.

Документация

Няма един начин, но има някои полезни компоненти:

Документация

Няма един начин, но има някои полезни компоненти:

Документация

Няма един начин, но има някои полезни компоненти:

Документация

Няма един начин, но има някои полезни компоненти:

Документация

Няма един начин, но има някои полезни компоненти:

Документация

Документация

Документация

Документация

Да се върнем на навигацията за проекти

Да се върнем на навигацията за проекти

Да се върнем на навигацията за проекти

Как да намерим factory?

Може да направим същото, но в един файл може да има няколко factory-та:

1 2 3 4 5 6 7 8 9 10
FactoryBot.define do
  factory :vimrc do
    user
  end

  factory :vimrc_revision do
    vimrc
    body { "set number\nset expandtab" }
  end
end

Как да намерим factory?

  1. Намираме файловете
  2. Минаваме през редовете на всеки файл
  3. Намираме ^\s*factory :\k\+ или нещо подобно

Тъпо, но тотално работи

Quickfix

Най-простия начин:

1 2 3 4 5 6
command! ListFactories call s:ListFactories()

function! s:ListFactories()
  vimgrep /^\s*factory :\zs\k\+/j spec/**/*.rb
  copen
endfunction

Quickfix: Базови неща

Quickfix: Базови неща

Quickfix: Базови неща

Quickfix: Базови неща

Quickfix

Може да изпълним "компилатор" с make или даже make some args, компилатора може да е локален за файла или глобален за проекта. Но това ни зарежда грешките в quickfix прозореца.

Quickfix

Може да изпълним "компилатор" с make или даже make some args, компилатора може да е локален за файла или глобален за проекта. Но това ни зарежда грешките в quickfix прозореца.

Същото прави и vimgrep/grep. Целта е просто да заредим локации в някакъв интерфейс

Quickfix

Можем и да направим така (:help cexpr)

1
cexpr systemlist('rg "factory :\w+" spec/ --vimgrep')

cexpr използва errorformat, така че можем ако искаме да сложим нещо custom като формат и да го restore-нем след това.

Quickfix

Най-гъвкавия начин: :help setqflist()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
for filename in s:FindFactoryFiles()
  for [line_index, line] in items(readfile(filename))
    let pattern = '^\s*factory :\zs\k\+\ze\s*\%(,\|do\)'

    if line =~ pattern
      let [_match, start_index, _end_index] = matchstrpos(line, pattern)

      call add(factory_entries, #{
            \   filename: filename,
            \   lnum:     line_index + 1,
            \   col:      start_index + 1,
            \   text:     trim(line),
            \ })
    endif
  endfor
endfor

if len(factory_entries) > 0
  call setqflist(factory_entries)
  copen
endif

Quickfix

Setqflist има тонове опции, повечето от които не съм пробвал:

Quickfix

Setqflist има тонове опции, повечето от които не съм пробвал:

Quickfix

Setqflist има тонове опции, повечето от които не съм пробвал:

Quickfix

Setqflist има тонове опции, повечето от които не съм пробвал:

Quickfix

Setqflist има тонове опции, повечето от които не съм пробвал:

Quickfix

:help getqflist()

Същото, ама за четене

1 2
ListFactories
for entry in getqflist() | echomsg string(entry) | endfor
1 2 3 4 5 6
{'lnum': 2, 'bufnr': 11, 'end_lnum': 0, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 12, 'text':
'factory :announcement do'}
{'lnum': 2, 'bufnr': 12, 'end_lnum': 0, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 12, 'text':
'factory :comment do'}
{'lnum': 2, 'bufnr': 13, 'end_lnum': 0, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 12, 'text':
'factory :free_task_solution do'}

И пак има context, title, etc

Quickfix

Защо сложните getqflist()/setqflist() вместо vimgrep/grep/cexpr?

Quickfix

Защо сложните getqflist()/setqflist() вместо vimgrep/grep/cexpr?

Защото са функции, които просто си работят с речници и списъци:

Quickfix

Защо сложните getqflist()/setqflist() вместо vimgrep/grep/cexpr?

Защото са функции, които просто си работят с речници и списъци:

Quickfix

Защо сложните getqflist()/setqflist() вместо vimgrep/grep/cexpr?

Защото са функции, които просто си работят с речници и списъци:

Quickfix

Защо сложните getqflist()/setqflist() вместо vimgrep/grep/cexpr?

Защото са функции, които просто си работят с речници и списъци:

Quickfix

За какво можем да го използваме?

Quickfix

За какво можем да го използваме?

Quickfix

За какво можем да го използваме?

Quickfix

За какво можем да го използваме?

Още навигация

Още навигация

Още навигация

Още навигация

Още навигация

Още навигация

Като цяло, ако имате език с импорти (rust, typescript, java), винаги може да "проследите" импортите. Ако имате език без импорти (ruby, vimscript), вероятно има някаква конвенция, която може да ползвате. Задайте си въпроса "как бих намерил това нещо на ръка" и опитайте да го автоматизирате.

Още навигация

Като цяло, ако имате език с импорти (rust, typescript, java), винаги може да "проследите" импортите. Ако имате език без импорти (ruby, vimscript), вероятно има някаква конвенция, която може да ползвате. Задайте си въпроса "как бих намерил това нещо на ръка" и опитайте да го автоматизирате.

Въпроси