Регекси 2

27 април 2023

Преговор

Преговор

Преговор

Преговор

Удобен setting за search-ване

Удобен setting за search-ване

Удобен setting за search-ване

Удобен setting за допускане на грешки

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

Още регекси: lookahead/lookbehind

От документацията

1 2 3 4 5
/\@>    like matching a whole pattern
/\@=    requires a match
/\@!    requires NO match
/\@<=   requires a match behind
/\@<!   requires NO match behind

Още регекси: lookahead/lookbehind

От документацията

1 2 3 4 5
/\@>    like matching a whole pattern
/\@=    requires a match
/\@!    requires NO match
/\@<=   requires a match behind
/\@<!   requires NO match behind

Още регекси: lookahead/lookbehind

От документацията

1 2 3 4 5
/\@>    like matching a whole pattern
/\@=    requires a match
/\@!    requires NO match
/\@<=   requires a match behind
/\@<!   requires NO match behind

Още регекси: Vim-специфични

Още регекси: Vim-специфични

Още регекси: Vim-специфични

Още регекси: Vim-специфични

Още регекси: Vim-специфични

Още регекси: Vim-специфични

Highlighting

Просто връзваме filetype-специфичните групи с "глобалните":

1 2 3 4 5
highlight link javaScriptComment Comment
" За кратко:
hi link javaScriptComment Comment
" Ако някой реши да линкне *преди* това:
hi default link javaScriptComment Comment

Списък на всички стандартни групи, които не са filetype-specific: :help highlight-groups. Стандартните конвенции като "Comment", "String" etc се гледат от някоя цветова схема.

Highlighting: GUI

Цветовите схеми highlight-ват generic групите:

1 2 3 4 5
highlight {група} gui=<bold,italic,etc> guifg=#<color> guibg=#<color> guisp=#<color>

hi Search guifg=#e4e4e4 guibg=#6a0dad gui=NONE
hi SpellBad guifg=#ff0000 guibg=NONE guisp=#ff0000 gui=undercurl cterm=underline
hi SpellCap guifg=#00d700 guibg=NONE guisp=#00d700 gui=undercurl cterm=underline

Highlighting: Terminal

1 2 3 4
highlight {група} ctermfg=<число> ctermbg=<число> cterm=<ефект>

hi Search ctermfg=254 ctermbg=55 cterm=NONE
hi IncSearch ctermfg=29 ctermbg=NONE cterm=reverse

Highlighting

Командата highlight може да се извиква колкото пъти си искате с каквато и да е комбинация от gui, cterm, term параметри, примерно и двата начина работят:

1 2 3 4
hi Comment ctermfg=248
hi Comment guifg=#777777

hi Comment ctermfg=248 guifg=#777777

Също така може просто да викнете :highlight или :highlght <група> за информация

Highlighting

Highlighting

Highlighting

Highlighting

Fun: TOhtml

Fun: TOhtml

Fun: TOhtml

Естетиката е важна

Откъде да намерим цветове?

Естетиката е важна

Откъде да намерим цветове?

Естетиката е важна

Откъде да намерим цветове?

Естетиката е важна

Откъде да намерим цветове?

Естетиката е важна

Откъде да намерим цветове?

Spellcheck

Spellcheck

Spellcheck

Spellcheck

Spellcheck

Substitute()

Обратно на регексите

Substitute()

Обратно на регексите

1 2
let string = "Hello, World!"
let string = substitute(string, '\k\+\ze!', 'Vim', 'g')

Substitute()

:help substitute() (скобките са важни, иначе help-а ще отвори :substitute)

Substitute()

:help substitute() (скобките са важни, иначе help-а ще отвори :substitute)

Substitute()

:help substitute() (скобките са важни, иначе help-а ще отвори :substitute)

Substitute()

:help substitute() (скобките са важни, иначе help-а ще отвори :substitute)

Match*()

Match*()

1 2
echo matchlist("  big if true", '\(\S.*\) if \(.*\)')
" ['big if true', 'big', 'true', '', '', '', '', '', '', '']
1
let [match, body, condition; rest] = matchlist(" big if true", '\(\S.*\) if \(.*\)')

Matchstr()

1 2 3 4 5
echo matchstr("  big if true", '\(\S.*\) if \(.*\)')
" big if true

echo matchstr("  big if true", '\zs\S.*\ze if .*')
" big

Matchstrpos()

1 2 3 4 5 6
let [word1, start1, end1] = matchstrpos("One word, then another", '\w\+')
let [word2, start2, end2] = matchstrpos("One word, then another", '\w\+', end1)
let [word3, start3, end3] = matchstrpos("One word, then another", '\w\+', end2)

echo string([word1, word2, word3])
" ['One', 'word', 'then']

Напомням, че има :help string-functions

Search*()

Доста важна фамилия от функции. Работят върху буфера, не върху низове.

Search*()

Доста важна фамилия от функции. Работят върху буфера, не върху низове.

Search*()

Доста важна фамилия от функции. Работят върху буфера, не върху низове.

Search*()

Доста важна фамилия от функции. Работят върху буфера, не върху низове.

Search()

Флагове -- комбинират се в един низ

Search()

Флагове -- комбинират се в един низ

Search()

Флагове -- комбинират се в един низ

Search()

Флагове -- комбинират се в един низ

Search()

Флагове -- комбинират се в един низ

Search()

Флагове -- комбинират се в един низ

Има и други флагове, но тези са най-често използвани. Пример: search('https://', 'Wbc') ще потърси за линк някъде преди курсора, но ще го приеме и на текущата позиция. Няма да wrap-не ако не намери нищо.

Search()

Search()

Search()

Searchpair()

searchpair({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])

