gundo.vim/tests/bundled/lh-vim-lib/autoload/lh/menu.vim
Steve Losh 6e679d6d4f Add a unit test. This here is a grey triangle moment, folks.
--HG--
extra : rebase_source : 17ea9665bc826a9365264122781e1a7f99948b34
2010-11-10 19:52:08 -05:00

385 lines
13 KiB
VimL

"=============================================================================
" $Id: menu.vim 246 2010-09-19 22:40:58Z luc.hermitte $
" File: autoload/lh/menu.vim {{{1
" Author: Luc Hermitte <EMAIL:hermitte {at} free {dot} fr>
" <URL:http://code.google.com/p/lh-vim/>
" Version: 2.2.1
" Created: 13th Oct 2006
" Last Update: $Date: 2010-09-19 18:40:58 -0400 (Sun, 19 Sep 2010) $ (28th Aug 2007)
"------------------------------------------------------------------------
" Description:
" Defines the global function lh#menu#def_menu
" Aimed at (ft)plugin writers.
"
"------------------------------------------------------------------------
" Installation:
" Drop this file into {rtp}/autoload/lh/
" Requires Vim 7+
" History:
" v2.0.0: Moving to vim7
" v2.0.1: :ToggleXxx echoes the new value
" v2.2.0: Support environment variables
" Only one :Toggle command is defined.
" TODO:
" Should the argument to :Toggle be simplified to use the variable
" name instead ?
" }}}1
"=============================================================================
"=============================================================================
let s:cpo_save=&cpo
set cpo&vim
"------------------------------------------------------------------------
" ## Internal Variables {{{1
let s:k_Toggle_cmd = 'Toggle'
if !exists('s:toggle_commands')
let s:toggle_commands = {}
endif
"------------------------------------------------------------------------
" ## Functions {{{1
" # Debug {{{2
function! lh#menu#verbose(level)
let s:verbose = a:level
endfunction
function! s:Verbose(expr)
if exists('s:verbose') && s:verbose
echomsg a:expr
endif
endfunction
function! lh#menu#debug(expr)
return eval(a:expr)
endfunction
"------------------------------------------------------------------------
" # Common stuff {{{2
" Function: lh#menu#text({text}) {{{3
" @return a text to be used in menus where "\" and spaces have been escaped.
function! lh#menu#text(text)
return escape(a:text, '\ ')
endfunction
" # Toggling menu item {{{2
" Function: s:Fetch({Data},{key}) {{{3
" @param[in] Data Menu-item definition
" @param[in] key Table table from which the result will be fetched
" @return the current value, or text, whose index is Data.idx_crt_value.
function! s:Fetch(Data, key)
let len = len(a:Data[a:key])
if a:Data.idx_crt_value >= len | let a:Data.idx_crt_value = 0 | endif
let value = a:Data[a:key][a:Data.idx_crt_value]
return value
endfunction
" Function: s:Search({Data},{value}) {{{3
" Searches for the index of {value} in {Data.values} list. Return 0 if not
" found.
function! s:Search(Data, value)
let idx = 0
while idx != len(a:Data.values)
if a:value == a:Data.values[idx]
" echo a:Data.variable . "[".idx."] == " . a:value
return idx
endif
let idx = idx + 1
endwhile
" echo a:Data.variable . "[".-1."] == " . a:value
return 0 " default is first element
endfunction
" Function: s:Set({Data}) {{{3
" @param[in,out] Data Menu item definition
"
" Sets the global variable associated to the menu item according to the item's
" current value.
function! s:Set(Data)
let value = a:Data.values[a:Data.idx_crt_value]
let variable = a:Data.variable
if variable[0] == '$' " environment variabmes
exe "let ".variable." = ".string(value)
else
let g:{variable} = value
endif
if has_key(a:Data, "actions")
let l:Action = a:Data.actions[a:Data.idx_crt_value]
if type(l:Action) == type(function('tr'))
call l:Action()
else
exe l:Action
endif
endif
return value
endfunction
" Function: s:MenuKey({Data}) {{{3
" @return the table name from which the current value name (to dsplay in the
" menu) must be fetched.
" Priority is given to the optional "texts" table over the madatory "values" table.
function! s:MenuKey(Data)
if has_key(a:Data, "texts")
let menu_id = "texts"
else
let menu_id = "values"
endif
return menu_id
endfunction
" Function: s:NextValue({Data}) {{{3
" Change the value of the variable to the next in the list of value.
" The menu, and the variable are updated in consequence.
function! s:NextValue(Data)
" Where the texts for values must be fetched
let labels_key = s:MenuKey(a:Data)
" Fetch the old current value
let old = s:Fetch(a:Data, labels_key)
" Remove the entry from the menu
call s:ClearMenu(a:Data.menu, old)
" Cycle/increment the current value
let a:Data.idx_crt_value += 1
" Fetch it
let new = s:Fetch(a:Data,labels_key)
" Add the updated entry in the menu
call s:UpdateMenu(a:Data.menu, new, a:Data.command)
" Update the binded global variable
let value = s:Set(a:Data)
echo a:Data.variable.'='.value
endfunction
" Function: s:ClearMenu({Menu}, {text}) {{{3
" Removes a menu item
"
" @param[in] Menu.priority Priority of the new menu-item
" @param[in] Menu.name Name of the new menu-item
" @param[in] text Text of the previous value of the variable binded
function! s:ClearMenu(Menu, text)
if has('gui_running')
let name = substitute(a:Menu.name, '&', '', 'g')
let cmd = 'unmenu '.lh#menu#text(name.'<tab>('.a:text.')')
silent! exe cmd
endif
endfunction
" Function: s:UpdateMenu({Menu}, {text}, {command}) {{{3
" Adds a new menu item, with the text associated to the current value in
" braces.
"
" @param[in] Menu.priority Priority of the new menu-item
" @param[in] Menu.name Name of the new menu-item
" @param[in] text Text of the current value of the variable binded to
" the menu-item
" @param[in] command Toggle command to execute when the menu-item is selected
function! s:UpdateMenu(Menu, text, command)
if has('gui_running')
let cmd = 'nnoremenu <silent> '.a:Menu.priority.' '.
\ lh#menu#text(a:Menu.name.'<tab>('.a:text.')').
\ ' :silent '.s:k_Toggle_cmd.' '.a:command."\<cr>"
silent! exe cmd
endif
endfunction
" Function: s:SaveData({Data}) {{{3
" @param Data Menu-item definition
" Saves {Data} as s:Data{s:data_id++}. The definition will be used by
" automatically generated commands.
" @return s:data_id
let s:data_id = 0
function! s:SaveData(Data)
let s:Data{s:data_id} = a:Data
let id = s:data_id
let s:data_id += 1
return id
endfunction
" Function: lh#menu#def_toggle_item({Data}) {{{3
" @param Data.idx_crt_value
" @param Data.definitions == [ {value:, menutext: } ]
" @param Data.menu == { name:, position: }
"
" Sets a toggle-able menu-item defined by {Data}.
"
function! lh#menu#def_toggle_item(Data)
" Save the menu data as an internal script variable
let id = s:SaveData(a:Data)
" If the index of the current value hasn't been set, fetch it from the
" associated variable
if !has_key(a:Data, "idx_crt_value")
" Fetch the value of the associated variable
let value = lh#option#get(a:Data.variable, 0, 'g')
" echo a:Data.variable . " <- " . value
" Update the index of the current value
let a:Data.idx_crt_value = s:Search(a:Data, value)
endif
" Name of the auto-matically generated toggle command
let cmdName = substitute(a:Data.menu.name, '[^a-zA-Z_]', '', 'g')
" Lazy definition of the command
if 2 != exists(':'.s:k_Toggle_cmd)
exe 'command! -nargs=1 -complete=custom,lh#menu#_toggle_complete '
\ . s:k_Toggle_cmd . ' :call s:Toggle(<f-args>)'
endif
" silent exe 'command! -nargs=0 '.cmdName.' :call s:NextValue(s:Data'.id.')'
let s:toggle_commands[cmdName] = eval('s:Data'.id)
let a:Data["command"] = cmdName
" Add the menu entry according to the current value
call s:UpdateMenu(a:Data.menu, s:Fetch(a:Data, s:MenuKey(a:Data)), cmdName)
" Update the associated global variable
call s:Set(a:Data)
endfunction
"------------------------------------------------------------------------
function! s:Toggle(cmdName)
if !has_key(s:toggle_commands, a:cmdName)
throw "toggle-menu: unknown toggable variable ".a:cmdName
endif
let data = s:toggle_commands[a:cmdName]
call s:NextValue(data)
endfunction
function! lh#menu#_toggle_complete(ArgLead, CmdLine, CursorPos)
return join(keys(s:toggle_commands),"\n")
endfunction
"------------------------------------------------------------------------
" # IVN Menus {{{2
" Function: s:CTRL_O({cmd}) {{{3
" Build the command (sequence of ':ex commands') to be executed from
" INSERT-mode.
function! s:CTRL_O(cmd)
return substitute(a:cmd, '\(^\|<CR>\):', '\1\<C-O>:', 'g')
endfunction
" Function: lh#menu#is_in_visual_mode() {{{3
function! lh#menu#is_in_visual_mode()
return exists('s:is_in_visual_mode') && s:is_in_visual_mode
endfunction
" Function: lh#menu#_CMD_and_clear_v({cmd}) {{{3
" Internal function that executes the command and then clears the @v buffer
" todo: save and restore @v,
function! lh#menu#_CMD_and_clear_v(cmd)
try
let s:is_in_visual_mode = 1
exe a:cmd
finally
let @v=''
silent! unlet s:is_in_visual_mode
endtry
endfunction
" Function: s:Build_CMD({prefix},{cmd}) {{{3
" build the exact command to execute regarding the mode it is dedicated
function! s:Build_CMD(prefix, cmd)
if a:cmd[0] != ':' | return ' ' . a:cmd
endif
if a:prefix[0] == "i" | return ' ' . <SID>CTRL_O(a:cmd)
elseif a:prefix[0] == "n" | return ' ' . a:cmd
elseif a:prefix[0] == "v"
if match(a:cmd, ":VCall") == 0
return substitute(a:cmd, ':VCall', ' :call', ''). "\<cr>gV"
" gV exit select-mode if we where in it!
else
return
\ " \"vy\<C-C>:call lh#menu#_CMD_and_clear_v('" .
\ substitute(a:cmd, "<CR>$", '', '') ."')\<cr>"
endif
elseif a:prefix[0] == "c" | return " \<C-C>" . a:cmd
else | return ' ' . a:cmd
endif
endfunction
" Function: lh#menu#map_all({map_type}, [{map args}...) {{{3
" map the command to all the modes required
function! lh#menu#map_all(map_type,...)
let nore = (match(a:map_type, '[aincv]*noremap') != -1) ? "nore" : ""
let prefix = matchstr(substitute(a:map_type, nore, '', ''), '[aincv]*')
if a:1 == "<buffer>" | let i = 3 | let binding = a:1 . ' ' . a:2
else | let i = 2 | let binding = a:1
endif
let binding = '<silent> ' . binding
let cmd = a:{i}
let i += 1
while i <= a:0
let cmd .= ' ' . a:{i}
let i += 1
endwhile
let build_cmd = nore . 'map ' . binding
while strlen(prefix)
if prefix[0] == "a" | let prefix = "incv"
else
execute prefix[0] . build_cmd . <SID>Build_CMD(prefix[0],cmd)
let prefix = strpart(prefix, 1)
endif
endwhile
endfunction
" Function: lh#menu#make({prefix},{code},{text},{binding},...) {{{3
" Build the menu and map its associated binding to all the modes required
function! lh#menu#make(prefix, code, text, binding, ...)
let nore = (match(a:prefix, '[aincv]*nore') != -1) ? "nore" : ""
let prefix = matchstr(substitute(a:prefix, nore, '', ''), '[aincv]*')
let b = (a:1 == "<buffer>") ? 1 : 0
let i = b + 1
let cmd = a:{i}
let i += 1
while i <= a:0
let cmd .= ' ' . a:{i}
let i += 1
endwhile
let build_cmd = nore . "menu <silent> " . a:code . ' ' . lh#menu#text(a:text)
if strlen(a:binding) != 0
let build_cmd .= '<tab>' .
\ substitute(lh#menu#text(a:binding), '&', '\0\0', 'g')
if b != 0
call lh#menu#map_all(prefix.nore."map", ' <buffer> '.a:binding, cmd)
else
call lh#menu#map_all(prefix.nore."map", a:binding, cmd)
endif
endif
if has("gui_running")
while strlen(prefix)
execute <SID>BMenu(b).prefix[0].build_cmd.<SID>Build_CMD(prefix[0],cmd)
let prefix = strpart(prefix, 1)
endwhile
endif
endfunction
" Function: s:BMenu({b}) {{{3
" If <buffermenu.vim> is installed and the menu should be local, then the
" apropriate string is returned.
function! s:BMenu(b)
let res = (a:b && exists(':Bmenu')
\ && (1 == lh#option#get("want_buffermenu_or_global_disable", 1, "bg"))
\) ? 'B' : ''
" call confirm("BMenu(".a:b.")=".res, '&Ok', 1)
return res
endfunction
" Function: lh#menu#IVN_make(...) {{{3
function! lh#menu#IVN_make(code, text, binding, i_cmd, v_cmd, n_cmd, ...)
" nore options
let nore_i = (a:0 > 0) ? ((a:1 != 0) ? 'nore' : '') : ''
let nore_v = (a:0 > 1) ? ((a:2 != 0) ? 'nore' : '') : ''
let nore_n = (a:0 > 2) ? ((a:3 != 0) ? 'nore' : '') : ''
"
call lh#menu#make('i'.nore_i,a:code,a:text, a:binding, '<buffer>', a:i_cmd)
call lh#menu#make('v'.nore_v,a:code,a:text, a:binding, '<buffer>', a:v_cmd)
if strlen(a:n_cmd) != 0
call lh#menu#make('n'.nore_n,a:code,a:text, a:binding, '<buffer>', a:n_cmd)
endif
endfunction
"
" Functions }}}1
"------------------------------------------------------------------------
let &cpo=s:cpo_save
"=============================================================================
" vim600: set fdm=marker: