mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-02 22:30:04 +08:00
882 lines
23 KiB
VimL
882 lines
23 KiB
VimL
|
" vim: foldmethod=marker
|
||
|
|
||
|
" Main entry points {{{1
|
||
|
"
|
||
|
" The two main functions that loop through callbacks and execute them.
|
||
|
"
|
||
|
" function! sj#Split() {{{2
|
||
|
"
|
||
|
function! sj#Split()
|
||
|
if !exists('b:splitjoin_split_callbacks')
|
||
|
return
|
||
|
end
|
||
|
|
||
|
" expand any folds under the cursor, or we might replace the wrong area
|
||
|
silent! foldopen
|
||
|
|
||
|
let disabled_callbacks = sj#settings#Read('disabled_split_callbacks')
|
||
|
|
||
|
let saved_view = winsaveview()
|
||
|
let saved_whichwrap = &whichwrap
|
||
|
set whichwrap-=l
|
||
|
|
||
|
if !sj#settings#Read('quiet') | echo "Splitjoin: Working..." | endif
|
||
|
for callback in b:splitjoin_split_callbacks
|
||
|
if index(disabled_callbacks, callback) >= 0
|
||
|
continue
|
||
|
endif
|
||
|
|
||
|
try
|
||
|
call sj#PushCursor()
|
||
|
|
||
|
if call(callback, [])
|
||
|
silent! call repeat#set("\<plug>SplitjoinSplit")
|
||
|
let &whichwrap = saved_whichwrap
|
||
|
if !sj#settings#Read('quiet')
|
||
|
" clear progress message
|
||
|
redraw | echo ""
|
||
|
endif
|
||
|
return 1
|
||
|
endif
|
||
|
|
||
|
finally
|
||
|
call sj#PopCursor()
|
||
|
endtry
|
||
|
endfor
|
||
|
|
||
|
call winrestview(saved_view)
|
||
|
let &whichwrap = saved_whichwrap
|
||
|
if !sj#settings#Read('quiet')
|
||
|
" clear progress message
|
||
|
redraw | echo ""
|
||
|
endif
|
||
|
return 0
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#Join() {{{2
|
||
|
"
|
||
|
function! sj#Join()
|
||
|
if !exists('b:splitjoin_join_callbacks')
|
||
|
return
|
||
|
end
|
||
|
|
||
|
" expand any folds under the cursor, or we might replace the wrong area
|
||
|
silent! foldopen
|
||
|
|
||
|
let disabled_callbacks = sj#settings#Read('disabled_join_callbacks')
|
||
|
|
||
|
let saved_view = winsaveview()
|
||
|
let saved_whichwrap = &whichwrap
|
||
|
set whichwrap-=l
|
||
|
|
||
|
if !sj#settings#Read('quiet') | echo "Splitjoin: Working..." | endif
|
||
|
for callback in b:splitjoin_join_callbacks
|
||
|
if index(disabled_callbacks, callback) >= 0
|
||
|
continue
|
||
|
endif
|
||
|
|
||
|
try
|
||
|
call sj#PushCursor()
|
||
|
|
||
|
if call(callback, [])
|
||
|
silent! call repeat#set("\<plug>SplitjoinJoin")
|
||
|
let &whichwrap = saved_whichwrap
|
||
|
if !sj#settings#Read('quiet')
|
||
|
" clear progress message
|
||
|
redraw | echo ""
|
||
|
endif
|
||
|
return 1
|
||
|
endif
|
||
|
|
||
|
finally
|
||
|
call sj#PopCursor()
|
||
|
endtry
|
||
|
endfor
|
||
|
|
||
|
call winrestview(saved_view)
|
||
|
let &whichwrap = saved_whichwrap
|
||
|
if !sj#settings#Read('quiet')
|
||
|
" clear progress message
|
||
|
redraw | echo ""
|
||
|
endif
|
||
|
return 0
|
||
|
endfunction
|
||
|
|
||
|
" Cursor stack manipulation {{{1
|
||
|
"
|
||
|
" In order to make the pattern of saving the cursor and restoring it
|
||
|
" afterwards easier, these functions implement a simple cursor stack. The
|
||
|
" basic usage is:
|
||
|
"
|
||
|
" call sj#PushCursor()
|
||
|
" " Do stuff that move the cursor around
|
||
|
" call sj#PopCursor()
|
||
|
"
|
||
|
" function! sj#PushCursor() {{{2
|
||
|
"
|
||
|
" Adds the current cursor position to the cursor stack.
|
||
|
function! sj#PushCursor()
|
||
|
if !exists('b:cursor_position_stack')
|
||
|
let b:cursor_position_stack = []
|
||
|
endif
|
||
|
|
||
|
call add(b:cursor_position_stack, winsaveview())
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#PopCursor() {{{2
|
||
|
"
|
||
|
" Restores the cursor to the latest position in the cursor stack, as added
|
||
|
" from the sj#PushCursor function. Removes the position from the stack.
|
||
|
function! sj#PopCursor()
|
||
|
call winrestview(remove(b:cursor_position_stack, -1))
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#DropCursor() {{{2
|
||
|
"
|
||
|
" Discards the last saved cursor position from the cursor stack.
|
||
|
" Note that if the cursor hasn't been saved at all, this will raise an error.
|
||
|
function! sj#DropCursor()
|
||
|
call remove(b:cursor_position_stack, -1)
|
||
|
endfunction
|
||
|
|
||
|
" Indenting {{{1
|
||
|
"
|
||
|
" Some languages don't have built-in support, and some languages have semantic
|
||
|
" indentation. In such cases, code blocks might need to be reindented
|
||
|
" manually.
|
||
|
"
|
||
|
|
||
|
" function! sj#SetIndent(start_lineno, end_lineno, indent) {{{2
|
||
|
" function! sj#SetIndent(lineno, indent)
|
||
|
"
|
||
|
" Sets the indent of the given line numbers to "indent" amount of whitespace.
|
||
|
"
|
||
|
function! sj#SetIndent(...)
|
||
|
if a:0 == 3
|
||
|
let start_lineno = a:1
|
||
|
let end_lineno = a:2
|
||
|
let indent = a:3
|
||
|
elseif a:0 == 2
|
||
|
let start_lineno = a:1
|
||
|
let end_lineno = a:1
|
||
|
let indent = a:2
|
||
|
endif
|
||
|
|
||
|
let is_tabs = &l:expandtab
|
||
|
let shift = shiftwidth()
|
||
|
|
||
|
if is_tabs == 0
|
||
|
if shift > 0
|
||
|
let indent = indent / shift
|
||
|
endif
|
||
|
|
||
|
let whitespace = repeat('\t', indent)
|
||
|
else
|
||
|
let whitespace = repeat(' ', indent)
|
||
|
endif
|
||
|
|
||
|
exe start_lineno.','.end_lineno.'s/^\s*/'.whitespace
|
||
|
|
||
|
" Don't leave a history entry
|
||
|
call histdel('search', -1)
|
||
|
let @/ = histget('search', -1)
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#PeekCursor() {{{2
|
||
|
"
|
||
|
" Returns the last saved cursor position from the cursor stack.
|
||
|
" Note that if the cursor hasn't been saved at all, this will raise an error.
|
||
|
function! sj#PeekCursor()
|
||
|
return b:cursor_position_stack[-1]
|
||
|
endfunction
|
||
|
|
||
|
" Text replacement {{{1
|
||
|
"
|
||
|
" Vim doesn't seem to have a whole lot of functions to aid in text replacement
|
||
|
" within a buffer. The ":normal!" command usually works just fine, but it
|
||
|
" could be difficult to maintain sometimes. These functions encapsulate a few
|
||
|
" common patterns for this.
|
||
|
|
||
|
" function! sj#ReplaceMotion(motion, text) {{{2
|
||
|
"
|
||
|
" Replace the normal mode "motion" with "text". This is mostly just a wrapper
|
||
|
" for a normal! command with a paste, but doesn't pollute any registers.
|
||
|
"
|
||
|
" Examples:
|
||
|
" call sj#ReplaceMotion('Va{', 'some text')
|
||
|
" call sj#ReplaceMotion('V', 'replacement line')
|
||
|
"
|
||
|
" Note that the motion needs to include a visual mode key, like "V", "v" or
|
||
|
" "gv"
|
||
|
function! sj#ReplaceMotion(motion, text)
|
||
|
" reset clipboard to avoid problems with 'unnamed' and 'autoselect'
|
||
|
let saved_clipboard = &clipboard
|
||
|
set clipboard=
|
||
|
let saved_selection = &selection
|
||
|
let &selection = "inclusive"
|
||
|
|
||
|
let saved_register_text = getreg('"', 1)
|
||
|
let saved_register_type = getregtype('"')
|
||
|
let saved_opening_visual = getpos("'<")
|
||
|
let saved_closing_visual = getpos("'>")
|
||
|
|
||
|
call setreg('"', a:text, 'v')
|
||
|
exec 'silent noautocmd normal! '.a:motion.'p'
|
||
|
|
||
|
" TODO (2021-02-22) Not a good idea to rely on reindent here
|
||
|
silent normal! gv=
|
||
|
|
||
|
call setreg('"', saved_register_text, saved_register_type)
|
||
|
call setpos("'<", saved_opening_visual)
|
||
|
call setpos("'>", saved_closing_visual)
|
||
|
|
||
|
let &clipboard = saved_clipboard
|
||
|
let &selection = saved_selection
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#ReplaceLines(start, end, text) {{{2
|
||
|
"
|
||
|
" Replace the area defined by the 'start' and 'end' lines with 'text'.
|
||
|
function! sj#ReplaceLines(start, end, text)
|
||
|
let interval = a:end - a:start
|
||
|
|
||
|
if interval == 0
|
||
|
return sj#ReplaceMotion(a:start.'GV', a:text)
|
||
|
else
|
||
|
return sj#ReplaceMotion(a:start.'GV'.interval.'j', a:text)
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#ReplaceCols(start, end, text) {{{2
|
||
|
"
|
||
|
" Replace the area defined by the 'start' and 'end' columns on the current
|
||
|
" line with 'text'
|
||
|
function! sj#ReplaceCols(start, end, text)
|
||
|
let start_position = getpos('.')
|
||
|
let end_position = getpos('.')
|
||
|
|
||
|
let start_position[2] = a:start
|
||
|
let end_position[2] = a:end
|
||
|
|
||
|
return sj#ReplaceByPosition(start_position, end_position, a:text)
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#ReplaceByPosition(start, end, text) {{{2
|
||
|
"
|
||
|
" Replace the area defined by the 'start' and 'end' positions with 'text'. The
|
||
|
" positions should be compatible with the results of getpos():
|
||
|
"
|
||
|
" [bufnum, lnum, col, off]
|
||
|
"
|
||
|
function! sj#ReplaceByPosition(start, end, text)
|
||
|
let saved_z_pos = getpos("'z")
|
||
|
|
||
|
try
|
||
|
call setpos('.', a:start)
|
||
|
call setpos("'z", a:end)
|
||
|
|
||
|
return sj#ReplaceMotion('v`z', a:text)
|
||
|
finally
|
||
|
call setpos("'z", saved_z_pos)
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
" Text retrieval {{{1
|
||
|
"
|
||
|
" These functions are similar to the text replacement functions, only retrieve
|
||
|
" the text instead.
|
||
|
"
|
||
|
" function! sj#GetMotion(motion) {{{2
|
||
|
"
|
||
|
" Execute the normal mode motion "motion" and return the text it marks.
|
||
|
"
|
||
|
" Note that the motion needs to include a visual mode key, like "V", "v" or
|
||
|
" "gv"
|
||
|
function! sj#GetMotion(motion)
|
||
|
call sj#PushCursor()
|
||
|
|
||
|
let saved_selection = &selection
|
||
|
let &selection = "inclusive"
|
||
|
let saved_register_text = getreg('z', 1)
|
||
|
let saved_register_type = getregtype('z')
|
||
|
let saved_opening_visual = getpos("'<")
|
||
|
let saved_closing_visual = getpos("'>")
|
||
|
|
||
|
let @z = ''
|
||
|
exec 'silent noautocmd normal! '.a:motion.'"zy'
|
||
|
let text = @z
|
||
|
|
||
|
if text == ''
|
||
|
" nothing got selected, so we might still be in visual mode
|
||
|
exe "normal! \<esc>"
|
||
|
endif
|
||
|
|
||
|
call setreg('z', saved_register_text, saved_register_type)
|
||
|
call setpos("'<", saved_opening_visual)
|
||
|
call setpos("'>", saved_closing_visual)
|
||
|
let &selection = saved_selection
|
||
|
|
||
|
call sj#PopCursor()
|
||
|
|
||
|
return text
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#GetLines(start, end) {{{2
|
||
|
"
|
||
|
" Retrieve the lines from "start" to "end" and return them as a list. This is
|
||
|
" simply a wrapper for getbufline for the moment.
|
||
|
function! sj#GetLines(start, end)
|
||
|
return getbufline('%', a:start, a:end)
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#GetCols(start, end) {{{2
|
||
|
"
|
||
|
" Retrieve the text from columns "start" to "end" on the current line.
|
||
|
function! sj#GetCols(start, end)
|
||
|
return strpart(getline('.'), a:start - 1, a:end - a:start + 1)
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#GetByPosition(start, end) {{{2
|
||
|
"
|
||
|
" Fetch the area defined by the 'start' and 'end' positions. The positions
|
||
|
" should be compatible with the results of getpos():
|
||
|
"
|
||
|
" [bufnum, lnum, col, off]
|
||
|
"
|
||
|
function! sj#GetByPosition(start, end)
|
||
|
let saved_z_pos = getpos("'z")
|
||
|
|
||
|
try
|
||
|
call setpos('.', a:start)
|
||
|
call setpos("'z", a:end)
|
||
|
|
||
|
return sj#GetMotion('v`z')
|
||
|
finally
|
||
|
call setpos("'z", saved_z_pos)
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
" String functions {{{1
|
||
|
" Various string manipulation utility functions
|
||
|
function! sj#BlankString(s)
|
||
|
return (a:s =~ '^\s*$')
|
||
|
endfunction
|
||
|
|
||
|
" Surprisingly, Vim doesn't seem to have a "trim" function. In any case, these
|
||
|
" should be fairly obvious.
|
||
|
function! sj#Ltrim(s)
|
||
|
return substitute(a:s, '^\_s\+', '', '')
|
||
|
endfunction
|
||
|
function! sj#Rtrim(s)
|
||
|
return substitute(a:s, '\_s\+$', '', '')
|
||
|
endfunction
|
||
|
function! sj#Trim(s)
|
||
|
return sj#Rtrim(sj#Ltrim(a:s))
|
||
|
endfunction
|
||
|
|
||
|
" Execute sj#Trim on each item of a List
|
||
|
function! sj#TrimList(list)
|
||
|
return map(a:list, 'sj#Trim(v:val)')
|
||
|
endfunction
|
||
|
|
||
|
" Remove blank strings from the List
|
||
|
function! sj#RemoveBlanks(list)
|
||
|
return filter(a:list, 'v:val !~ "^\\s*$"')
|
||
|
endfunction
|
||
|
|
||
|
" Searching for patterns {{{1
|
||
|
"
|
||
|
" function! sj#SearchUnderCursor(pattern, flags, skip) {{{2
|
||
|
"
|
||
|
" Searches for a match for the given pattern under the cursor. Returns the
|
||
|
" result of the |search()| call if a match was found, 0 otherwise.
|
||
|
"
|
||
|
" Moves the cursor unless the 'n' flag is given.
|
||
|
"
|
||
|
" The a:flags parameter can include one of "e", "p", "s", "n", which work the
|
||
|
" same way as the built-in |search()| call. Any other flags will be ignored.
|
||
|
"
|
||
|
function! sj#SearchUnderCursor(pattern, ...)
|
||
|
let [match_start, match_end] = call('sj#SearchColsUnderCursor', [a:pattern] + a:000)
|
||
|
if match_start > 0
|
||
|
return match_start
|
||
|
else
|
||
|
return 0
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#SearchColsUnderCursor(pattern, flags, skip) {{{2
|
||
|
"
|
||
|
" Searches for a match for the given pattern under the cursor. Returns the
|
||
|
" start and (end + 1) column positions of the match. If nothing was found,
|
||
|
" returns [0, 0].
|
||
|
"
|
||
|
" Moves the cursor unless the 'n' flag is given.
|
||
|
"
|
||
|
" Respects the skip expression if it's given.
|
||
|
"
|
||
|
" See sj#SearchUnderCursor for the behaviour of a:flags
|
||
|
"
|
||
|
function! sj#SearchColsUnderCursor(pattern, ...)
|
||
|
if a:0 >= 1
|
||
|
let given_flags = a:1
|
||
|
else
|
||
|
let given_flags = ''
|
||
|
endif
|
||
|
|
||
|
if a:0 >= 2
|
||
|
let skip = a:2
|
||
|
else
|
||
|
let skip = ''
|
||
|
endif
|
||
|
|
||
|
let lnum = line('.')
|
||
|
let col = col('.')
|
||
|
let pattern = a:pattern
|
||
|
let extra_flags = ''
|
||
|
|
||
|
" handle any extra flags provided by the user
|
||
|
for char in ['e', 'p', 's']
|
||
|
if stridx(given_flags, char) >= 0
|
||
|
let extra_flags .= char
|
||
|
endif
|
||
|
endfor
|
||
|
|
||
|
call sj#PushCursor()
|
||
|
|
||
|
" find the start of the pattern
|
||
|
call search(pattern, 'bcW', lnum)
|
||
|
let search_result = sj#SearchSkip(pattern, skip, 'cW'.extra_flags, lnum)
|
||
|
if search_result <= 0
|
||
|
call sj#PopCursor()
|
||
|
return [0, 0]
|
||
|
endif
|
||
|
|
||
|
call sj#PushCursor()
|
||
|
|
||
|
" find the end of the pattern
|
||
|
if stridx(extra_flags, 'e') >= 0
|
||
|
let match_end = col('.')
|
||
|
|
||
|
call sj#PushCursor()
|
||
|
call sj#SearchSkip(pattern, skip, 'cWb', lnum)
|
||
|
let match_start = col('.')
|
||
|
call sj#PopCursor()
|
||
|
else
|
||
|
let match_start = col('.')
|
||
|
call sj#SearchSkip(pattern, skip, 'cWe', lnum)
|
||
|
let match_end = col('.')
|
||
|
end
|
||
|
|
||
|
" set the end of the pattern to the next character, or EOL. Extra logic
|
||
|
" is for multibyte characters.
|
||
|
normal! l
|
||
|
if col('.') == match_end
|
||
|
" no movement, we must be at the end
|
||
|
let match_end = col('$')
|
||
|
else
|
||
|
let match_end = col('.')
|
||
|
endif
|
||
|
call sj#PopCursor()
|
||
|
|
||
|
if !sj#ColBetween(col, match_start, match_end)
|
||
|
" then the cursor is not in the pattern
|
||
|
call sj#PopCursor()
|
||
|
return [0, 0]
|
||
|
else
|
||
|
" a match has been found
|
||
|
if stridx(given_flags, 'n') >= 0
|
||
|
call sj#PopCursor()
|
||
|
else
|
||
|
call sj#DropCursor()
|
||
|
endif
|
||
|
|
||
|
return [match_start, match_end]
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" function! sj#SearchSkip(pattern, skip, ...) {{{2
|
||
|
" A partial replacement to search() that consults a skip pattern when
|
||
|
" performing a search, just like searchpair().
|
||
|
"
|
||
|
" Note that it doesn't accept the "n" and "c" flags due to implementation
|
||
|
" difficulties.
|
||
|
function! sj#SearchSkip(pattern, skip, ...)
|
||
|
" collect all of our arguments
|
||
|
let pattern = a:pattern
|
||
|
let skip = a:skip
|
||
|
|
||
|
if a:0 >= 1
|
||
|
let flags = a:1
|
||
|
else
|
||
|
let flags = ''
|
||
|
endif
|
||
|
|
||
|
if stridx(flags, 'n') > -1
|
||
|
echoerr "Doesn't work with 'n' flag, was given: ".flags
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
let stopline = (a:0 >= 2) ? a:2 : 0
|
||
|
let timeout = (a:0 >= 3) ? a:3 : 0
|
||
|
|
||
|
" Note: Native search() seems to hit a bug with one of the HTML tests
|
||
|
" (because of \zs?)
|
||
|
if skip == ''
|
||
|
" no skip, can delegate to native search()
|
||
|
return search(pattern, flags, stopline, timeout)
|
||
|
" elseif has('patch-8.2.915')
|
||
|
" " the native search() function can do this now:
|
||
|
" return search(pattern, flags, stopline, timeout, skip)
|
||
|
endif
|
||
|
|
||
|
" search for the pattern, skipping a match if necessary
|
||
|
let skip_match = 1
|
||
|
while skip_match
|
||
|
let match = search(pattern, flags, stopline, timeout)
|
||
|
|
||
|
" remove 'c' flag for any run after the first
|
||
|
let flags = substitute(flags, 'c', '', 'g')
|
||
|
|
||
|
if match && eval(skip)
|
||
|
let skip_match = 1
|
||
|
else
|
||
|
let skip_match = 0
|
||
|
endif
|
||
|
endwhile
|
||
|
|
||
|
return match
|
||
|
endfunction
|
||
|
|
||
|
function! sj#SkipSyntax(syntax_groups)
|
||
|
let syntax_groups = a:syntax_groups
|
||
|
let skip_pattern = '\%('.join(syntax_groups, '\|').'\)'
|
||
|
|
||
|
return "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".skip_pattern."'"
|
||
|
endfunction
|
||
|
|
||
|
function! sj#IncludeSyntax(syntax_groups)
|
||
|
let syntax_groups = a:syntax_groups
|
||
|
let include_pattern = '\%('.join(syntax_groups, '\|').'\)'
|
||
|
|
||
|
return "synIDattr(synID(line('.'),col('.'),1),'name') !~ '".include_pattern."'"
|
||
|
endfunction
|
||
|
|
||
|
" Checks if the current position of the cursor is within the given limits.
|
||
|
"
|
||
|
function! sj#CursorBetween(start, end)
|
||
|
return sj#ColBetween(col('.'), a:start, a:end)
|
||
|
endfunction
|
||
|
|
||
|
" Checks if the given column is within the given limits.
|
||
|
"
|
||
|
function! sj#ColBetween(col, start, end)
|
||
|
return a:start <= a:col && a:end > a:col
|
||
|
endfunction
|
||
|
|
||
|
" Regex helpers {{{1
|
||
|
"
|
||
|
" function! sj#ExtractRx(expr, pat, sub) {{{2
|
||
|
"
|
||
|
" Extract a regex match from a string. Ordinarily, substitute() would be used
|
||
|
" for this, but it's a bit too cumbersome for extracting a particular grouped
|
||
|
" match. Example usage:
|
||
|
"
|
||
|
" sj#ExtractRx('foo:bar:baz', ':\(.*\):', '\1') == 'bar'
|
||
|
"
|
||
|
function! sj#ExtractRx(expr, pat, sub)
|
||
|
let rx = a:pat
|
||
|
|
||
|
if stridx(a:pat, '^') != 0
|
||
|
let rx = '^.*'.rx
|
||
|
endif
|
||
|
|
||
|
if strridx(a:pat, '$') + 1 != strlen(a:pat)
|
||
|
let rx = rx.'.*$'
|
||
|
endif
|
||
|
|
||
|
return substitute(a:expr, rx, a:sub, '')
|
||
|
endfunction
|
||
|
|
||
|
" Compatibility {{{1
|
||
|
"
|
||
|
" Functionality that is present in newer versions of Vim, but needs a
|
||
|
" compatibility layer for older ones.
|
||
|
"
|
||
|
" function! sj#Keeppatterns(command) {{{2
|
||
|
"
|
||
|
" Executes the given command, but attempts to keep search patterns as they
|
||
|
" were.
|
||
|
"
|
||
|
function! sj#Keeppatterns(command)
|
||
|
if exists(':keeppatterns')
|
||
|
exe 'keeppatterns '.a:command
|
||
|
else
|
||
|
let histnr = histnr('search')
|
||
|
|
||
|
exe a:command
|
||
|
|
||
|
if histnr != histnr('search')
|
||
|
call histdel('search', -1)
|
||
|
let @/ = histget('search', -1)
|
||
|
endif
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" Splitjoin-specific helpers {{{1
|
||
|
|
||
|
" These functions are not general-purpose, but can be used all around the
|
||
|
" plugin disregarding filetype, so they have no place in the specific autoload
|
||
|
" files.
|
||
|
|
||
|
function! sj#Align(from, to, type)
|
||
|
if a:from >= a:to
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
if exists('g:tabular_loaded')
|
||
|
call s:Tabularize(a:from, a:to, a:type)
|
||
|
elseif exists('g:loaded_AlignPlugin')
|
||
|
call s:Align(a:from, a:to, a:type)
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:Tabularize(from, to, type)
|
||
|
if a:type == 'hashrocket'
|
||
|
let pattern = '^[^=>]*\zs=>'
|
||
|
elseif a:type == 'css_declaration' || a:type == 'json_object'
|
||
|
let pattern = '^[^:]*:\s*\zs\s/l0'
|
||
|
elseif a:type == 'lua_table'
|
||
|
let pattern = '^[^=]*\zs='
|
||
|
elseif a:type == 'when_then'
|
||
|
let pattern = 'then'
|
||
|
elseif a:type == 'equals'
|
||
|
let pattern = '='
|
||
|
else
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
exe a:from.",".a:to."Tabularize/".pattern
|
||
|
endfunction
|
||
|
|
||
|
function! s:Align(from, to, type)
|
||
|
if a:type == 'hashrocket'
|
||
|
let pattern = 'l: =>'
|
||
|
elseif a:type == 'css_declaration' || a:type == 'json_object'
|
||
|
let pattern = 'lp0W0 :\s*\zs'
|
||
|
elseif a:type == 'when_then'
|
||
|
let pattern = 'l: then'
|
||
|
elseif a:type == 'equals'
|
||
|
let pattern = '='
|
||
|
else
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
exe a:from.",".a:to."Align! ".pattern
|
||
|
endfunction
|
||
|
|
||
|
" Returns a pair with the column positions of the closest opening and closing
|
||
|
" braces on the current line. The a:open and a:close parameters are the
|
||
|
" opening and closing brace characters to look for.
|
||
|
"
|
||
|
" The optional parameter is the list of syntax groups to skip while searching.
|
||
|
"
|
||
|
" If a pair is not found on the line, returns [-1, -1]
|
||
|
"
|
||
|
" Examples:
|
||
|
"
|
||
|
" let [start, end] = sj#LocateBracesOnLine('{', '}')
|
||
|
" let [start, end] = sj#LocateBracesOnLine('{', '}', ['rubyString'])
|
||
|
" let [start, end] = sj#LocateBracesOnLine('[', ']')
|
||
|
"
|
||
|
function! sj#LocateBracesOnLine(open, close, ...)
|
||
|
let [_bufnum, line, col, _off] = getpos('.')
|
||
|
let search_pattern = '\V'.a:open.'\m.*\V'.a:close
|
||
|
let current_line = line('.')
|
||
|
|
||
|
" bail early if there's obviously no match
|
||
|
if getline('.') !~ search_pattern
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
|
||
|
" optional skip parameter
|
||
|
if a:0 > 0
|
||
|
let skip = sj#SkipSyntax(a:1)
|
||
|
else
|
||
|
let skip = ''
|
||
|
endif
|
||
|
|
||
|
" try looking backwards, then forwards
|
||
|
let found = searchpair('\V'.a:open, '', '\V'.a:close, 'cb', skip, line('.'))
|
||
|
if found <= 0
|
||
|
let found = sj#SearchSkip(search_pattern, skip, '', line('.'))
|
||
|
endif
|
||
|
|
||
|
if found > 0
|
||
|
let from = col('.')
|
||
|
|
||
|
normal! %
|
||
|
if line('.') != current_line
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
|
||
|
let to = col('.')
|
||
|
|
||
|
return [from, to]
|
||
|
else
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" Returns a pair with the column positions of the closest opening and closing
|
||
|
" braces on the current line, but only if the cursor is between them.
|
||
|
"
|
||
|
" The optional parameter is the list of syntax groups to skip while searching.
|
||
|
"
|
||
|
" If a pair is not found around the cursor, returns [-1, -1]
|
||
|
"
|
||
|
" Examples:
|
||
|
"
|
||
|
" let [start, end] = sj#LocateBracesAroundCursor('{', '}')
|
||
|
" let [start, end] = sj#LocateBracesAroundCursor('{', '}', ['rubyString'])
|
||
|
" let [start, end] = sj#LocateBracesAroundCursor('[', ']')
|
||
|
"
|
||
|
function! sj#LocateBracesAroundCursor(open, close, ...)
|
||
|
let args = [a:open, a:close]
|
||
|
if a:0 > 0
|
||
|
call extend(args, a:000)
|
||
|
endif
|
||
|
|
||
|
call sj#PushCursor()
|
||
|
let [start, end] = call('sj#LocateBracesOnLine', args)
|
||
|
call sj#PopCursor()
|
||
|
|
||
|
if sj#CursorBetween(start, end)
|
||
|
return [start, end]
|
||
|
else
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
" Removes all extra whitespace on the current line. Such is often left when
|
||
|
" joining lines that have been aligned.
|
||
|
"
|
||
|
" Example:
|
||
|
"
|
||
|
" var one = { one: "two", three: "four" };
|
||
|
" " turns into:
|
||
|
" var one = { one: "two", three: "four" };
|
||
|
"
|
||
|
function! sj#CompressWhitespaceOnLine()
|
||
|
call sj#PushCursor()
|
||
|
call sj#Keeppatterns('s/\S\zs \+/ /g')
|
||
|
call sj#PopCursor()
|
||
|
endfunction
|
||
|
|
||
|
" Parses a JSON-like object and returns a list of its components
|
||
|
" (comma-separated parts).
|
||
|
"
|
||
|
" Note that a:from and a:to are the start and end of the body, not the curly
|
||
|
" braces that usually define a JSON object. This makes it possible to use the
|
||
|
" function for parsing an argument list into separate arguments, knowing their
|
||
|
" start and end.
|
||
|
"
|
||
|
" Different languages have different rules for delimiters, so it might be a
|
||
|
" better idea to write a specific parser. See autoload/sj/argparser/json.vim
|
||
|
" for inspiration.
|
||
|
"
|
||
|
function! sj#ParseJsonObjectBody(from, to)
|
||
|
" Just use js object parser
|
||
|
let parser = sj#argparser#json#Construct(a:from, a:to, getline('.'))
|
||
|
call parser.Process()
|
||
|
return parser.args
|
||
|
endfunction
|
||
|
|
||
|
" Jumps over nested brackets until it reaches the given pattern.
|
||
|
"
|
||
|
" Special handling for "<" for Rust (for now), but this only matters if it's
|
||
|
" provided in the `a:opening_brackets`
|
||
|
"
|
||
|
function! sj#JumpBracketsTill(end_pattern, brackets)
|
||
|
let opening_brackets = a:brackets['opening']
|
||
|
let closing_brackets = a:brackets['closing']
|
||
|
|
||
|
try
|
||
|
" ensure we can't go to the next line:
|
||
|
let saved_whichwrap = &whichwrap
|
||
|
set whichwrap-=l
|
||
|
" ensure we can go to the very end of the line
|
||
|
let saved_virtualedit = &virtualedit
|
||
|
set virtualedit=onemore
|
||
|
|
||
|
let remainder_of_line = s:RemainderOfLine()
|
||
|
while remainder_of_line !~ '^'.a:end_pattern
|
||
|
\ && remainder_of_line !~ '^\s*$'
|
||
|
let [opening_bracket_match, offset] = s:BracketMatch(remainder_of_line, opening_brackets)
|
||
|
let [closing_bracket_match, _] = s:BracketMatch(remainder_of_line, closing_brackets)
|
||
|
|
||
|
if opening_bracket_match < 0 && closing_bracket_match >= 0
|
||
|
let closing_bracket = closing_brackets[closing_bracket_match]
|
||
|
|
||
|
if closing_bracket == '>'
|
||
|
" an unmatched > in this context means comparison, so do nothing
|
||
|
else
|
||
|
" there's an extra closing bracket from outside the list, bail out
|
||
|
break
|
||
|
endif
|
||
|
elseif opening_bracket_match >= 0
|
||
|
" then try to jump to the closing bracket
|
||
|
let opening_bracket = opening_brackets[opening_bracket_match]
|
||
|
let closing_bracket = closing_brackets[opening_bracket_match]
|
||
|
|
||
|
" first, go to the opening bracket
|
||
|
if offset > 0
|
||
|
exe "normal! ".offset."l"
|
||
|
end
|
||
|
|
||
|
if opening_bracket == closing_bracket
|
||
|
" same bracket (quote), search for it, unless it's escaped
|
||
|
call search('\\\@<!\V'.closing_bracket, 'W', line('.'))
|
||
|
else
|
||
|
" different closing, use searchpair
|
||
|
call searchpair('\V'.opening_bracket, '', '\V'.closing_bracket, 'W', '', line('.'))
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
normal! l
|
||
|
let remainder_of_line = s:RemainderOfLine()
|
||
|
if remainder_of_line =~ '^$'
|
||
|
" we have no more content, the current column is the end of the expression
|
||
|
return col('.')
|
||
|
endif
|
||
|
endwhile
|
||
|
|
||
|
" we're past the final column of the expression, so return the previous
|
||
|
" one:
|
||
|
return col('.') - 1
|
||
|
finally
|
||
|
let &whichwrap = saved_whichwrap
|
||
|
let &virtualedit = saved_virtualedit
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
function! s:RemainderOfLine()
|
||
|
return strpart(getline('.'), col('.') - 1)
|
||
|
endfunction
|
||
|
|
||
|
function! s:BracketMatch(text, brackets)
|
||
|
let index = 0
|
||
|
let offset = match(a:text, '^\s*\zs')
|
||
|
let text = strpart(a:text, offset)
|
||
|
|
||
|
for char in split(a:brackets, '\zs')
|
||
|
if text[0] ==# char
|
||
|
return [index, offset]
|
||
|
else
|
||
|
let index += 1
|
||
|
endif
|
||
|
endfor
|
||
|
|
||
|
return [-1, 0]
|
||
|
endfunction
|