1
call searchpair('\<if\>', '\<else\>', '\<endif\>')

Скача по първия, втория, третия регекс, и игнорира вложени match-ове!

1 2 3 4 5 6 7 8 9 10 11
if outer_branch
  echomsg "Yes"
else
  echomsg "No"

  if inner_branch
    echomsg "Maybe"
  else
    echomsg "I don't know"
  endif
endif

Searchpair()

Вървене на обратно с 'b' работи, но е малко странно.

1 2 3 4 5 6 7 8 9 10
The search starts exactly at the cursor.  A match with
{start}, {middle} or {end} at the next character, in the
direction of searching, is the first one found.

[...]

When searching backwards and {end} is more than one character,
it may be useful to put "\zs" at the end of the pattern, so
that when the cursor is inside a match with the end it finds
the matching start.

Т.е. за горния пример, ако вървим на обратно, вероятно искаме:

1
call searchpair('\<if\>', '\<else\>\zs', '\<endif\>\zs', 'b')

(В повечето случаи ще търсим неща напред, so whatever)

Aside: matchit

Сложете си това във vimrc-тата и ще имате по-мощен %:

1
packadd matchit

Search*()

Switch клонинг

Сменяме единични на двойни кавички под курсора. Опростен вариант на switch.vim

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let test_data = ["foo", "bar", "baz"]

let g:test_definitions = {
      \ '"\(\k\+\)"': '''\1''',
      \ '''\(\k\+\)''': '"\1"',
      \ }

nnoremap - :call Switch(g:test_definitions)<cr>

function! Switch(definitions)
  let saved_view = winsaveview()
  defer winrestview(saved_view)

  " ...
endfunction

Switch клонинг

Един вариант -- намираме pattern-а и добавяме \%# за да се погрижим да се изпълни точно където сме match-нали.

1 2 3 4 5 6 7 8 9 10
function! Switch(definitions)
  let saved_view = winsaveview()
  defer winrestview(saved_view)

  for [pattern, replacement] in items(a:definitions)
    if search(pattern, 'Wcb')
      exe 's/\%#'..escape(pattern, '/')..'/'..escape(replacement, '/').'/'
    endif
  endfor
endfunction

Switch клонинг

Втори вариант, без да местим курсора -- вземаме координатите и ги прикачваме към pattern-a със \%c

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
function! Switch(definitions)
  let saved_view = winsaveview()
  defer winrestview(saved_view)

  let cursor_col = col('.')

  for [pattern, replacement] in items(a:definitions)
    let [_, start_col] = searchpos(pattern, 'Wcnb', line('.'))
    if start_col == 0 || start_col > cursor_col
      continue
    endif

    let [_, end_col] = searchpos(pattern, 'Wcne', line('.'))
    if end_col == 0 || end_col < cursor_col
      continue
    endif

    let pattern     = $'\%{start_col}c{pattern}\%{end_col + 1}c'
    let replacement = replacement

    exe 's/'..escape(pattern, '/')..'/'..escape(replacement, '/')
    return
  endfor
endfunction

Splitjoin клонинг

Трансформираме if-клаузи от body if condition на if condition \n body \n end. Join-ването може да е толкова лесно (стига да можете да прочетете регекса :D)

1 2 3 4 5 6
function Join()
  let saved_view = winsaveview()
  defer winrestview(saved_view)

  s/if \(.*\)\n\s*\(.*\)\n\s*end$/\2 if \1/e
endfunction

(e флага -- да не хвърли грешка ако не match-не)

Splitjoin клонинг

Сплитването също спокойно може да е един substitution. Но ако искаме да избягваме if-ове в низове и коментари…

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
function Split()
  let saved_view = winsaveview()
  defer winrestview(saved_view)

  let pattern = '\S \zsif \S'
  let skip = 'synID(line("."), col("."), 0)->synIDattr("name") =~# "String\|Comment"'

  normal! 0
  let [_, if_col] = searchpos(pattern, 'W', line('.'), 0, skip)
  if if_col == 0
    return
  endif

  let if_line = getline('.')->strpart(if_col - 1)
  let body    = getline('.')->strpart(0, if_col - 1)->trim()
  let indent  = getline('.')->matchstr('^\s*')

  call setline('.', indent .. if_line)
  call append(line('.'), [indent .. '  ' .. body, indent .. 'end'])
endfunction

Splitjoin клонинг

Без foo()->bar() синтаксиса (можете ли да напишете vimscript, който да прави тази трансформация, switch-style?) и със ламбда функция за skip expression-а вместо eval-нат низ.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
function Split()
  let saved_view = winsaveview()
  defer winrestview(saved_view)

  let pattern = '\S \zsif \S'
  let Skip = {-> synIDattr(synID(line("."), col("."), 0), "name") =~# "String\|Comment" }

  normal! 0
  let [_, if_col] = searchpos(pattern, 'W', line('.'), 0, Skip)
  if if_col == 0
    return
  endif

  let if_line = strpart(getline('.'), if_col - 1)
  let body    = trim(strpart(getline('.'), 0, if_col - 1))
  let indent  = matchstr(getline('.'), '^\s*')

  call setline('.', indent .. if_line)
  call append(line('.'), [indent .. '  ' .. body, indent .. 'end'])
endfunction

(Забележете, че skip вече е Skip. Функциите трябва да започват с главна буква :))

Splitjoin

Пълния плъгин е доста по-голям: https://github.com/andrewradev/splitjoin.vim

Но индивидуалните функции решават конкретни малки проблеми. Стъпка по стъпка можете за проекта да си направите такъв плъгин, който просто има 3-4 полезни трансформации за 3-4 различни filetype-а, стига да измислите общата "схема", какво концептуално прави.

Въпроси