vim9script # Name: autoload/text.vim # Author: Maxim Kim # Desc: Text manipulation functions. # Fix text: # * replace non-breaking spaces with spaces # * replace multiple spaces with a single space (preserving indent) # * remove spaces between closed braces: ) ) -> )) # * remove space before closed brace: word ) -> word) # * remove space after opened brace: ( word -> (word # * remove space at the end of line # Usage: # command! -range FixSpaces call text#fix_spaces(,) export def FixSpaces(line1: number, line2: number) var view = winsaveview() defer winrestview(view) # replace non-breaking space to space first exe printf('silent :%d,%ds/\%%xA0/ /ge', line1, line2) # replace multiple spaces to a single space (preserving indent) exe printf('silent :%d,%ds/\S\+\zs\(\s\|\%%xa0\)\+/ /ge', line1, line2) # remove spaces between closed braces: ) ) -> )) exe printf('silent :%d,%ds/)\s\+)\@=/)/ge', line1, line2) # remove spaces between opened braces: ( ( -> (( exe printf('silent :%d,%ds/(\s\+(\@=/(/ge', line1, line2) # remove space before closed brace: word ) -> word) exe printf('silent :%d,%ds/\s)/)/ge', line1, line2) # remove space after opened brace: ( word -> (word exe printf('silent :%d,%ds/(\s/(/ge', line1, line2) # remove space at the end of line exe printf('silent :%d,%ds/\s*$//ge', line1, line2) enddef # Underline current line with a chars # example mappings: # nnoremap = :call text#Underline('=') # nnoremap - :call text#Underline('-') # nnoremap ~ :call text#Underline('~') # nnoremap ^ :call text#Underline('^') # nnoremap + :call text#Underline('+') export def Underline(char: string) var nextnr = line('.') + 1 var line = matchlist(getline('.'), '^\(\s*\)\(.*\)$') if empty(line[2]) | return | endif var underline = line[1] .. repeat(char, strchars(line[2])) if getline(nextnr) =~ '^\s*' .. escape(char, '*\~^.') .. '\+$' setline(nextnr, underline) else append('.', underline) endif enddef # Dates (text object and stuff) var mons_en = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] var months_en = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] var months_ru = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'] var months = extend(months_en, months_ru) months = extend(months, mons_en) g:months = copy(months) # * ISO-8601 2020-03-21 # * RU 21 марта 2020 # * EN 10 December 2012 # * EN December 10, 2012 # * EN 10 Dec 2012 # * EN Dec 10, 2012 # Usage: # xnoremap id :call text#ObjDate(1) # onoremap id :normal vid # xnoremap ad :call text#ObjDate(0) # onoremap ad :normal vad export def ObjDate(inner: bool) var view = winsaveview() defer winrestview(view) var cword = expand("") if cword =~ '\d\{4}' # var rx = '^\|' var rx = '\%(\D\d\{1,2}\s\+\%(' .. join(months, '\|') .. '\)\)' rx ..= '\|' rx ..= '\%(\s*\%(' .. join(months, '\|') .. '\)\s\+\d\{1,2},\)' if !search(rx, 'bcW', line('.')) search('\s*\D', 'bcW', line('.')) endif elseif cword =~ join(months, '\|') search('^\|\D\ze\d\{1,2}\s\+', 'bceW') elseif cword =~ '\d\{1,2}' if !search('^\|\S\ze\%(' .. join(months, '\|') .. '\)\s\+\d\{1,2}', 'bceW') search('^\|[^0-9\-]', 'becW') endif endif var rxdate = '\%(\d\{4}-\d\{2}-\d\{2}\)' rxdate ..= '\|' rxdate ..= '\%(\d\{1,2}\s\+\%(' .. join(months, '\|') .. '\)\s\+\d\{4}\)' rxdate ..= '\|' rxdate ..= '\%(\%(' .. join(months, '\|') .. '\)\s\+\d\{1,2},\s\+\d\{4}\)' if !inner rxdate = '\s*\%(' .. rxdate .. '\)\s*' endif if search(rxdate, 'cW') > 0 normal v search(rxdate, 'ecW') return endif enddef export def ObjDateRu() var [year, month, day] = split(strftime("%Y-%m-%d"), '-') return printf("%d %s %s", day, months_ru[month-1], year) enddef # number text object export def ObjNumber() var rx_num = '\d\+\(\.\d\+\)*' if search(rx_num, 'ceW') > 0 normal! v search(rx_num, 'bcW') endif enddef # Line text object export def ObjLine(inner: bool) if inner normal! g_v^ else normal! $v0 endif enddef # Indent text object # Usage: # onoremap ii :call text#obj_indent(v:true) # onoremap ai :call text#obj_indent(v:false) # xnoremap ii :call text#obj_indent(v:true) # xnoremap ai :call text#obj_indent(v:false) export def ObjIndent(inner: bool) var ln_start: number var ln_end: number if getline('.') =~ '^\s*$' ln_start = prevnonblank('.') ?? 1 ln_end = ln_start else ln_start = line('.') ln_end = ln_start endif var indent = indent(ln_start) while ln_start > 0 && indent(ln_start) >= indent ln_start = prevnonblank(ln_start - 1) endwhile while ln_end <= line('$') && indent(ln_end) >= indent ln_end = nextnonblank(ln_end + 1) ?? line('$') + 1 endwhile if inner ln_start = nextnonblank(ln_start + 1) ?? line('$') + 1 ln_end = prevnonblank(ln_end - 1) else ln_start += 1 ln_end -= 1 endif if ln_end < ln_start ln_end = ln_start endif exe ":" ln_end normal! V exe ":" ln_start enddef # 26 simple text objects # ---------------------- # i_ i. i: i, i; i| i/ i\ i* i+ i- i# i # a_ a. a: a, a; a| a/ a\ a* a+ a- a# a # Usage: # for char in [ '_', '.', ':', ',', ';', '', '/', '', '*', '+', '-', '#', '' ] # execute 'xnoremap i' .. char .. ' :call text#Obj("' .. char .. '", 1)' # execute 'xnoremap a' .. char .. ' :call text#Obj("' .. char .. '", 0)' # execute 'onoremap i' .. char .. ' :normal vi' .. char .. '' # execute 'onoremap a' .. char .. ' :normal va' .. char .. '' # endfor export def Obj(char: string, inner: bool) var lnum = line('.') var echar = escape(char, '.*') if (search('^\|' .. echar, 'cnbW', lnum) > 0 && search(echar, 'W', lnum) > 0) || (search(echar, 'nbW', lnum) > 0 && search(echar .. '\|$', 'cW', lnum) > 0) if inner search('[^' .. char .. ']', 'cbW', lnum) endif normal! v search('^\|' .. echar, 'bW', lnum) if inner search('[^' .. char .. ']', 'cW', lnum) endif return endif enddef # Toggle current word # nnoremap call text#Toggle() export def Toggle() var toggles = { true: 'false', false: 'true', True: 'False', False: 'True', TRUE: 'FALSE', FALSE: 'TRUE', yes: 'no', no: 'yes', Yes: 'No', No: 'Yes', YES: 'NO', NO: 'YES', on: 'off', off: 'on', On: 'Off', Off: 'On', ON: 'OFF', OFF: 'ON', open: 'close', close: 'open', Open: 'Close', Close: 'Open', dark: 'light', light: 'dark', width: 'height', height: 'width', first: 'last', last: 'first', top: 'right', right: 'bottom', bottom: 'left', left: 'center', center: 'top', } var word = expand("") if toggles->has_key(word) execute 'normal! "_ciw' .. toggles[word] endif enddef