Работа с проекти 1

25 май 2023

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

Преговор

Преговор

Преговор

Преговор

Преговор

Как работим с проекти?

Няма един начин, но има тонове инструменти. Ще започна от най-скучните (за мен).

LSP

LSP

LSP

LSP

LSP

LSP

LSP

Vim-lsp setup

Rust-analyzer: https://rust-analyzer.github.io/manual.html#installation

Препоръката от плъгина:

1 2 3 4 5 6 7
if executable('rust-analyzer')
  au User lsp_setup call lsp#register_server({
        \   'name': 'rust-analyzer',
        \   'cmd': { _server_info -> ['rust-analyzer'] },
        \   'allowlist': ['rust'],
        \ })
endif

Vim-lsp setup

Rust-analyzer: https://rust-analyzer.github.io/manual.html#installation

Това, което аз бих направил:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
augroup LspSetup
  autocmd!
  autocmd User lsp_setup call s:LspSetup()
augroup END

function! s:LspSetup()
  if executable('rust-analyzer')
    call lsp#register_server({
          \   'name': 'rust-analyzer',
          \   'cmd': { _server_info -> ['rust-analyzer'] },
          \   'allowlist': ['rust'],
          \ })
  endif
endfunction

Във ftplugin/rust.vim: call lsp#enable()

Vim-lsp

Vim-lsp

Vim-lsp

LSP

Защо ми е скучно? (Не лошо, просто скучно)

LSP

Защо ми е скучно? (Не лошо, просто скучно)

LSP

Защо ми е скучно? (Не лошо, просто скучно)

LSP

Защо ми е скучно? (Не лошо, просто скучно)

LSP

Защо ми е скучно? (Не лошо, просто скучно)

LSP

Защо ми е скучно? (Не лошо, просто скучно)

LSP

Защо ми е скучно? (Не лошо, просто скучно)

Vim-lsp

Моите настройки:

1 2 3 4 5 6 7 8
" Аз бих го активирал с `lsp#enable()` per-filetype
let g:lsp_auto_enable = 0

" За да използваме специализирани канали за performance:
let g:lsp_use_native_client = 1

" Добра идея, но в зависимост от сървъра, може буквално да нарастне до гигабайти:
let g:lsp_log_file = expand('~/.vim-lsp.log')

"Native client" каналите: https://github.com/vim/vim/commit/9247a221ce7800c0ae1b3487112d314b8ab79f53

LSP канали

Или как да си го направим сами:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let job = job_start('rust-analyzer', {
      \ 'in_mode': 'lsp',
      \ 'out_mode': 'lsp',
      \ 'err_mode': 'nl',
      \ 'out_cb': function('s:LspOut'),
      \ 'err_cb': function('s:LspErr'),
      \ })

function! s:LspOut(_job, message)
  echomsg 'Err: ' .. string(a:message)
endfunction

function! s:LspErr(_job, message)
  echomsg 'Out: ' .. string(a:message)
endfunction

LSP канали

LSP канали

LSP канали

LSP канали

Без jobs:

1 2 3
let channel = ch_open({address} [, {options}])
if ch_status(channel) == "open"
  " use the channel

LSP канали

:help channel-address

1 2 3 4
www.example.com:80   " domain + port
127.0.0.1:1234       " IPv4 + port
[2001:db8::1]:8765   " IPv6 + port
unix:/tmp/my-socket  " Unix-domain socket path

Т.е. ако имаме сървър на някой TCP порт, може да се вържем директно с нещо като:

1
let channel = ch_open('127.0.0.1:1234', { 'mode': 'lsp' })

LSP канали

Изпращане на request:

1 2 3 4 5 6 7 8 9
let response = ch_evalexpr(job, {
      \   'method': 'initialize',
      \   'params': {
      \     'processId': getpid(),
      \     'rootUri': root_uri,
      \     'rootPath': root_path,
      \     'capabilities': {...},
      \   },
      \ })

LSP канали

Изпращане на request:

1 2 3 4 5 6 7 8 9
let response = ch_evalexpr(job, {
      \   'method': 'initialize',
      \   'params': {
      \     'processId': getpid(),
      \     'rootUri': root_uri,
      \     'rootPath': root_path,
      \     'capabilities': {...},
      \   },
      \ })

:help ch_evalexpr()

1 2 3 4
ch_evalexpr() waits for a response and returns the decoded
expression.  When there is an error or timeout it returns an
empty |String| or, when using the "lsp" channel mode, returns an
empty |Dict|.

LSP канали

Изпращане на "notification", one-way (kind of):

1 2 3 4 5 6 7 8 9 10 11
call ch_sendexpr(job, {
      \   'method': 'textDocument/didOpen',
      \   'params': {
      \     'textDocument': {
      \       'uri': 'file://' .. expand('%:p'),
      \       'languageId': 'rust',
      \       'version': b:changedtick,
      \       'text': join(getbufline('%', 1), "\n")
      \     },
      \   },
      \ })

LSP канали

Изпращане на "notification", one-way (kind of):

1 2 3 4 5 6 7 8 9 10 11
call ch_sendexpr(job, {
      \   'method': 'textDocument/didOpen',
      \   'params': {
      \     'textDocument': {
      \       'uri': 'file://' .. expand('%:p'),
      \       'languageId': 'rust',
      \       'version': b:changedtick,
      \       'text': join(getbufline('%', 1), "\n")
      \     },
      \   },
      \ })

:help ch_sendexpr()

1 2 3 4 5 6
If the channel mode is "lsp", then returns a Dict. Otherwise
returns an empty String.  If the "callback" item is present in
{options}, then the returned Dict contains the ID of the
request message.  The ID can be used to send a cancellation
request to the LSP server (if needed).  Returns an empty Dict
on error.

