123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- "=============================================================================
- " $Id: menu.vim 520 2012-03-19 18:09:15Z 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/>
- " License: GPLv3 with exceptions
- " <URL:http://code.google.com/p/lh-vim/wiki/License>
- " Version: 3.0.0
- " Created: 13th Oct 2006
- " Last Update: $Date: 2012-03-19 19:09:15 +0100 (Mon, 19 Mar 2012) $ (07th Dec 2010)
- "------------------------------------------------------------------------
- " 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: :Toggle echoes the new value
- " v2.2.0: Support environment variables
- " Only one :Toggle command is defined.
- " v2.2.3: :Toggle can directly set the final value
- " (prefer this way of proceeding to update the menu to the new
- " value)
- " :Toggle suports auto-completion on possible values
- " v2.2.6: Toggle menus are silent, but not the actions executed
- " v3.0.0: GPLv3
- " TODO:
- " * Should the argument to :Toggle be simplified to use the variable name
- " instead ? May be a banged :Toggle! could work on the real variable
- " name, and on the real value.
- " * show all possible values in a sub menu (on demand)
- " }}}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
- " # Version {{{2
- let s:k_version = 300
- function! lh#menu#version()
- return s:k_version
- endfunction
-
- " # Debug {{{2
- let s:verbose = 0
- function! lh#menu#verbose(...)
- if a:0 > 0 | let s:verbose = a:1 | endif
- return s:verbose
- 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 = index(a:Data.values, a:value)
- " echo a:Data.variable . "[".idx."] == " . a:value
- return idx > 0 ? idx : 0 " default is first element
- endfunction
-
- " Function: s:SearchText({Data},{value}) {{{3
- " Searches for the index of {value} in {Data.values/text} list.
- " Returns -1 if not found.
- function! s:SearchText(Data, value)
- let labels_key = s:MenuKey(a:Data)
- let list = a:Data[labels_key]
- let idx = index(list, a:value)
- return idx
- 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
- if has_key(a:Data, "hook")
- let l:Action = a:Data.hook
- if type(l:Action) == type(function('tr'))
- call a:Data.hook()
- 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:SetTextValue({Data},{TextValue}) {{{3
- " Force the value of the variable to the one associated to the {TextValue}
- " The menu, and the variable are updated in consequence.
- function! s:SetTextValue(Data, text)
- " 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)
- let new_idx = s:SearchText(a:Data, a:text)
- if -1 == new_idx
- throw "toggle-menu: unsupported value for {".(a:Data.variable)."}"
- endif
- if a:Data.idx_crt_value == new_idx
- " value unchanged => abort
- return
- endif
-
- " Remove the entry from the menu
- call s:ClearMenu(a:Data.menu, old)
-
- " Cycle/increment the current value
- let a:Data.idx_crt_value = new_idx
- " 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: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.')').
- \ ' :'.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=+ -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]
- if a:0 > 0
- call s:SetTextValue(data, a:1)
- else
- call s:NextValue(data)
- endif
- endfunction
-
- function! lh#menu#_toggle_complete(ArgLead, CmdLine, CursorPos)
- let cmdline = split(a:CmdLine)
- " echomsg "cmd line: " . string(cmdline)." # ". (a:CmdLine =~ ' $')
- let nb_args = len(cmdline)
- if (a:CmdLine !~ ' $')
- let nb_args -= 1
- endif
- " echomsg "nb args: ". nb_args
- if nb_args < 2
- return join(keys(s:toggle_commands),"\n")
- elseif nb_args == 2
- let variable = cmdline[1]
- if !has_key(s:toggle_commands, variable)
- throw "toggle-menu: unknown toggable variable ".variable
- endif
- let data = s:toggle_commands[variable]
- let labels_key = s:MenuKey(data)
- " echomsg "keys: ".string(data[labels_key])
- return join(data[labels_key], "\n")
- else
- return ''
- endif
- 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 prefix == 'i' && exists('*IMAP')
- if b != 0
- call IMAP(a:binding, cmd, &ft)
- else
- call IMAP(a:binding, cmd)
- endif
- else
- 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
- 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:
|