diff --git a/autoload/maktaba/keymapping.vim b/autoload/maktaba/keymapping.vim new file mode 100644 index 0000000..a8d09b3 --- /dev/null +++ b/autoload/maktaba/keymapping.vim @@ -0,0 +1,281 @@ +"" +" @dict KeyMapping +" A maktaba representation of a Vim key mapping, which is used to configure and +" unmap it from Vim. + + +if !exists('s:next_keymap_id') + let s:next_keymap_id = 0 +endif + +if !exists('s:KEYMAPS_BY_ID') + let s:KEYMAPS_BY_ID = {} +endif + + +function! s:ReserveKeyMapId() abort + let l:keymap_id = s:next_keymap_id + let s:next_keymap_id += 1 + return l:keymap_id +endfunction + + +function! s:GetMappingById(id) abort + try + return s:KEYMAPS_BY_ID[a:id] + catch /E716:/ + return 0 + endtry +endfunction + + +function! s:GetFuncCallKeystrokes(funcstr, mode) abort + if a:mode ==# 'n' + return printf(':call %s', a:funcstr) + elseif a:mode ==# 'i' + return printf(':call %s', a:funcstr) + elseif a:mode ==# 'v' + " Needs C-u to remove "'<,'>" range inserted for commands from visual mode. + " Uses "gv" at the end to re-enter visual mode. + return printf(':call %sgv', a:funcstr) + elseif a:mode ==# 's' + " Uses "gv" at the end to re-enter select mode. + return printf(':call %sgv', a:funcstr) + endif + throw maktaba#error#NotImplemented( + \ 'MapOnce not implemented for mode %s', a:mode) +endfunction + + +"" +" @dict KeyMapping +" Returns 1 if the mapping is still defined, 0 otherwise +function! maktaba#keymapping#IsMapped() dict abort + let l:foundmap = maparg(self._lhs, self._mode, 0, 1) + return !empty(l:foundmap) && l:foundmap == self._maparg +endfunction + + +"" +" @dict KeyMapping +" Unmaps the mapping in Vim. +" Returns 1 if mapping was found and unmapped, 0 if mapping was gone already. +function! maktaba#keymapping#Unmap() dict abort + if self.IsMapped() + let l:arg_prefix = self._maparg.buffer ? ' ' : '' + execute printf( + \ 'silent %sunmap %s%s', + \ self._maparg.mode, + \ l:arg_prefix, + \ self._maparg.lhs) + if has_key(s:KEYMAPS_BY_ID, self._id) + unlet s:KEYMAPS_BY_ID[self._id] + endif + return 1 + else + return 0 + endif +endfunction + + +"" +" @dict KeyMapping +" Return a copy of the spec used to issue this mapping. +function! maktaba#keymapping#GetSpec() dict abort + return copy(self._spec) +endfunction + + +let s:IsMapped = function('maktaba#keymapping#IsMapped') +let s:Unmap = function('maktaba#keymapping#Unmap') +let s:GetSpec = function('maktaba#keymapping#GetSpec') + + +"" +" Set up a key mapping in Vim, mapping key sequence {lhs} to replacement +" sequence {rhs} in the given [mode]. It is equivalent to calling: > +" :call maktaba#keymappingspec#Spec({lhs}, {rhs}, [mode]).Map() +" < +" +" See the |maktaba#keymappingspec#Spec()| and |KeyMappingSpec.Map()| functions +" for usage and behavior details. +" +" @default mode=all of 'n', 'v', and 'o' (Vim's default) +function! maktaba#keymapping#Map(lhs, rhs, ...) abort + if a:0 >= 1 + let l:spec = maktaba#keymappingspec#Spec(a:lhs, a:rhs, a:1) + else + let l:spec = maktaba#keymappingspec#Spec(a:lhs, a:rhs) + endif + return l:spec.Map() +endfunction + + +"" +" @private +" Unmap the one-shot mapping identified by {id} (an internal ID generated in the +" implementation) and mapped with @function(KeyMappingSpec.MapOnce) or +" MapOnceWithTimeout. +" Returns 1 if mapping was found and unmapped, 0 if mapping was gone already. +function! maktaba#keymapping#UnmapById(id) abort + let l:keymap = s:GetMappingById(a:id) + if l:keymap is 0 + return 0 + endif + call l:keymap.Unmap() + return 1 +endfunction + + +"" +" @private +" Performs the actions needed for a MapOnceWithTimestamp mapping, unmapping it +" by {id} if it's still mapped and conditionally mapping a simpler version of +" itself to be triggered by the upcoming LHS keystrokes (mapped if +" {timeout_start} + 'timeoutlen' hasn't elapsed). +function! maktaba#keymapping#UnwrapForIdAndTimeoutWithRhs( + \ id, timeout_start, orig_rhs) abort + let l:keymap = s:GetMappingById(a:id) + call l:keymap.Unmap() + if reltimefloat(reltime(a:timeout_start)) < &timeoutlen + " Timeout hasn't elapsed. + " Remap a version of {orig_rhs} to be invoked immediately. + let l:spec_without_timestamp_or_remap = l:keymap.GetSpec().WithRemap(0) + let l:spec_without_timestamp_or_remap._rhs = a:orig_rhs + call l:spec_without_timestamp_or_remap.MapOnce() + else + " Timeout has elapsed. + " Register nothing, so we fall back to original {rhs}. + endif +endfunction + + +"" +" @private +" Creates a skeleton @dict(KeyMapping) from {spec}. +" Internal helper only intended to be called by @function(KeyMappingSpec.Map). +function! maktaba#keymapping#PopulateFromSpec(spec) abort + return { + \ '_id': s:ReserveKeyMapId(), + \ '_spec': a:spec, + \ '_lhs': a:spec._lhs, + \ '_mode': a:spec._mode, + \ '_is_noremap': a:spec._is_noremap, + \ '_is_bufmap': a:spec._is_bufmap, + \ 'IsMapped': s:IsMapped, + \ 'Unmap': s:Unmap, + \ 'GetSpec': s:GetSpec, + \ '_DoMap': function('maktaba#keymapping#MapSelf'), + \ '_DoMapOnce': function('maktaba#keymapping#MapSelfOnce'), + \ '_DoMapOnceWithTimeout': + \ function('maktaba#keymapping#MapSelfOnceWithTimeout'), + \ } +endfunction + + +"" +" @private +" @dict KeyMapping +" Defines the key mapping in Vim via the |:map| commands for the keymap in self. +" Core internal implementation of @function(KeyMappingSpec.Map). +function! maktaba#keymapping#MapSelf() dict abort + " TODO(dbarnett): Perform a sweep for expired mapping timeouts before trying + " to register more mappings (which might conflict). + let l:spec = self._spec + let s:KEYMAPS_BY_ID[self._id] = self + execute printf('%s%smap %s %s %s', + \ l:spec._mode, + \ l:spec._is_noremap ? 'nore' : '', + \ join(l:spec._args, ' '), + \ l:spec._lhs, + \ l:spec._rhs) + let self._maparg = maparg(l:spec._lhs, self._mode, 0, 1) +endfunction + + +"" +" @private +" @dict KeyMapping +" Define a buffer-local one-shot Vim mapping from spec that will only trigger +" once and then unmap itself. +" +" @throws NotImplemented if used with `WithRemap(1)` +function! maktaba#keymapping#MapSelfOnce() dict abort + let l:spec = self._spec + if !l:spec._is_noremap + throw maktaba#error#NotImplemented( + \ "MapOnce doesn't support recursive mappings") + endif + let s:KEYMAPS_BY_ID[self._id] = self + execute printf( + \ '%snoremap %s %s %s%s', + \ self._mode, + \ join(l:spec._args, ' '), + \ l:spec._lhs, + \ s:GetFuncCallKeystrokes( + \ 'maktaba#keymapping#UnmapById(' . self._id . ')', + \ self._mode), + \ l:spec._rhs) + let self._maparg = maparg(l:spec._lhs, self._mode, 0, 1) +endfunction + + +"" +" @private +" @dict KeyMapping +" Define a short-lived Vim mapping from spec that will only trigger once and +" will also expire if 'timeoutlen' duration expires with 'timeout' setting +" active. See |KeyMappingSpec.MapOnceWithTimeout()| for details. +" +" @throws NotImplemented if used with `WithRemap(1)` +function! maktaba#keymapping#MapSelfOnceWithTimeout() dict abort + if !self._spec._is_noremap + throw maktaba#error#NotImplemented( + \ "MapOnceWithTimeout doesn't support recursive mappings") + endif + + " Handle cases for !has('reltime') and 'notimeout', which map without timeout. + if !has('reltime') + call s:plugin.logger.Info( + \ 'Vim is missing +reltime feature. ' + \ . 'MapOnceWithTimeout fell back to mapping without timeout') + call self._DoMapOnce() + return + elseif !&timeout + call self._DoMapOnce() + return + endif + " Handle case for timeoutlen=0, which "times out" immediately and skips the + " mapping entirely. Handle will always have IsMapped()=0. + if &timeoutlen == 0 + return + endif + + " This conditionally sends keystrokes by using a recursive mapping that will + " check reltime/timeout and then invoke either + " (a) a version of itself with no time check, if timeout hasn't elapsed, or + " (b) a fallback to the behavior if the mapping hadn't existed. + " The recursive wrapper always starts by unmapping itself and mapping an + " unwrapped RHS mapping, which avoids recursing indefinitely. + let l:spec = self._spec + let s:KEYMAPS_BY_ID[self._id] = self + " Escapes any special keystroke sequences (example: convert to Esc>) + " since they would be passed to map as special keysrokes instead of part of + " the arg string. + let l:escaped_rhs = substitute(l:spec._rhs, '\m<\([^>]*\)>', '\1>', 'g') + " TODO(dbarnett): Also schedule a timer_start job if +timers is available to + " sweep away expired maps after timeout expires. + execute printf( + \ '%smap %s %s %s%s', + \ self._mode, + \ join(['', ''] + l:spec._args, ' '), + \ l:spec._lhs, + \ s:GetFuncCallKeystrokes(printf( + \ 'maktaba#keymapping#UnwrapForIdAndTimeoutWithRhs(%d, %s, %s)', + \ self._id, + \ string(reltime()), + \ string(l:escaped_rhs)), + \ self._mode), + \ l:spec._rhs) + let self._maparg = maparg(l:spec._lhs, self._mode, 0, 1) +endfunction diff --git a/autoload/maktaba/keymappingspec.vim b/autoload/maktaba/keymappingspec.vim new file mode 100644 index 0000000..4882f48 --- /dev/null +++ b/autoload/maktaba/keymappingspec.vim @@ -0,0 +1,131 @@ +let s:plugin = maktaba#Maktaba() + + +"" +" @dict KeyMappingSpec +" A spec for a key mapping that can be mapped in Vim with +" @function(KeyMappingSpec.Map). + + +"" +" Create a @dict(KeyMappingSpec) that can be configured with options and then +" mapped in Vim with @function(KeyMappingSpec.Map). The eventual mapping will +" map {lhs} to {rhs} in the given [mode]. +" +" Initialized with recursive mappings disallowed by default (see |:noremap|). +" To allow them, use @function(KeyMappingSpec.WithRemap). +" +" Arguments will be configured with |map-| by default. Use the +" "overwrite" option of `.WithArgs(…, 1)` if you want to map without . +" +" @default mode=all of 'n', 'v', and 'o' (Vim's default) +function! maktaba#keymappingspec#Spec(lhs, rhs, ...) abort + let l:mode = get(a:, 1, '') + + let l:spec = { + \ '_lhs': a:lhs, + \ '_rhs': a:rhs, + \ '_mode': l:mode, + \ '_args': [''], + \ '_is_noremap': 1, + \ '_is_bufmap': 0, + \ 'WithArgs': function('maktaba#keymappingspec#WithArgs'), + \ 'WithRemap': function('maktaba#keymappingspec#WithRemap'), + \ 'Map': function('maktaba#keymappingspec#MapSelf'), + \ 'MapOnce': function('maktaba#keymappingspec#MapSelfOnce'), + \ 'MapOnceWithTimeout': + \ function('maktaba#keymappingspec#MapSelfOnceWithTimeout'), + \ } + return l:spec +endfunction + + +"" +" @dict KeyMappingSpec +" Add {args} as |map-arguments| to spec (appended to any already configured on +" spec), or overwrite instead of appending if [overwrite] is passed and is true. +" {args} is a list of strings in the literal syntax accepted by |:map|, such as +" `['', '']`. +" @default overwrite=0 +function! maktaba#keymappingspec#WithArgs(args, ...) dict abort + let l:should_overwrite = maktaba#ensure#IsBool(get(a:, 1, 0)) + let l:spec = copy(self) + if l:should_overwrite + " TODO: Test this case + let l:spec._args = a:args + else + let l:spec._args += a:args + endif + if index(a:args, '') >= 0 + let l:spec._is_bufmap = 1 + endif + return l:spec +endfunction + + +"" +" @dict KeyMappingSpec +" Configure whether spec should have nested/recursive mappings {enabled}. +" @throws WrongType +function! maktaba#keymappingspec#WithRemap(enabled) dict abort + let l:spec = copy(self) + let l:spec._is_noremap = !maktaba#ensure#IsBool(a:enabled) + return l:spec +endfunction + + +"" +" @dict KeyMappingSpec.Map +" Define a Vim mapping from spec via the |:map| commands. +function! maktaba#keymappingspec#MapSelf() dict abort + let l:keymap = maktaba#keymapping#PopulateFromSpec(self) + call l:keymap._DoMap() + return l:keymap +endfunction + + +"" +" @dict KeyMappingSpec.MapOnce +" Define a buffer-local one-shot Vim mapping from spec that will only trigger +" once and then unmap itself. +" +" Not supported for recursive mappings. +" @throws NotImplemented if used with `WithRemap(1)` +function! maktaba#keymappingspec#MapSelfOnce() dict abort + let l:keymap = maktaba#keymapping#PopulateFromSpec(self) + call l:keymap._DoMapOnce() + return l:keymap +endfunction + + +"" +" @dict KeyMappingSpec.MapOnceWithTimeout +" Define a short-lived Vim mapping from spec that will only trigger once and +" will also expire if 'timeoutlen' duration expires with 'timeout' setting +" active. +" +" This is useful to detect if the user presses a key immediately after something +" else happens, and respond with a particular behavior. +" +" It can also be used for an improved version of Vim's |map-ambiguous| behavior +" when one mapping is a prefix of another. You can create a prefix mapping that +" does one thing immediately and then a different follow-up behavior on another +" keystroke. +" +" For example, this defines a mapping that immediately unfolds one level but +" unfolds all levels if the ">" keypress is repeated: > +" nnoremap z> zr:call maktaba#keymappingspec#Spec('>', 'zR', 'n') +" \.WithArgs(['']).MapOnceWithTimeout() +" < +" +" Caveat: Unlike Vim's |map-ambiguous| behavior, this currently doesn't stop +" waiting for keypresses if another unrelated key is pressed while it's waiting. +" Caveat: For long mappings, you might notice that the timeout is currently for +" the entire mapping and not for each keystroke. If you need to work around that +" you can define a chain of single-key mappings that each map the next key in +" the sequence. +function! maktaba#keymappingspec#MapSelfOnceWithTimeout() dict abort + let l:keymap = maktaba#keymapping#PopulateFromSpec(self) + call l:keymap._DoMapOnceWithTimeout() + return l:keymap +endfunction diff --git a/doc/maktaba.txt b/doc/maktaba.txt index fee98f2..0522b49 100644 --- a/doc/maktaba.txt +++ b/doc/maktaba.txt @@ -243,6 +243,72 @@ Flag.Translate({value}) *Flag.Translate()* Returns the value that the flag will have after being set to {value} (after running {value} through all registered translators). + *maktaba.KeyMapping* +A maktaba representation of a Vim key mapping, which is used to configure and +unmap it from Vim. + +KeyMapping.IsMapped() *KeyMapping.IsMapped()* + Returns 1 if the mapping is still defined, 0 otherwise + +KeyMapping.Unmap() *KeyMapping.Unmap()* + Unmaps the mapping in Vim. Returns 1 if mapping was found and unmapped, 0 if + mapping was gone already. + +KeyMapping.GetSpec() *KeyMapping.GetSpec()* + Return a copy of the spec used to issue this mapping. + + *maktaba.KeyMappingSpec* +A spec for a key mapping that can be mapped in Vim with +|KeyMappingSpec.Map()|. + +KeyMappingSpec.WithArgs({args}, [overwrite]) *KeyMappingSpec.WithArgs()* + Add {args} as |map-arguments| to spec (appended to any already configured on + spec), or overwrite instead of appending if [overwrite] is passed and is + true. {args} is a list of strings in the literal syntax accepted by |:map|, + such as `['', '']`. + [overwrite] is 0 if omitted. + +KeyMappingSpec.WithRemap({enabled}) *KeyMappingSpec.WithRemap()* + Configure whether spec should have nested/recursive mappings {enabled}. + Throws ERROR(WrongType) + +KeyMappingSpec.Map() *KeyMappingSpec.Map()* + Define a Vim mapping from spec via the |:map| commands. + +KeyMappingSpec.MapOnce() *KeyMappingSpec.MapOnce()* + Define a buffer-local one-shot Vim mapping from spec that will only trigger + once and then unmap itself. + + Not supported for recursive mappings. + Throws ERROR(NotImplemented) if used with `WithRemap(1)` + +KeyMappingSpec.MapOnceWithTimeout() *KeyMappingSpec.MapOnceWithTimeout()* + Define a short-lived Vim mapping from spec that will only trigger once and + will also expire if 'timeoutlen' duration expires with 'timeout' setting + active. + + This is useful to detect if the user presses a key immediately after + something else happens, and respond with a particular behavior. + + It can also be used for an improved version of Vim's |map-ambiguous| + behavior when one mapping is a prefix of another. You can create a prefix + mapping that does one thing immediately and then a different follow-up + behavior on another keystroke. + + For example, this defines a mapping that immediately unfolds one level but + unfolds all levels if the ">" keypress is repeated: +> + nnoremap z> zr:call maktaba#keymappingspec#Spec('>', 'zR', 'n') + \.WithArgs(['']).MapOnceWithTimeout() +< + + Caveat: Unlike Vim's |map-ambiguous| behavior, this currently doesn't stop + waiting for keypresses if another unrelated key is pressed while it's + waiting. Caveat: For long mappings, you might notice that the timeout is + currently for the entire mapping and not for each keystroke. If you need to + work around that you can define a chain of single-key mappings that each map + the next key in the sequence. + *maktaba.Logger* Interface for a plugin to send log messages to maktaba. @@ -1177,6 +1243,32 @@ maktaba#json#python#IsDisabled() *maktaba#json#python#IsDisabled()* Returns whether the Python implementation of the maktaba#json functions is disabled, and prevents further changes. +maktaba#keymapping#Map({lhs}, {rhs}, [mode]) *maktaba#keymapping#Map()* + Set up a key mapping in Vim, mapping key sequence {lhs} to replacement + sequence {rhs} in the given [mode]. It is equivalent to calling: +> + :call maktaba#keymappingspec#Spec({lhs}, {rhs}, [mode]).Map() +< + + See the |maktaba#keymappingspec#Spec()| and |KeyMappingSpec.Map()| functions + for usage and behavior details. + + [mode] is all of 'n', 'v', and 'o' (Vim's default) if omitted. + +maktaba#keymappingspec#Spec({lhs}, {rhs}, [mode]) + *maktaba#keymappingspec#Spec()* + Create a |maktaba.KeyMappingSpec| that can be configured with options and + then mapped in Vim with |KeyMappingSpec.Map()|. The eventual mapping will + map {lhs} to {rhs} in the given [mode]. + + Initialized with recursive mappings disallowed by default (see |:noremap|). + To allow them, use |KeyMappingSpec.WithRemap()|. + + Arguments will be configured with |map-| by default. Use the + "overwrite" option of `.WithArgs(…, 1)` if you want to map without . + + [mode] is all of 'n', 'v', and 'o' (Vim's default) if omitted. + maktaba#library#Import({library}) *maktaba#library#Import()* Imports {library}. diff --git a/vroom/keymapping-advanced.vroom b/vroom/keymapping-advanced.vroom new file mode 100644 index 0000000..e1b0757 --- /dev/null +++ b/vroom/keymapping-advanced.vroom @@ -0,0 +1,62 @@ +This file explores some advanced key mapping utilities. You may want to look +through keymapping.vroom before digging into these concepts. + +At any rate, before digging in, let's install maktaba: + + :set nocompatible + :let g:maktabadir = fnamemodify($VROOMFILE, ':p:h:h') + :let g:bootstrapfile = g:maktabadir . '/bootstrap.vim' + :execute 'source' g:bootstrapfile + :let g:maktaba = maktaba#plugin#Get('maktaba') + +You might sometimes want to define a one-shot mapping that only lives for a +moment, to detect if a user presses a key immediately after something else +happens and respond with a particular behavior. Maktaba provides a +MapOnceWithTimeout helper to make this easy. + +For example, is kind of an awkward key combination to insert at the +beginning of the line. Let's make it so "ii" will do the same, but only if you +press the second "i" quickly. Otherwise, it will type a literal "i" in insert +mode like it normally would. + +What "quick" means here is based on vim's 'timeout' and 'timeoutlen' settings. +Let's configure those to something reasonable first. + + :set timeout timeoutlen=1000 + +Now let's define that mapping and try it out! + + :function ListenForAnotherIWithTimout() + | call maktaba#keymappingspec#Spec('i', '<'.'Esc>I', 'i').MapOnceWithTimeout() + |endfunction + :execute 'nnoremap i :call ListenForAnotherIWithTimout()<' . 'CR>i' + + % sometext sometext + > i[_] + sometext sometex[_]t + @clear + +One "i" entered insert mode towards the end of the line. Pressing "i" twice will +switch to inserting at the beginning of the line. + + % sometext sometext + :normal ii[_] + [_]sometext sometext + @clear + +But if you wait for a moment between keypresses or have timeoutlen=0, you won't +trigger your short-lived mapping (you'll get the original behavior of inserting +a literal "i" instead). + + :set timeoutlen=0 + % sometext sometext + > $ii[_] + sometext sometexi[_]t + + +We could have set up similar behavior by mapping "ii" in vim, but with an +annoying quirk that every time you press "i" to enter insert mode, vim won't +enter insert mode immediately, but will wait for a short delay to see if you're +going to type a second "i". With MapOnceWithTimeout, you can get the best of +both, entering insert mode immediately but still being able to keep typing and +trigger the alternate behavior. diff --git a/vroom/keymapping.vroom b/vroom/keymapping.vroom new file mode 100644 index 0000000..501ffba --- /dev/null +++ b/vroom/keymapping.vroom @@ -0,0 +1,130 @@ +Vim has a very configurable mappings system that allows users to assign custom +behavior to certain keystrokes. + +Maktaba offers some high-level helpers to plugin developers to help work with +mappings programmatically. It introduces its own "KeyMapping" type as a handle +to key mappings it defines that can be used to easily unmap or otherwise +interact with the underlying vim mapping. + +Before we explore that, let's install maktaba: + + :set nocompatible + :let g:maktabadir = fnamemodify($VROOMFILE, ':p:h:h') + :let g:bootstrapfile = g:maktabadir . '/bootstrap.vim' + :execute 'source' g:bootstrapfile + :let g:maktaba = maktaba#plugin#Get('maktaba') + + + +Now we'll create a mapping. Let's prank the user so whenever they try to enter +insert mode by pressing "i", they'll get denied. + + :let evil_mapping = maktaba#keymapping#Map('i', ':echomsg "Nope!"<'. 'CR>') + +(Please excuse the weird '<'.'CR>' workaround. The special keycodes confuse +vroom.) + +Now let's try it out! + + :normal i + ~ Nope! + +Well, that was fun, but let's unmap our mapping and restore the original +behavior. + + :call evil_mapping.Unmap() + + @messages (STRICT) + :normal i + @messages + +You can also use the handle from maktaba to verify that our prank mapping is no +longer mapped. + + :echomsg evil_mapping.IsMapped() + ~ 0 + + +That example defined a mapping in vim's default mapping modes of n, v, and o +(normal, visual, and operator-pending). We can also configure which modes should +get the mappings. Let's define an insert-mode mapping to automatically close +open parentheses. + + :let paren_mapping = maktaba#keymapping#Map('(', '()', 'i') + :echomsg paren_mapping.IsMapped() + ~ 1 + + :normal i( + () + @end + + :let paren_mapping.Unmap() + + + +Vim also supports special arguments to mappings to affect mapping behavior (see +:help :map-arguments). Let's use the argument to define a buffer-local +mapping. + +Maktaba provides a builder interface for mappings configuration more complex +than LHS, RHS, and mode. That will let us pass arguments in to the mapping we're +defining. + + :let buffer_mapping = maktaba#keymappingspec#Spec('!', '?') + |.WithArgs(['']) + |.Map() + :let mapping_info = maparg('!', '', 0, 1) + :echomsg mapping_info.buffer + ~ 1 + :call buffer_mapping.Unmap() + + +By default Maktaba uses and defines non-recursive mappings to avoid +unpleasant surprises, since programmatically-defined mappings usually wouldn't +intend to silently override existing mappings or expand recursively-nested +mappings. The spec interface allows you to override these default behaviors. + + :let recursive_nonunique_mapping = maktaba#keymappingspec#Spec('!', '?') + |.WithArgs([], 1) + |.WithRemap(1) + |.Map() + :let mapping_info = maparg('!', '', 0, 1) + :echomsg mapping_info.noremap + ~ 0 + :call recursive_nonunique_mapping.Unmap() + + + +You might in some scenarios want to set up a one-shot mapping that triggers the +next time a certain keystroke is pressed and then removes itself. Let's define a +mapping suitable for quickly pasting a line of text you just grabbed two lines +below. + + :let paste_below_keymap = maktaba#keymappingspec#Spec('J', 'jpk', 'n') + |.MapOnce() + +If you press J once, it will trigger the mapping and paste the text. + + % 1 + |2 + |3 + + :1delete + :normal J + + 2 + 3 + 1 + @end + +If you press J again after that, the mapping is already gone. Instead it will +trigger vim's built-in behavior of joining lines. + + > J + 2 + 3 1 + @end + + +See keymap-advanced.vroom for more advanced things maktaba can do with keymaps, +such as defining a one-shot keymap that expires after the keypress timeout. diff --git a/vroom/mappings.vroom b/vroom/pluginmappings.vroom similarity index 100% rename from vroom/mappings.vroom rename to vroom/pluginmappings.vroom