LSP канали

Това е всичко… на теория. На практика има ужасяващо много дребни детайли, които са мега досадни за имплементация. API документацията изглежда почти активно engineered да е user-hostile.

LSP: Проекти?

Ако искате да имплементирате нещо с LSP… по-добре недейте. Но ако много настоявате, няколко идеи:

LSP: Проекти?

Ако искате да имплементирате нещо с LSP… по-добре недейте. Но ако много настоявате, няколко идеи:

LSP: Проекти?

Ако искате да имплементирате нещо с LSP… по-добре недейте. Но ако много настоявате, няколко идеи:

LSP: Проекти?

Ако искате да имплементирате нещо с LSP… по-добре недейте. Но ако много настоявате, няколко идеи:

LSP: Проекти?

Ако искате да имплементирате нещо с LSP… по-добре недейте. Но ако много настоявате, няколко идеи:

Fuzzy finding

Плъгини има доста:

Fuzzy finding

Плъгини има доста:

Fuzzy finding

Плъгини има доста:

Fuzzy finding

Плъгини има доста:

Fuzzy finding

Demo

Fuzzy finding

:help matchfuzzy(), :help fuzzy-matching

1 2 3 4 5 6 7 8
echo matchfuzzy(["clay", "crow"], "cay")
" => ['clay']
echo matchfuzzy(v:oldfiles, "test", { 'limit': 3, 'matchseq': 1 })
" => [
"   '~/.vim/bundle/tagalong/test.erb',
"   '~/.vim/bundle/tagalong/test.html',
"   '~/.vim/.git/modules/bundle/tagalong/COMMIT_EDITMSG'
" ]

Fuzzy finding

:help matchfuzzypos()

1 2 3 4 5 6 7 8 9
echo matchfuzzypos(["clay", "crow"], "cay")
" => [['clay'], [[0, 2, 3]], [150]]

for entry in matchfuzzypos(v:oldfiles, "test", { 'limit': 3, 'matchseq': 1 })
  echomsg string(entry)
endfor
" ['~/.vim/bundle/tagalong/test.erb', '~/.vim/bundle/tagalong/test.html', '~/.vim/.git/modules/bundle/tagalong/COMMIT_EDITMSG']
" [[23, 24, 25, 26], [23, 24, 25, 26], [10, 17, 18, 27]]
" [208, 207, 77]

Fuzzy finding: aside

Fuzzy finding: aside

Fuzzy finding

Алгоритмичния проблем е предимно решен. Пак може да ползвате външен инструмент или lua на Neovim за собствен алгоритъм, но ако просто искате нещо, което работи достатъчно добре, вземате matchfuzzypos и измисляте как да го представите.

Fuzzy finding

Алгоритмичния проблем е предимно решен. Пак може да ползвате външен инструмент или lua на Neovim за собствен алгоритъм, но ако просто искате нещо, което работи достатъчно добре, вземате matchfuzzypos и измисляте как да го представите.

Fuzzy finding

Алгоритмичния проблем е предимно решен. Пак може да ползвате външен инструмент или lua на Neovim за собствен алгоритъм, но ако просто искате нещо, което работи достатъчно добре, вземате matchfuzzypos и измисляте как да го представите.

Fuzzy finding

Алгоритмичния проблем е предимно решен. Пак може да ползвате външен инструмент или lua на Neovim за собствен алгоритъм, но ако просто искате нещо, което работи достатъчно добре, вземате matchfuzzypos и измисляте как да го представите.

Fuzzy finding: Проекти?

Fuzzy finding: Проекти?

Структурата на един Rails проект

Demo

Структурата на един Rails проект

Demo

Структурата на един Rails проект

Demo

Структурата на един Rails проект

Demo

Как да намерим модел?

Как да намерим модел?

Ужасно е лесно:

1 2 3 4 5
command! -nargs=1 Emodel call s:Emodel(<q-args>)

function! s:Emodel(model_name)
  exe 'edit app/models/'.a:model_name.'.rb'
endfunction

… но не е много приятно за употреба

Command-line completion

Command-line completion

Как да намерим модел?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
command! -nargs=1 -complete=custom,s:CompleteRailsModels
      \ Emodel call s:Emodel(<q-args>)

function! s:Emodel(model_name)
  exe 'edit app/models/'.a:model_name.'.rb'
endfunction

function! s:CompleteRailsModels(ArgLead, CmdLine, CursorPos)
  let names = []

  for file in split(glob('app/models/**/*.rb'), "\n")
    let name = file
    let name = substitute(name, '^app/models/', '', '')
    let name = substitute(name, '\.rb$', '', '')

    call add(names, name)
  endfor

  return join(names, "\n")
endfunction

Как да намерим модел?

Как да намерим модел?

Как да намерим модел?

1 2 3 4 5
let g:global_hash = { 'command1': { ... some data ... }, ... }

function! s:CompletionFunction(argument_lead, command_line, cursor_position)
  return join(sort(keys(g:global_hash)), "\n")
endfunction

Как да намерим контролер?

Буквално по същия начин, само че вместо "models" ползваме "controllers".

Как да намерим контролер?

Буквално по същия начин, само че вместо "models" ползваме "controllers".

Формулата е толкова ясна, че Tim Pope я извлича в плъгин: https://github.com/tpope/vim-projectionist

Side note: защо не fuzzy finding?

Side note: защо не fuzzy finding?

Side note: защо не fuzzy finding?

Side note: защо не fuzzy finding?

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

… Следващия път

Следващия път

Следващия път

Следващия път

Следващия път

Следващия път

Въпроси