mirror of
https://github.com/inkarkat/vim-ingo-library.git
synced 2026-05-29 11:18:51 +02:00
abeb9494d0
And then reuse in ingo#plugin#rendered#ListJoinedOrBraceExpression().
265 lines
12 KiB
VimL
265 lines
12 KiB
VimL
" ingo/plugin/rendered.vim: Functions to interactively work with rendered items.
|
|
"
|
|
" DEPENDENCIES:
|
|
" - ingo/avoidprompt.vim autoload script
|
|
" - ingo/query.vim autoload script
|
|
" - ingo/subs/BraceCreation.vim autoload script
|
|
" - ingo/plugin/rendered/*.vim autoload scripts
|
|
"
|
|
" Copyright: (C) 2018-2022 Ingo Karkat
|
|
" The VIM LICENSE applies to this script; see ':help copyright'.
|
|
"
|
|
" Maintainer: Ingo Karkat <ingo@karkat.de>
|
|
let s:save_cpo = &cpo
|
|
set cpo&vim
|
|
|
|
function! ingo#plugin#rendered#List( what, renderer, additionalOptions, items )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Allow interactive reordering, filtering, and eventual rendering of List
|
|
" a:items (and potentially more a:additionalOptions).
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"* INPUTS:
|
|
" a:what Text describing what each element in a:items represents (e.g.
|
|
" "matches").
|
|
" a:renderer Object that implements the rendering of the a:items.
|
|
" Can supply additional rendering options presented to the user,
|
|
" via a List returned from a:renderer.options(). If such an option
|
|
" is chosen, a:renderer.handleOption(command) is invoked. Finally,
|
|
" a:renderer.render(items) is used to render the List.
|
|
" This library ships with some default renderers that can be
|
|
" copy()ed and passed; see below.
|
|
" a:additionalOptions List of additional options presented to the user. Can
|
|
" include "&" accelerators; these will be dropped in the
|
|
" command passed to a:renderer.handleOption().
|
|
" a:items List of items to be renderer.
|
|
"* RETURN VALUES:
|
|
" List of [command, renderedItems]. The command contains "Quit" if the user
|
|
" chose to cancel. If an additional option was chosen, command contains the
|
|
" option (without "&" accelerators), and renderedItems the (so far unrendered,
|
|
" but potentially filtered) List of a:items. If an ordering was chosen,
|
|
" command is empty and renderedItems contains the result.
|
|
"******************************************************************************
|
|
let l:items = a:items
|
|
let l:processOptions = a:additionalOptions + ['&Confirm each', '&Subset', '&Quit']
|
|
let l:additionalChoices = map(copy(a:additionalOptions), 'ingo#query#StripAccellerator(v:val)')
|
|
|
|
let l:save_guioptions = &guioptions
|
|
set guioptions+=c
|
|
try
|
|
while 1
|
|
redraw
|
|
let l:orderingOptions = []
|
|
let l:orderingToItems = {}
|
|
let l:orderingToString = {}
|
|
call s:AddOrdering(l:orderingOptions, l:orderingToItems, l:orderingToString, '&Original', a:renderer, l:items, l:items)
|
|
call s:AddOrdering(l:orderingOptions, l:orderingToItems, l:orderingToString, 'Re&versed', a:renderer, l:items, reverse(copy(l:items)))
|
|
call s:AddOrdering(l:orderingOptions, l:orderingToItems, l:orderingToString, '&Ascending', a:renderer, l:items, sort(copy(l:items)))
|
|
call s:AddOrdering(l:orderingOptions, l:orderingToItems, l:orderingToString, '&Descending', a:renderer, l:items, reverse(sort(copy(l:items))))
|
|
|
|
let l:orderingMessage = printf('Choose ordering for %d %s: ', len(l:items), a:what)
|
|
|
|
let l:rendererOptions = a:renderer.options()
|
|
let l:renderChoices = map(copy(l:rendererOptions), 'ingo#query#StripAccellerator(v:val)')
|
|
let l:ordering = ingo#query#ConfirmAsText(l:orderingMessage, l:orderingOptions + l:rendererOptions + l:processOptions, 1)
|
|
if empty(l:ordering) || l:ordering ==# 'Quit'
|
|
return ['Quit', '']
|
|
elseif l:ordering ==# 'Confirm each' || l:ordering == 'Subset'
|
|
if v:version < 702 | runtime autoload/ingo/plugin/rendered/*.vim | endif " The Funcref doesn't trigger the autoload in older Vim versions.
|
|
let l:ProcessingFuncref = function('ingo#plugin#rendered#' . substitute(l:ordering, '\s', '', 'g') . '#Filter')
|
|
let l:items = call(l:ProcessingFuncref, [l:items])
|
|
elseif index(l:renderChoices, l:ordering) != -1
|
|
call a:renderer.handleOption(l:ordering)
|
|
elseif index(l:additionalChoices, l:ordering) != -1
|
|
return [l:ordering, l:items]
|
|
else
|
|
break
|
|
endif
|
|
endwhile
|
|
finally
|
|
let &guioptions = l:save_guioptions
|
|
endtry
|
|
|
|
return ['', l:orderingToString[l:ordering]]
|
|
endfunction
|
|
function! s:AddOrdering( orderingOptions, orderingToItems, orderingToString, option, renderer, items, reorderedItems )
|
|
if a:reorderedItems isnot# a:items && a:reorderedItems ==# a:items ||
|
|
\ index(values(a:orderingToItems), a:reorderedItems) != -1
|
|
return
|
|
endif
|
|
|
|
let l:option = substitute(a:option, '&', '', 'g')
|
|
let l:string = call(a:renderer.render, [a:reorderedItems])
|
|
|
|
if index(values(a:orderingToString), l:string) != -1
|
|
" Different ordering yields same rendered string; skip.
|
|
return
|
|
endif
|
|
|
|
call add(a:orderingOptions, a:option)
|
|
let a:orderingToItems[l:option] = a:reorderedItems
|
|
let a:orderingToString[l:option] = l:string
|
|
|
|
call ingo#avoidprompt#EchoAsSingleLine(printf("%s:\t%s", l:option, l:string))
|
|
endfunction
|
|
|
|
|
|
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Renderer that joins the items on a self.separator, and optionally wraps the
|
|
" result in self.prefix and self.suffix.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"******************************************************************************
|
|
let g:ingo#plugin#rendered#JoinRenderer = {
|
|
\ 'prefix': '',
|
|
\ 'separator': '',
|
|
\ 'suffix': '',
|
|
\}
|
|
function! g:ingo#plugin#rendered#JoinRenderer.options() dict
|
|
return []
|
|
endfunction
|
|
function! g:ingo#plugin#rendered#JoinRenderer.render( items ) dict
|
|
return self.prefix . join(a:items, self.separator) . self.suffix
|
|
endfunction
|
|
function! g:ingo#plugin#rendered#JoinRenderer.handleOption( command ) dict
|
|
endfunction
|
|
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Renderer that extracts common substrings and turns these into a Brace
|
|
" Expression, like in Bash. The algorithm's parameters can be tweaked by the
|
|
" user. These tweaks override any defaults in self.braceOptions, which is the
|
|
" configuration passed to ingo#subs#BraceCreation#FromList().
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"******************************************************************************
|
|
let g:ingo#plugin#rendered#BraceExpressionRenderer = {
|
|
\ 'commonLengthOffset': 0,
|
|
\ 'differingLengthOffset': 0,
|
|
\ 'braceOptions': {}
|
|
\}
|
|
function! g:ingo#plugin#rendered#BraceExpressionRenderer.options() dict
|
|
let l:options = ['Longer co&mmon', 'Shor&ter common', 'Longer disti&nct', 'Sho&rter distinct']
|
|
if ! get(self.braceOptions, 'strict', 0) | call add(l:options, '&Strict') | endif
|
|
if ! get(self.braceOptions, 'short', 0) | call add(l:options, 'S&hort') | endif
|
|
if has_key(self.braceOptions, 'isIgnoreCase')
|
|
call add(l:options, (self.braceOptions.isIgnoreCase ? 'no ' : '') . '&ignore-case')
|
|
elseif get(self.braceOptions, 'short', 0)
|
|
call add(l:options, 'no &ignore-case')
|
|
else
|
|
call add(l:options, '&ignore-case')
|
|
endif
|
|
return l:options
|
|
endfunction
|
|
function! g:ingo#plugin#rendered#BraceExpressionRenderer.render( items ) dict
|
|
let l:braceOptions = copy(self.braceOptions)
|
|
let l:braceOptions.minimumCommonLength = max([1, get(self.braceOptions, 'minimumCommonLength', 1) + self.commonLengthOffset])
|
|
let l:braceOptions.minimumDifferingLength = max([0, get(self.braceOptions, 'minimumDifferingLength', 0) + self.differingLengthOffset])
|
|
|
|
return ingo#subs#BraceCreation#FromList(a:items, l:braceOptions)
|
|
endfunction
|
|
function! g:ingo#plugin#rendered#BraceExpressionRenderer.handleOption( command ) dict
|
|
if a:command ==# 'Strict'
|
|
let self.braceOptions.strict = 1
|
|
let self.braceOptions.short = 0
|
|
elseif a:command ==# 'Short'
|
|
let self.braceOptions.short = 1
|
|
let self.braceOptions.strict = 0
|
|
elseif a:command ==# 'no ignore-case'
|
|
let self.braceOptions.isIgnoreCase = 0
|
|
elseif a:command ==# 'ignore-case'
|
|
let self.braceOptions.isIgnoreCase = 1
|
|
elseif a:command ==# 'Longer common'
|
|
let self.commonLengthOffset += 1
|
|
elseif a:command ==# 'Shorter common'
|
|
let self.commonLengthOffset -= 1
|
|
elseif a:command ==# 'Longer distinct'
|
|
let self.differingLengthOffset += 1
|
|
elseif a:command ==# 'Shorter distinct'
|
|
let self.differingLengthOffset -= 1
|
|
else
|
|
throw 'ASSERT: Invalid render command: ' . string(a:command)
|
|
endif
|
|
endfunction
|
|
|
|
|
|
|
|
function! ingo#plugin#rendered#ListJoinedOrBraceExpression( what, braceOptions, additionalOptions, items )
|
|
"******************************************************************************
|
|
"* PURPOSE:
|
|
" Allow interactive reordering, filtering, and eventual rendering of List
|
|
" a:items (and potentially more a:additionalOptions) either as a joined String
|
|
" or as a Bash-like Brace Expression. The separator (and optional prefix /
|
|
" suffix) is queried first, and can be changed during the interaction. Also,
|
|
" there's the option to yank the result to a register.
|
|
"* ASSUMPTIONS / PRECONDITIONS:
|
|
" None.
|
|
"* EFFECTS / POSTCONDITIONS:
|
|
" None.
|
|
"* INPUTS:
|
|
" a:what Text describing what each element in a:items represents (e.g.
|
|
" "matches").
|
|
" a:braceOptions Dictionary of parameters for the Brace Expression creation;
|
|
" cp. ingo#subs#BraceCreation#FromList().
|
|
" a:additionalOptions List of additional options presented to the user. Can
|
|
" include "&" accelerators; these will be dropped in the
|
|
" command passed to a:renderer.handleOption().
|
|
" a:items List of items to be renderer.
|
|
"* RETURN VALUES:
|
|
" List of [command, renderedItems]. The command contains "Quit" if the user
|
|
" chose to cancel, and "Yank" if the result was yanked to a register. If an
|
|
" additional option was chosen, command contains the option (without "&"
|
|
" accelerators), and renderedItems the (so far unrendered, but potentially
|
|
" filtered) List of a:items. If an ordering was chosen, command is empty and
|
|
" renderedItems contains the result.
|
|
"******************************************************************************
|
|
echohl Question
|
|
let l:separator = input('Enter separator string (or prefix^Mseparator^Msuffix); empty for creation of Brace Expression: ')
|
|
echohl None
|
|
if empty(l:separator)
|
|
let l:renderer = copy(g:ingo#plugin#rendered#BraceExpressionRenderer)
|
|
let l:renderer.braceOptions = a:braceOptions
|
|
else
|
|
let l:renderer = copy(g:ingo#plugin#rendered#JoinRenderer)
|
|
let l:renderer.separator = l:separator
|
|
if l:renderer.separator =~# '^\%(\r\@!.\)*\r\%(\r\@!.\)*\r\%(\r\@!.\)*$'
|
|
let [l:renderer.prefix, l:renderer.separator, l:renderer.suffix] = split(l:renderer.separator, '\r', 1)
|
|
endif
|
|
endif
|
|
|
|
|
|
let [l:command, l:result] = ingo#plugin#rendered#List(a:what, l:renderer, ['Change se¶tor', '&Yank'] + a:additionalOptions, a:items)
|
|
if l:command ==# 'Quit'
|
|
return [l:command, '']
|
|
elseif l:command ==# 'Yank'
|
|
call ingo#msg#HighlightMsg('Register ([a-zA-Z0-9"*+-] <Enter> for default): ', 'Question')
|
|
let l:register = ingo#query#get#WritableRegister({'additionalValidExpr': '\r', 'invalidRegisterExpr': '_'})
|
|
if empty(l:register) | return ['Quit', ''] | endif
|
|
let l:register = (l:register ==# "\<C-m>" ? '' : l:register)
|
|
let [l:command, l:result] = ingo#plugin#rendered#List('yanked ' . a:what, l:renderer, [], l:result)
|
|
if empty(l:command)
|
|
call setreg(l:register, l:result)
|
|
endif
|
|
return ['Yank', l:result]
|
|
elseif l:command ==# 'Change separator'
|
|
return ingo#plugin#rendered#ListJoinedOrBraceExpression(a:what, a:braceOptions, a:additionalOptions, a:items)
|
|
elseif empty(l:command)
|
|
return ['', l:result]
|
|
else
|
|
throw 'ASSERT: Invalid command: ' . string(l:command)
|
|
endif
|
|
endfunction
|
|
|
|
let &cpo = s:save_cpo
|
|
unlet s:save_cpo
|
|
" vim: set ts=8 sts=4 sw=4 noexpandtab ff=unix fdm=syntax :
|