vim-config/unmanaged-vim-plug/txtfmt/ftplugin/txtfmt.vim

2673 lines
113 KiB
VimL
Raw Normal View History

2018-05-26 17:18:32 +00:00
" Txtfmt: Set of Vim plugins (syntax, ftplugin, plugin) for creating and
" displaying formatted text with Vim.
" File: This is the txtfmt ftplugin file, which contains mappings and
" functions for working with the txtfmt color/formatting tokens.
" Creation: 2004 Nov 06
" Last Change: 2008 May 10
" Maintainer: Brett Pershing Stahlman <brettstahlman@comcast.net>
" License: This file is placed in the public domain.
"echoerr "Sourcing ftplugin..."
" Let the common code know whether this is syntax file or ftplugin
let s:script_name = 'ftplugin'
" Function: s:Add_undo() <<<
" Purpose: Add the input string to the b:undo_ftplugin variable. (e.g., unmap
" commands, etc...)
" Return: none
" NOTE: This cannot be in common config, since it is called even when common
" config is being skipped for this script.
fu! s:Add_undo(s)
if !exists('b:undo_ftplugin')
let b:undo_ftplugin = ''
let add_sep = 0
elseif b:undo_ftplugin !~ '^\s*$'
let add_sep = 1
else
let add_sep = 0
endif
let b:undo_ftplugin = b:undo_ftplugin.(add_sep!=0?'|':'').a:s
endfu
" >>>
" plugin load considerations <<<
" Important Note: I intentionally skip the standard check for
" exists("b:did_ftplugin") because I want to make it easy for txtfmt user to
" load txtfmt ftplugin after another ftplugin. The standard check would
" make this more difficult.
" NOTE: Vim bug? in ftplugin.vim will cause error if you set b:undo_ftplugin
" but not b:did_ftplugin.
" Yes. It is a bug. It's been fixed, but don't count on it, since we want it
" to work for version 6.3...
let b:did_ftplugin = 1
" Don't source the ftplugin again for same buffer, as it will cause errors due
" to <unique>. Note that this is important now that I've removed the check on
" b:did_ftplugin.
if exists("b:loaded_txtfmt")
finish
endif
let b:loaded_txtfmt = 1
" Ensure that b:loaded_txtfmt is unlet whenever an ftplugin is about to be
" loaded.
" Note: Originally, I initialized b:undo_ftplugin to the empty string. The
" problem with doing so, however, is that it clobbers undo actions defined by
" a previously sourced ftplugin. Recall that txtfmt is designed to be used in
" combination with other filetypes (e.g., by using the dot-separated filetype
" name mechanism).
" Note: Don't unlet b:did_ftplugin, as standard ftplugin.vim does this
call s:Add_undo('unlet! b:loaded_txtfmt')
" >>>
" Set compatibility options <<<
" (set to Vim defaults to avoid errors with line continuation)
let s:save_cpo = &cpo
set cpo&vim
" >>>
" Common Configuration <<<
" Note: No point in having the modeline and/or global options processed by
" both the syntax and ftplugin files.
" IMPORTANT: Everything inside the "Common Configuration" fold should be
" identical between the syntax and ftplugin files. Keep in sync as changes are
" made...
if !exists('b:txtfmt_did_common_config')
" Note: An unlet of b:txtfmt_did_common_config is intentionally NOT added
" to b:undo_ftplugin. If it were unlet by b:undo_ftplugin, we would
" generally do common configuration twice, since the standard setup will
" source b:undo_ftplugin between the loading of syntax and filetype
" plugins. In order for the mechanism to work properly,
" b:txtfmt_did_common_config needs to be unlet before either syntax or
" filetype plugin is loaded. There are currently several ways to get this
" to happen: :e[dit], :b[delete], :Refresh
let b:txtfmt_did_common_config = 1
" Ensure that the code within plugin/txtfmt.vim will be executed when the
" file is sourced.
let b:txtfmt_do_common_config = 1
" TODO - Should we ensure that warning occurs for missing file?
runtime plugin/txtfmt.vim
" Make sure the common config doesn't run again
unlet b:txtfmt_do_common_config
endif
" >>>
" Note on writing to b:undo_ftplugin <<<
" Regardless of who did the common config, it is ESSENTIAL that only the
" ftplugin write to b:undo_ftplugin, as ftplugin.vim function LoadFTPlugin()
" can run between sourcing of syntax file and sourcing of ftplugin file, and
" when it does, if b:undo_ftplugin exists, it will attempt to unlet both it
" and did_ftplugin. Problem is two-fold. Whatever syntax file has written to
" undo_ftplugin is lost, and worse, did_ftplugin may not exist at that point.
" If it does not, the "unlet" without the ! will generate error! Note: Bram
" said he would fix this in distributed version, but everyone will still have
" the old for awhile...
" >>>
" Script-local functions <<<
" Function: s:SID() <<<
" Purpose: Returns the SID number of this script. (help <SNR>)
" Note: This function is taken directly from the Vim help.
fu! s:SID()
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
endfu
" >>>
" Function: s:Prep_for_single_quotes() <<<
" Description: Double all single-quotes in the input string to prepare it for
" insertion within single quotes in a string that will be exec'ed. (:help
" literal-string)
fu! s:Prep_for_single_quotes(s)
return substitute(a:s, "'", "''", 'g')
endfu
" >>>
" Function: s:Move_cursor() <<<
" Purpose: Move the cursor right or left (within the current line) by the
" specified number of characters positions. Note that character positions are
" not always the same as screen columns, and are certainly not always the same
" as byte positions.
" IMPORTANT NOTE: This function was designed to effect the cursor position
" offset specified by the '.' in a fmt/clr spec. Because of the way the
" offsets are calculated in the caller (Insert_tokstr), the raw offset may
" need to be adjusted by one character due to (for example) the impossibility
" of positioning cursor before BOL. The logic for doing this should be
" multi-byte aware. Although such logic could be implemented in this
" function's caller, there is no reason to have it in both functions. The
" logic in Insert_tokstr may be simplified if this function is made simply to
" move as far as possible on the current line in the specified direction, in
" cases in which the offset as specified is too large.
" Note: As a general rule, character positions correspond to screen columns;
" in some cases, however, a single character may occupy multiple screen
" columns (e.g. a tab). Also, there may be cases in which multiple characters
" occupy a single screen column (e.g. a base character followed by combining
" characters).
" Method: Begin moving in the specified direction a single byte at a time.
" Each time a byte position change results in a screen position change of *at
" least* one column, consider that we have moved by exactly one character.
" This strategy ensures that a tab will be considered a single character
" (unless 'virtualedit' is set), as will the combination of a base character
" and its associated combining characters. (Note, however, that such special
" cases should not generally be an issue because this function was designed to
" handle offsets in sequences of txtfmt tokens, which should not include tabs
" or combining characters.)
" Inputs:
" off - Absolute value specifies number of character positions to
" move. Sign specifies the direction:
" pos. ==> right, neg. ==> left
" Return: The difference between the number of characters positions we were
" requested to move and the number of character positions we actually moved;
" i.e., zero indicates no problem executing the requested move.
" Error: Not possible. Since this function is used only internally,
" questionable inputs are silently converted to safe ones.
fu! s:Move_cursor(off)
" Convert the signed offset that was input to a direction and magnitude.
if a:off < 0
let off = -a:off
let inc = -1
else
let off = a:off
let inc = 1
endif
" Are we moving rightward?
if inc == 1
" Determine the current mode, which determines the right-most valid
" position on the line.
let mode = mode()
" Determine the last byte on which cursor may be positioned.
" (In insert mode, cursor may be positioned just after the last byte.)
let last_col = mode =~ 'i' ? virtcol('$') : virtcol('$') - 1
"echomsg 'mode='.mode.' last_col='.last_col
endif
" Determine starting byte and screen column location.
let col = col('.')
let vcol_prev = virtcol('.')
" Keep track of how many character positions we've moved.
let moved = 0
" Loop until we reach desired location
while moved < off
" Determine next byte position. (Next byte may or may not belong to a
" different character.)
let col = col + inc
" Make sure we're not about to move too far.
if inc == 1
" Moving rightward
if col > last_col
" Can't move any further right!
break
endif
else
" Moving leftward
if col < 1
" Can't move any further left!
break
endif
endif
" Adjust cursor pos to new byte position
call cursor(0, col)
" Determine new virtual col
let vcol = virtcol('.')
" Has screen position changed since last time through?
if vcol_prev != vcol
let moved = moved + 1
endif
" Save the virtual column for next time
let vcol_prev = vcol
endwhile
" Return the number of character positions *not* moved (usually 0)
return off - moved
endfu
" >>>
" Function: s:Insert_tokstr() <<<
" Called_from: 'insert' or 'normal' mode mapping
" Purpose: Insert the fmt/clr token sequence specified by tokstr at the cursor
" location. Tokstr should be one of the following:
" -a literal (already translated) token sequence with offset prepended (just
" as it would have been returned by s:Translate_fmt_clr_spec)
" -an untranslated fmt/clr spec.
" -empty string (in which case, user will be prompted to enter a fmt/clr spec)
" After any required translations, this function ensures that the token
" sequence is inserted into the buffer (at a location determined by 'cmd'
" argument). It accomplishes the token insertion in one of two ways, depending
" upon current mode:
" 'insert': Returns the token sequence to be inserted (since the assumption is
" that this function has been invoked from an expression register
" (<C-R>=)).
" 'normal': Uses the specified 'cmd' in conjunction with normal!.
" Originally, this function was also responsible for moving the cursor
" to the inter-token position indicated by the (optional) dot replacing one of
" the commas in the fmt/clr spec. This could be done only because all
" insertions (including ones from 'insert' mode) were accomplished within this
" function using normal! That implementation, however, (specifically, the
" use of normal! from within an insert-mode mapping) caused problems with
" respect to an active count applied to the preceding enter-insert command
" (the one that started the insert from which mapping was invoked). Thus, the
" cursor adjustment functionality has been moved to a separate function
" (s:Adjust_cursor), invoked from the mapping after this function has
" returned. Since the offset governing cursor adjustment is determined within
" this function, we use static variables s:Adjust_cursor_inv_off and
" s:Adjust_cursor_modestr to communicate the information to s:Adjust_cursor.
" Inputs:
" tokstr - raw (un-translated) fmt/clr spec to insert. If empty, user
" will be prompted for fmt/clr spec list.
" cmd - [iIaAoOs] - Vim command used (conceptually, at least) to
" insert the token string. Note that in some cases, the current
" mode will determine how the tokens are actually inserted.
" literal - nonzero if the input tokstr comprises an offset followed by
" the literal fmt/clr tokens; i.e., if it needs no translation.
" (This might be the case if this routine is called from a
" user-defined mapping.)
" Note: When this flag is set, the input tokstr should be in the
" form returned by s:Translate_fmt_clr_spec.
" end_in_norm - nonzero if it is desired that we end up in normal mode after
" map completion. (Most 'insert-token' commands have an
" alternate form that causes this flag to be set)
" v:count1 If greater than 1 and mode is normal, indicates the count to
" be used with the normal mode command used to insert the
" tokens.
" Note: Used only when a:1 is not supplied.
" a:1 If supplied, represents the count to be used with the normal
" mode command that inserts the tokens. (Needed when this
" function is called from a user-map)
" Return: Depends upon mode from which we are called:
" 'insert': Returns the inserted string, since we have been called from an
" expression register (<C-R>=).
" 'normal': Returns an empty string.
" Error: Use echoerr with meaningful error message, as this function is
" generally invoked directly from mapping.
" Assumptions: Cursor position and mode unchanged from when map was invoked
" Vim trivia: col('.') and col('$') return 1 for empty line
fu! s:Insert_tokstr(tokstr, cmd, literal, end_in_norm, ...)
" Declare modifiable version of input parameter
let tokstr = a:tokstr
" Ensure that if we return without inserting any tokens (i.e. because user
" canceled the operation), s:Adjust_cursor will not attempt to do
" anything.
unlet! s:Adjust_cursor_inv_off
unlet! s:Adjust_cursor_modestr
" Assumption: If a:literal is true, input tokstr has already been
" translated and validated by s:Translate_fmt_clr_spec, and in fact, is
" exactly the string returned by that function; hence, validation will not
" be performed here.
if !a:literal
if tokstr == ''
" Prompt user for special fmt/clr spec string
let tokstr = s:Prompt_fmt_clr_spec()
" Strip surrounding whitespace, which is ignored.
" Note: Only surrounding whitespace is ignored! Whitespace not
" permitted within fmt/clr spec list.
let tokstr = substitute(tokstr, '^\s*\(.\{-}\)\s*$', '\1', 'g')
" Check for Cancel request
if tokstr == ''
" Note that nothing about position or mode has changed at this point
return ''
endif
endif
" Translate and validate fmt/clr spec
let tokstr = s:Translate_fmt_clr_list(tokstr)
if tokstr == ''
" Invalid fmt/clr sequence
echoerr "Insert_tokstr(): Invalid fmt/clr sequence entered: ".s:err_str
" Note that nothing about position or mode has changed at this point
return ''
endif
endif
" At this point, we have a translated fmt/clr spec comprising an offset
" followed by the actual fmt/clr token sequence. Extract the pieces from
" the string (internally generated - no need for validation)
let offset = substitute(tokstr, '\(\-\?[[:digit:]]\+\),\(.*\)', '\1', '')
let tokstr = substitute(tokstr, '\(\-\?[[:digit:]]\+\),\(.*\)', '\2', '')
" If user didn't specify offset, default is past inserted chars
if offset < 0
" Get length of tokstr, noting that it may contain multi-byte
" tokens.
let offset = strlen(substitute(tokstr, '.', 'x', 'g'))
endif
" Validate the command
" Get cmd string in standard form (strip surrounding whitespace)
" TODO - Perhaps this isn't necessary, since cmd is generated internally.
let cmd = substitute(a:cmd, '^\s*\(.\{-}\)\s*$', '\1', '')
if cmd !~ '^[iIaAoOs]$'
echoerr 'Insert_tokstr(): Invalid insert token cmd string: '.cmd
return ''
endif
" Validate current mode
let modestr = mode()
if modestr !~ '^[niR]$'
echoerr "Insert_tokstr(): May be called only from 'normal' and 'insert' modes."
return ''
endif
" Validation Complete!
" Build start/end mode string for convenience in testing
let modestr = modestr.(a:end_in_norm ? 'n' : 'i')
" Calculate offset from end of tokstr (inverse offset)
" a b c d e f
"0 1 2 3 4 5 6 : offset
"6 5 4 3 2 1 0 : inv_off
" We'll need to know number of *characters* (not bytes) in tokstr
let tokstrlen = strlen(substitute(tokstr, '.', 'x', 'g'))
let inv_off = tokstrlen - offset
" Make modestr and inv_off available for s:Adjust_cursor, which should run
" immediately after this function returns.
let s:Adjust_cursor_inv_off = inv_off
let s:Adjust_cursor_modestr = modestr
" Note on cursor positioning: <<<
" -The normal! insert commands will result in cursor being positioned 'ON'
" the last char inserted, regardless of which mode we start in. (For
" commands ending in insert mode, this means insert position just before
" the last char inserted.)
" TODO - Explain why - help on normal explains how the incomplete
" command (in this case enter-insert) will be terminated with <Esc>.
" -The 'stopinsert' used when starting in insert and ending in normal mode
" will cause cursor to move back one, if we were in insert mode when
" mapping was invoked.
" -undo undoes everything in a 'normal' command as a unit, so we put the
" entire command for changing text into a single normal!. Note that since
" cursor movement commands are not part of undo mechanism, any necessary
" cursor adjustments may be made after the text change.
" -When starting in normal and ending in insert mode, there are 2 cases:
" 1) Last char inserted is end of line, so we must use startinsert! to
" start insert and end up with cursor after last inserted char. (Can't
" position cursor beyond it in normal mode.)
" 2) Last char inserted is not at end of line. Could move right one, then
" use 'startinsert'.
" >>>
if modestr[0] == 'n'
" Insert the tokens with the appropriate enter-insert command and
" count. Originally, I thought that doing it this way, rather than
" using explicit cursor() and setline() calls, made the insertions
" repeatable with repeat command (.); unfortunately, however, the
" repeat command works only for normal mode commands entered ON
" COMMAND LINE! While I could go back to using setline() and cursor(),
" the logic for simulating a particular type of enter insert command
" ([iIaAoOs]) with an optional count argument is a bit simpler this
" way.
" Determine the effective count (either from optional input or
" v:count1).
if a:0 > 0
" Note: Counts are generated internally; hence, validation has
" been performed already.
let l:count = a:1
else
let l:count = v:count1
endif
" IMPORTANT NOTE: <C-R><C-R>= works differently in actual normal mode
" from the way it works when used in a normal command. Due to what I
" would call a bug, but one which Bram has no intention of fixing due
" to the complexity of the various segments of code that process the
" strings, you cannot embed character nr2char(128) in a string literal
" used in an expression register. This character is used in the gui to
" introduce a special sequence, which results in termcap expansion of
" some sort. The point is, since character 128 could be used as txtfmt
" token, need another way to insert a string that could contain it.
" Solution: use the actual string variable, rather than a string
" literal. This is actually more intuitive anyways - I just wasn't
" sure what the scope requirements were for variables used in
" expression register - it works.
exe 'normal! '.l:count.a:cmd."\<C-R>\<C-R>=l:tokstr\<CR>"
"IMPORTANT: Return empty string so that default return of 0 is not
"inserted by the "<C-R>=" used to invoke this function from insert
"mode !
return ''
else
" Return the token string to be inserted via expression register
" (<C-R>=) in insert-mode mapping.
return l:tokstr
endif
endfu
" >>>
" Function: s:Adjust_cursor() <<<
" Purpose:
" Method:
" Inputs: (indirect, via script-local vars)
" s:Adjust_cursor_inv_off
" s:Adjust_cursor_modestr
" Return: Empty string (to permit the function to be called from an expression
" register)
" Error: Not possible. Since this function is used only internally,
" questionable inputs are silently converted to safe ones.
" Note: There is at least one scenario under which we must return empty string
" immediately (i.e., without performing any adjustment): it is the case in
" which user has canceled a token insertion. In this case, neither of the
" script-local input vars will exist.
fu! s:Adjust_cursor()
if !exists('s:Adjust_cursor_modestr') || !exists('s:Adjust_cursor_inv_off')
" It appears no adjustment is required
return ''
endif
if s:Adjust_cursor_modestr == 'nn'
" Cursor is on last inserted char now. Special care must be taken
" to ensure that we don't attempt to set cursor before beginning of
" line (inv_off == tokstrlen and first char inserted is first char on
" line). Note that this insert type works as though the chars had been
" inserted, offset implemented, then insert mode exited.
" Extreme points:
" inv_off==0 --> Position on last char inserted
" inv_off==tokstrlen --> Position before first char or on first
" char if beginning of line.
" Adjust cursor in multi-byte safe manner
" Note: I am intentionally letting Move_cursor handle the special
" case of cursor positioned at beginning of line. Move_cursor is
" multi-byte safe and will not attempt to position the cursor before
" the beginning of the line, even when inv_off requests it.
call s:Move_cursor(-s:Adjust_cursor_inv_off)
elseif s:Adjust_cursor_modestr == 'ni'
" Cursor is on last inserted char now, but with an inv_off of 0,
" needs to end up 1 col position right of last inserted char after
" entering insert mode. There are 2 cases to consider...
" *** Case 1 ***
" New cursor position is past the end of the line.
" In this case, we cannot use Move_cursor to accomplish the shift
" because we are currently in normal mode, which precludes the setting
" of cursor position past EOL. (Note: Calling startinsert prior to
" Move_cursor doesn't work since, according to the Vim docs, "when
" using this command in a function or script, the insertion only
" starts after the function or script is finished.") Fortunately, we
" can call startinsert! to enter insert mode and position the cursor
" past the end of the line.
" *** Case 2 ***
" New cursor position is *NOT* past the end of the line.
" Accomplish the required right shift simply by adjusting the value of
" inv_off passed to Move_cursor. There is no way for Move_cursor
" to fail to reach requested position in this case, since even in the
" extreme case (inv_off == tokstrlen and tokens inserted at beginning
" of line), the offset of 1 ensures we won't request a position before
" BOL.
if s:Move_cursor(-s:Adjust_cursor_inv_off + 1)
" Move_cursor was unable to move past EOL.
startinsert!
else
startinsert
endif
elseif s:Adjust_cursor_modestr == 'in'
" Cursor is at col number of last inserted char + 1, which is where it
" needs to be for inv_off==0. Stopinsert will shift it 1 char left.
" Note that if inv_off==tokstrlen, cursor will end up to left of
" inserted chars unless this would put it prior to beginning of line.
call s:Move_cursor(-s:Adjust_cursor_inv_off)
exe 'stopinsert'
elseif s:Adjust_cursor_modestr == 'ii'
" Cursor is at col number of last inserted char + 1, which is where it
" needs to be for inv_off==0.
" one beyond it (for inv_off==0). Note that since we're staying in
" insert mode, positions before and after inserted chars are legal,
" even when inserted char(s) are at beginning or end of line.
call s:Move_cursor(-s:Adjust_cursor_inv_off)
endif
return ''
endfu
" >>>
" Function: s:Prompt_fmt_clr_spec() <<<
" Purpose: Prompt user for type of formatting region desired, and return
" the string entered
" How: The user will be prompted to enter a fmt/clr[/bgc] list, consisting of
" fmt/clr[/bgc] atoms separated by commas and/or dots. The format of a
" fmt/clr[/bgc] atom is described in header of Translate_fmt_clr_spec().,
" Return: The entered string
fu! s:Prompt_fmt_clr_spec()
" Prompt the user for fmt/clr spec string
" TODO: Decide whether prompt needs to distinguish between bgc and clr
call inputsave()
let str = input('Enter a fmt / clr string. (Enter to cancel): ')
call inputrestore()
return str
endfu
" >>>
" Function: s:Lookup_clr_namepat() <<<
" Purpose: Convert the input color name pattern to a color index in range
" 1..8, using the buffer-specific color definition array
" b:txtfmt_{clr|bgc}_namepat.
" Use b:txtfmt_cfg_{fg|bg}color{} arrays to determine whether the specified
" color is active.
" Return:
" Requested color valid and active: Color index between 1 and 8
" Requested color invalid: 0
" Requested color valid but inactive: -1 * {color_index}
fu! s:Lookup_clr_namepat(typ, namepat)
if a:typ == 'c'
let fg_or_bg = 'fg'
let clr_or_bgc = 'clr'
elseif a:typ == 'k'
let fg_or_bg = 'bg'
let clr_or_bgc = 'bgc'
else
echoerr "Internal error - Unknown color type `".a:typ."' passed to Lookup_clr_namepat()"
return 0
endif
" Loop over all color definitions (active and inactive), looking for one
" whose regex matches input color name
let i = 1
while i < b:txtfmt_num_colors
if a:namepat =~ b:txtfmt_{clr_or_bgc}_namepat{i}
" We found a match!
if b:txtfmt_cfg_{fg_or_bg}colormask[i - 1] != '1'
" Inactive color
return -1 * i
else
" Active color
return i
endif
endif
let i = i + 1
endwhile
" Didn't find it!
return 0
endfu
" >>>
" Function: s:Translate_fmt_clr_spec() <<<
" Purpose: Convert the input fmt/clr spec string to the corresponding fmt/clr
" token.
" How: The input fmt/clr spec string will be in one of the following formats:
" "f-"
" "c-"
" "k-" if background colors are active
" "f[u][b][i][[s][r][[c]]]" Note that s, r and c values must be disallowed for
" certain permutations of b:txtfmt_cfg_longformats
" and b:txtfmt_cfg_undercurl
" "c<clr_patt>"
" "k<clr_patt>" if background colors are active
" Note: <clr_patt> must match one of the color definitions specified by user
" (or default if user hasn't overriden).
" Note: Specification of an inactive color is considered to be an error.
" Return: One of the following:
" 1) A single fmt token
" 2) A single clr token
" 3) A single bgc token
" 4) '' - empty string if erroneous user entry
" Error: If error, function will set the script-local s:err_str
" Note: The function logic takes advantage of the fact that both strpart() and
" string offset bracket notation (s[i]) allow indices past end of string, in
" which case, they return empty strings.
fu! s:Translate_fmt_clr_spec(s)
" Declare modifiable version of input parameter
let s = a:s
" Check for empty string (all whitespace considered empty string, as it
" should have been detected as 'Cancel' request by caller).
if s =~ '^\s*$'
" Caller should validate this, but just in case
let s:err_str = "Empty fmt/clr spec"
return ''
endif
let len = strlen(s)
let ret_str = ''
if s[0] ==? 'f'
" fmt string
if s[1] == '-'
if strlen(s) == 2
" default format
let ret_str = ret_str.nr2char(b:txtfmt_fmt_first_tok)
else
" Shouldn't be anything after f-
let s:err_str = 'Unexpected chars after "f-"'
return ''
endif
else
" Not a default fmt request - remainder of string should match
" [ubi[sr[c]]]
let s = strpart(s, 1)
if s =~ '[^'.b:ubisrc_fmt{b:txtfmt_num_formats-1}.']'
" s contains illegal (but not necessarily invalid) char
if s !~ '[^ubisrc]'
" Illegal (but not invalid) char
" User has mistakenly used s, r or c with one of the
" 'short' formats or c with a version of Vim that doesn't
" support undercurl. Give an appropriate warning.
if !b:txtfmt_cfg_longformats
let s:err_str = "Only 'u', 'b' and 'i' attributes are permitted when one of the 'short' formats is in effect"
else
" Long formats are in use; hence, we can get here only
" if user attempted to use undercurl in version of Vim
" that doesn't support it.
let s:err_str = "Undercurl attribute supported only in Vim 7 or later"
endif
else
let s:err_str = 'Invalid chars in fmt spec after "f"'
endif
return ''
else
" Convert the entered chars to a binary val used to get token
" Note: Validation has already been performed; hence, we know
" that s represents both a valid and active token.
let bin_val = 0
if s=~'u' | let bin_val = bin_val + 1 | endif
if s=~'b' | let bin_val = bin_val + 2 | endif
if s=~'i' | let bin_val = bin_val + 4 | endif
if s=~'s' | let bin_val = bin_val + 8 | endif
if s=~'r' | let bin_val = bin_val + 16 | endif
if s=~'c' | let bin_val = bin_val + 32 | endif
let ret_str = ret_str.nr2char(b:txtfmt_fmt_first_tok + bin_val)
endif
endif
elseif s[0] ==? 'c' || s[0] ==? 'k'
if s[0] ==? 'k' && !b:txtfmt_cfg_bgcolor
" Oops! Background colors aren't active.
let s:err_str = "The current 'tokrange' setting does not support background colors."
\." (:help txtfmt-formats)"
return ''
endif
" clr or bgc string
if s[1] == '-'
if strlen(s) == 2
" default format
let ret_str = ret_str.nr2char(
\ s[0] ==? 'c'
\ ? b:txtfmt_clr_first_tok
\ : b:txtfmt_bgc_first_tok
\)
else
" Shouldn't be anything after c- or k-
let s:err_str = 'Unexpected chars after "'.s[0].'-"'
return ''
endif
else
" Not a default clr/bgc request - remainder of string denotes a
" color
let typ = s[0]
let s = strpart(s, 1)
" Determine which color index corresponds to color pattern
let clr_ind = s:Lookup_clr_namepat(typ, s)
if clr_ind == 0
let s:err_str = "Invalid color name pattern: '".s."'"
return ''
elseif clr_ind < 0
" TODO_BG: Make sure the help note below is still valid after
" help has been updated.
let s:err_str = "Color ".(-1 * clr_ind)." is not an active "
\.(typ ==? 'c' ? "foreground" : "background")
\." color. (:help "
\.(typ ==? 'c' ? "txtfmtFgcolormask" : "txtfmtBgcolormask").")"
return ''
endif
" IMPORTANT NOTE: clr_ind is 1-based index (1 corresponds to first
" non-default color)
let ret_str = ret_str.nr2char(
\(typ ==? 'c'
\ ? b:txtfmt_clr_first_tok
\ : b:txtfmt_bgc_first_tok)
\ + clr_ind)
endif
else
let s:err_str = 'Invalid fmt/clr spec. Must begin with '
\.(b:txtfmt_cfg_bgcolor ? '"f", "c" or "k"' : '"f" or "c"')
return ''
endif
" Return the token as a string
return ret_str
endfu
" >>>
" Function: s:Translate_fmt_clr_list() <<<
" Purpose: Translate the input comma/dot-separated list of fmt/clr/bgc spec
" atoms into a string of tokens suitable for insertion into the buffer.
" Validation is performed. Also, cursor offset into translated token string is
" determined based upon the presence of a dot (replaces comma when it appears
" between fmt/clr/bgc atoms - may also appear as first or last character in
" fmt/clr/bgc spec list).
" Input: Comma/Dot-separated list of fmt/clr/bgc spec atoms.
" Return: String of the following format:
" <offset>,<tokstr>
" Error: Return empty string and set s:err_str
" Warning: Set s:wrn_str
fu! s:Translate_fmt_clr_list(s)
" For convenience
let s = a:s
let len = strlen(s)
" Initializations <<<
let offset = -1 " -1 means not explicitly set by user
let offset_fixed = 0 " binary flag
let i = 0
let sep = '' "[,.] or '' for end-of string
let num_fld = 0 " # of atoms encountered
let tokstr = '' " built up in loop
" >>>
" Process the fmt/clr/bgc spec atom(s) in a loop
while i < len
" Find end of spec ([,.] or end of string)
" (Commas and dots not allowed except as field sep)
" NOTE: Match with '$' returns strlen (even for empty string)
let ie = match(s, '[,.]\|$', i)
" Extract field sep and text
let sep = ie<len ? s[ie] : ''
" TODO - See about consolidating the if's below...
if ie>i
let fld = strpart(s, i, ie-i)
let num_fld = num_fld+1
" Translate field if non-empty
let tok = s:Translate_fmt_clr_spec(fld)
if tok == ''
" Must have been error
let s:err_str = "Invalid fmt/clr spec: '".fld."': ".s:err_str
return ''
endif
let tokstr = tokstr.tok
" Validate the field in various ways
elseif i==ie " check null fields
let fld = ''
if ie==0 " at beginning of list ('.' permitted)
if ie==len-1
let s:err_str = "Separator with nothing to separate"
return ''
elseif len==0
" Note: This should probably be checked before now, but
" just to be complete...
let s:err_str = "Invalid empty fmt/clr spec list"
return ''
elseif sep=='.'
let offset = 0
else
let s:err_str = "Invalid leading ',' in fmt/clr spec list"
return ''
endif
else " not at beginning of list
let s:err_str = "Empty field encountered at '".strpart(s, i)
return ''
endif
endif
if ie==len-1 " validate last char in string
if num_fld==0
" NOTE: Can't actually get here...
let s:err_str = "fmt/clr spec list contains no fields"
return ''
elseif sep!='.'
let s:err_str = "Trailing comma not allowed in fmt/clr spec list"
return ''
endif
endif
" If here, field is OK unless atom is bad...
" Do offset logic
if offset==-1 && sep=='.'
let offset = num_fld
endif
" Update for next iteration
let i = ie+1
if i > len
break
endif
" OLD (implicit) logic for determining cursor offset <<<
" TODO_BG: Get rid of this...
"if tok=~b:re_fmt_any_stok || tok=~b:re_fmt_etok
" if fmt_begun
" " Fix cursor location (if not already fixed)
" if offset==0
" let offset = num_fld-1
" endif
" endif
" " If fmt start tok, set flag
" if tok!~b:re_fmt_etok
" let fmt_begun = 1
" endif
"elseif tok=~b:re_clr_any_stok || tok=~b:re_clr_etok
" if clr_begun
" " Fix cursor location (if not already fixed)
" if offset==0
" let offset = num_fld-1
" endif
" endif
" " If clr start tok, set flag
" if tok!~b:re_clr_etok
" let clr_begun = 1
" endif
"endif
" >>>
endwhile
" Return the special format string
return offset.','.tokstr
endfu
" >>>
" Function: s:Jump_to_tok() <<<
" Purpose: Jumps forward or backwards (as determined by a:dir), to the
" v:count1'th nearest token of type given by a:type ('c'=clr 'k'=bgc 'f'=fmt
" 'a'=any (clr, bgc or fmt)). If 'till' argument is nonzero, jump will
" position cursor one char position closer to starting location than the
" sought token. (This behavior is analogous to t and T normal mode commands.)
" Note: If the map that invokes this function is a visual-mode mapping,
" special logic is required to restore the visual selection prior to
" performing any cursor movement. This is because Vim's default vmap behavior
" is to remove the visual highlighting and position the cursor at the start of
" the visual area as soon as the map is invoked. For the motion mappings that
" call this function, the default behavior is not acceptable.
" Inputs:
" type 1 or 2 chars indicating the type of token sought. Format is as
" follows:
" [{target-modifier}]{target-type}
" {target-modifier} :=
" b 'begin region' tokens only
" e 'end region' tokens only
" {target-type}
" c = fg color, k = bg color, f = format,
" a = fg color, bg color, or format
" dir single char indicating direction for search (f=forward, b=back).
" Wrap behavior is determined by the 'wrapscan' option.
" till If nonzero, jump lands cursor not on the token, but just 'before'
" it (where before indicates the side closest to starting point).
" 'Till' is used because of analogy with Vim's t and T commands in
" normal mode.
" v:count1 If greater than 1, indicates the number of jumps to be performed.
" Allows a count to be used with the invoking mapping when jumping
" to the N'th token of a particular type and in a particular
" direction is desired.
" a:1 If supplied, represents the count to be used. (Needed when this
" function is called from a user-map)
" Return: Always return empty string, in case function is called from an
" expression register in insert mode.
" IMPORTANT NOTE: On the use of \%# atom -- When used in search() (i.e.,
" non-interactively), Vim appears to use lookahead to optimize when using
" \%#\@!; however, a '\%#' by itself, or followed by \@=, is NOT optimized.
" (Vim searches the entire file with wraparound before finding the cursor
" position!)
" NOTE: Ideally, if the 'till' flag is set for a backwards search, I would use
" the /e modifier with a ? search begun from normal mode to find the token and
" position the cursor on the character after it. (If token is last char on
" line, cursor would be positioned in first column of following line.)
" However, this can cause problems when tok range includes char code 128. This
" problem can be avoided if search() is used. Unfortunately, search() does not
" permit the /e modifier to be used (and \zs and/or \ze appear to be a bit
" buggy when used just after a newline - e.g., try /\n\zs/ and see what
" happens!). Thus, my strategy for finding the target location when the 'till'
" flag is set is to use search() to find the sought token, employing patterns
" that will match only if the 'till' destination location actually exists. If
" search() finds a valid destination, I then accomplish the 'till' move with a
" subsequent positioning command.
fu! s:Jump_to_tok(mode, type, dir, till, ...)
" Determine whether we jump only to active tokens
" By default, we don't.
let jtin = exists('b:txtfmtJumptoinactive')
\ ? b:txtfmtJumptoinactive
\ : exists('g:txtfmtJumptoinactive')
\ ? g:txtfmtJumptoinactive
\ : 0
" Get the search pattern
" Design Decision Needed: Decide whether to permit inactive color tokens
" to serve as target of jump. If this is desired, perhaps create special
" b:txtfmt_re_CLR_<...> and b:txtfmt_re_BGC_<...> regexes. Alternatively,
" use the b:re_no_self_esc and b:re_no_bslash_esc patterns on the
" <...>_atom regexes.
" Note: Let jumptoinactive option determine whether inactive tokens can
" serve as jump targets.
if a:type == 'c'
let re = b:txtfmt_re_{jtin ? 'CLR' : 'clr'}_tok
elseif a:type == 'bc'
let re = b:txtfmt_re_{jtin ? 'CLR' : 'clr'}_stok
elseif a:type == 'ec'
let re = b:txtfmt_re_{jtin ? 'CLR' : 'clr'}_etok
elseif a:type == 'k'
let re = b:txtfmt_re_{jtin ? 'BGC' : 'bgc'}_tok
elseif a:type == 'bk'
let re = b:txtfmt_re_{jtin ? 'BGC' : 'bgc'}_stok
elseif a:type == 'ek'
let re = b:txtfmt_re_{jtin ? 'BGC' : 'bgc'}_etok
elseif a:type == 'f'
let re = b:txtfmt_re_fmt_tok
elseif a:type == 'bf'
let re = b:txtfmt_re_fmt_stok
elseif a:type == 'ef'
let re = b:txtfmt_re_fmt_etok
elseif a:type == 'a'
let re = b:txtfmt_re_{jtin ? 'ANY' : 'any'}_tok
elseif a:type == 'ba'
let re = b:txtfmt_re_{jtin ? 'ANY' : 'any'}_stok
elseif a:type == 'ea'
let re = b:txtfmt_re_{jtin ? 'ANY' : 'any'}_etok
else
" Error - shouldn't ever get here - just return
return ''
endif
" Important Note: If mode is visual, Vim has already removed the visual
" highlighting and positioned the cursor at the start of the visual
" region. Since this is a motion mapping, we need to undo this; i.e.,
" restore the visual highlighting and put the cursor at the correct
" end/corner of the visual region, allowing for the fact that any number
" of "o" and or "O" commands may have been executed to bounce the cursor
" between ends/corners... Normal mode gv fits the bill.
" Important Note: When a visual mode mapping invokes this function, Vim
" has already changed mode to normal before we get here. Thus, we must use
" the mode string passed from the mapping to determine whether we need to
" restore the visual selection. Since we're using gv, it doesn't matter
" which visual sub-mode was in effect.
if a:mode == 'v'
normal! gv
endif
" Get the search options
if a:dir == 'b'
" Leave wrap option alone so that 'wrapscan' will be honored
let opt = 'b'
if a:till
" NOTE: The \n\_. handles the following 2 cases:
" 1) Sought token is at end of line followed by non-empty line
" 2) Sought token is at end of line followed by empty line
" NOTE: The \%#\@! ensures that if we're sitting on a character
" after the the target token type, we don't match the token just
" before it. (Otherwise we'd get stuck when trying to do multiple
" successive backwards jumps.)
let re = re.'\%(\n\%#\@!\_.\|\%#\@!.\)\@='
endif
elseif a:dir == 'f'
" Leave wrap option alone so that 'wrapscan' will be honored
let opt = ''
if a:till
" The following pattern will position us on the buffer position
" one char prior to the sought token, even in case where the token
" is at beginning of a line preceded by blank line.
" NOTE: landing on a \n puts cursor at end of line ended by the
" newline.
" NOTE: \@= is necessary when cpo-c is set to avoid skipping every
" other token when there are multiple consecutive tokens of same
" type.
let re = '\_.'.re.'\@='
endif
else
" Error - Should never get here - just return
return ''
endif
" Get the count, which is either supplied explicitly as optional extra
" arg, or is obtained from v:count1
if a:0 > 0
" Note: Counts are generated internally; hence, validation has
" been performed already.
let l:count = a:1
else
let l:count = v:count1
endif
" In a loop count, perform the search()
let i = 0
while i < l:count
" Note: If search fails, cursor will not be moved.
" IMPORTANT NOTE: Simplest thing would be to use normal search command
" here, but that gives problems if tok range includes 128!
let l2 = search(re, opt)
" Did we find the sought token?
if l2 > 0
" We're on found tok
if a:till
" NOTE: 2 cases:
" 1) Backward search - we're on token, but we need to be at
" position just past it (and search() assures us the position
" exists.
" 2) Forward search - search() got us to correct position (for
" both 'till' and non-'till' cases.
if a:dir == 'b'
" Backward search
" IMPORTANT NOTE: Original implementation used col() and
" cursor(), which are *NOT* multi-byte safe!
" Use virtcol and special search instead.
" Note: Vim documentation implies that the following holds
" true when cursor is positioned on the last character of
" the line: virtcol('.') == virtcol('$') - 1
let c2 = virtcol('.')
if c2 != virtcol('$') - 1
" Not last char on line
call search('\%'.(c2 + 1).'v', '')
else
" Last char on line - move to start of next line
" Note: cursor() can handle col of 1 even for empty
" line. Also, it's mult-byte safe.
call cursor(l2 + 1, 1)
endif
endif
endif
else
" No reason to keep looping...
break
endif
let i = i + 1
endwhile
return ''
endfu
" >>>
" Function: s:Mapwarn_check() <<<
" Purpose: Determine whether the user has already been warned about the
" mapping ambiguity/conflict indicated by input arguments, and return nonzero
" if so. Additionally, if the 'add' parameter is true, record the input
" conflict/ambiguity in the data structures maintaining such information so
" that the function will return true for it next time.
" Inputs:
" lhs - lhs of the new mapping
" rhs - rhs of the old (existing) mapping
" mode - single character indicating the mode of the mapping (e.g. n=normal,
" v=visual, i=insert, o=operator-pending, etc...)
" add - flag indicating whether the conflict indicated by lhs, rhs and mode
" should be added to the data structures searched by this function
fu! s:Mapwarn_check(lhs, rhs, mode, add)
let found = 0
let i = 0
if exists('g:txtfmt_mapwarn_cnt')
while i < g:txtfmt_mapwarn_cnt
if a:lhs == g:txtfmt_mapwarn_lhs{i} &&
\ a:rhs == g:txtfmt_mapwarn_rhs{i} &&
\ a:mode == g:txtfmt_mapwarn_mode{i}
let found = 1
break
endif
let i = i + 1
endwhile
endif
if !found && a:add
" Make sure g:txtfmt_mapwarn_cnt is self-starting
if !exists('g:txtfmt_mapwarn_cnt')
let g:txtfmt_mapwarn_cnt = 0
endif
" Add a new conflict/ambiguity to the arrays
let g:txtfmt_mapwarn_lhs{g:txtfmt_mapwarn_cnt} = a:lhs
let g:txtfmt_mapwarn_rhs{g:txtfmt_mapwarn_cnt} = a:rhs
let g:txtfmt_mapwarn_mode{g:txtfmt_mapwarn_cnt} = a:mode
let g:txtfmt_mapwarn_cnt = g:txtfmt_mapwarn_cnt + 1
endif
" Indicate whether input conflict/ambiguity was found
return found
endfu
" >>>
" Function: s:Undef_map() <<<
" Purpose: Creates an undo action for the map whose lhs, rhs and unmap_cmd are
" input, and adds the undo action to b:undo_ftplugin.
" Inputs:
" mode - single char, used as input to maparg, mapcheck, etc...
" lhs - string representing the lhs of the map to be undone
" rhs - string representing the rhs of the map to be undone.
" Assumptions:
" -All maps to be undone are buffer-local.
" -All occurrences of '<SID>[_a-zA-Z0-9]' in the rhs of a mapping defined by
" this plugin represent a call to a script-local function.
" Note: rhs is required so that we can be sure to delete *only* maps created
" by this plugin. (Consider that user could either intentionally or
" inadvertently override one of the txtfmt maps with a completely unrelated
" map after this plugin is loaded. For this reason, we cannot (or should not)
" blindly delete lhs.)
fu! s:Undef_map(lhs, rhs, mode)
" Determine the unmap command to be used.
if a:mode=='n'
let unmap_cmd = 'nunmap'
elseif a:mode=='i'
let unmap_cmd = 'iunmap'
elseif a:mode=='o'
let unmap_cmd = 'ounmap'
elseif a:mode=='v'
let unmap_cmd = 'vunmap'
else
echoerr 'Internal error - unsupported mapmode passed to Undef_map()'
return 1
endif
" Create the undo action, taking special care to avoid deleting a map with
" the same lhs, created by user after the sourcing of this plugin.
" Note: Prep_for_single_quotes ensures that single quotes contained in lhs
" or rhs are properly escaped before being wrapped in the single-quoted
" string that will be parsed when b:undo_ftplugin is exec'ed.
" Note: Be sure not to add whitespace between the lhs of the map being
" unmapped and the subsequent '|' as this will result in nonexistent
" mapping error.
" Note: When the maparg() is executed, it will return function names of
" the form '<SNR>{number}_func' rather than '<SID>func'. Thus, to ensure
" that the delayed comparison works properly, I need to convert a:rhs to
" the <SNR>{number}_ form now.
let rhs = substitute(a:rhs, '<SID>\ze[_a-zA-Z0-9]',
\'\= "<SNR>" . s:SID() . "_"', 'g')
call s:Add_undo("if maparg('".s:Prep_for_single_quotes(a:lhs)
\."', '".a:mode."') == '".s:Prep_for_single_quotes(rhs)."' | "
\.unmap_cmd." <buffer> ".a:lhs."| endif")
endfu
" >>>
" Function: s:Def_map() <<<
" Purpose: Define both the level 1 and level 2 map as appropriate.
" Inputs:
" mode - single char, used as input to maparg, mapcheck, etc...
" lhs1 - lhs of first-level map
" lhs2 - rhs of first-level map, lhs of second-level map
" rhs2 - rhs of second-level map
" How: Consider whether user already has a map to level 2 (which should take
" precedence over maplevel 1). Also, make sure the map from level 2, if it
" exists, is not incorrect, etc...
" Note: Cause b:undo_ftplugin to be updated so that whatever mappings are made
" by us will be unmapped when ftplugin is unloaded.
" Return:
" 0 - success
" nonzero - error
" NOTE: Function will echoerr to user
fu! s:Def_map(mode, lhs1, lhs2, rhs2)
" TODO - Perhaps eventually support operator mode if needed
if a:mode=='n'
let cmd1 = 'nmap'
let cmd2 = 'nnoremap'
elseif a:mode=='i'
let cmd1 = 'imap'
let cmd2 = 'inoremap'
elseif a:mode=='o'
let cmd1 = 'omap'
let cmd2 = 'onoremap'
elseif a:mode=='v'
let cmd1 = 'vmap'
let cmd2 = 'vnoremap'
else
echoerr 'Internal error - unsupported mapmode passed to Def_map()'
return 1
endif
" Do first map level <<<
if !hasmapto(a:lhs2, a:mode)
" User hasn't overridden the default level 1 mapping
" Make sure there's no conflict or ambiguity between an existing map
" and the default one we plan to add...
let oldarg = maparg(a:lhs1, a:mode)
let oldchk = mapcheck(a:lhs1, a:mode)
" Check for conflicts and ambiguities, decoding applicable portions of
" mapwarn option character flag string into more immediately useful
" variables, to avoid messy ternaries in the subsequent logic.
" Note: Create only the variables that will be used.
if oldarg != ''
" Map conflict
let l:problem = 'c'
if b:txtfmt_cfg_mapwarn =~ 'M'
let l:msg_or_err = 'm'
elseif b:txtfmt_cfg_mapwarn =~ 'E'
let l:msg_or_err = 'e'
endif
if exists('l:msg_or_err')
let l:once_only = b:txtfmt_cfg_mapwarn =~ 'O'
endif
let l:create = b:txtfmt_cfg_mapwarn =~ 'C'
let l:old_rhs = oldarg
elseif oldchk != ''
" Map ambiguity
let l:problem = 'a'
if b:txtfmt_cfg_mapwarn =~ 'm'
let l:msg_or_err = 'm'
elseif b:txtfmt_cfg_mapwarn =~ 'e'
let l:msg_or_err = 'e'
endif
if exists('l:msg_or_err')
let l:once_only = b:txtfmt_cfg_mapwarn =~ 'o'
endif
let l:create = b:txtfmt_cfg_mapwarn =~ 'c'
let l:old_rhs = oldchk
endif
if exists('l:problem')
" There's an ambiguity or conflict
if exists('l:msg_or_err')
" We need to warn unless warning is precluded by 'once-only'
" mechanism
if !l:once_only || !s:Mapwarn_check(a:lhs1, l:old_rhs, a:mode, l:once_only)
let l:warnstr = 'Level 1 map '
\.(l:problem == 'a' ? 'ambiguity:' : 'conflict: ')
\.a:lhs1.' already mapped to '.l:old_rhs
if l:msg_or_err == 'm'
echomsg l:warnstr
else
echoerr l:warnstr
endif
endif
endif
endif
" Do the map for buffer unless map creation is precluded by conflict
" or ambiguity in absence of the 'create' flag.
" Note: Do not use <unique> attribute, since that would cause Vim to
" display error, due to the original mapping.
if !exists('l:problem') || l:create
exe cmd1.' <buffer> '.a:lhs1.' '.a:lhs2
" Create undo action for the map just created
call s:Undef_map(a:lhs1, a:lhs2, a:mode)
endif
else
"echomsg "Skipping 1st level"
endif
" >>>
" Do second map level <<<
" Assumption: Second-level mappings have long <Scriptname><...> names,
" preceded by <Plug>. It is safe to assume user hasn't mapped one to
" something else...
exe cmd2.' <silent> <buffer> '.a:lhs2.' '.a:rhs2
" Create undo action for the map just created
call s:Undef_map(a:lhs2, a:rhs2, a:mode)
" >>>
" Success
return 0
endfu
" >>>
" Function: s:MakeString() <<<
" Purpose: Build and return a string by concatenating a base string some
" number of times to itself.
" Inputs:
" str -base string, which will be concatenated to itself
" len -# of occurrences of 'str' to put in the return string
" Return: The generated string
fu! s:MakeString(str, len)
let s = ''
let i = 0
while i < a:len
let s = s.a:str
let i = i + 1
endwhile
return s
endfu
" >>>
" Function: s:ShowTokenMap() <<<
" Purpose: Echo to user a table showing the current use of all tokens in the
" range of fmt/clr tokens.
" How: Use echo, as this is intended as a temporary showing for informational
" purposes only. Highlighting of column headers is accomplished via echohl
" Format: Should be something like the sample table shown below...
" Note: char-nr should use the number format indicated by
" b:txtfmt_cfg_starttok_display.
" Note: For inactive colors, an asterisk will be prepended to char-nr, and
" '(inactive)' will be appended to the description. In order to keep the
" numbers aligned properly, active colors will have a space prepended to the
" char-nr.
" TODO: Decide whether it's necessary to wrap inactive char-nr's in parens. If
" not, get rid of it.
"=== [FG] COLORS ===
"char-nr description clr-pattern clr-def
"180 no color -
"181 Color0 ^\\%(k\\|bla\\%[ck]\\)$,c:Black,g:#000000 #000000
"182 Color1 ^blu\\%[e]$,c:DarkBlue,g:#0000FF #0000FF
"183 Color2 ^g\\%[reen]$,c:DarkGreen,g:#00FF00 #00FF00
".
".
"=== FORMAT ===
"char-nr description spec
"189 no format -
"190 italic i
"191 bold b
"192 bold,italic bi
".
".
".
".
" Important Note: The subsequent lines will be output if and only if
" background colors are enabled.
"=== BG COLORS ===
"char-nr description clr-pattern clr-def
" 197 no color -
"*198 Color0 (inactive) ^\\%(k\\|bla\\%[ck]\\)$,c:Black,g:#000000 #000000
" 199 Color1 ^blu\\%[e]$,c:DarkBlue,g:#0000FF #0000FF
" 200 Color2 ^g\\%[reen]$,c:DarkGreen,g:#00FF00 #00FF00
" .
" .
fu! s:ShowTokenMap()
" Loop 2 times - first time is just to calculate column widths
let cw1 = 0 | let cw2 = 0 | let cw3 = 0 | let cw4 = 0
" Define an array, indexed by fgbg_idx, which may be used to build fg/bg
" specific var names.
let clr_or_bgc{0} = 'clr'
let clr_or_bgc{1} = 'bgc'
" Initialize the vars that will accumulate table text
let fmt_header = '' | let fmt_lines = ''
let clr_header = '' | let clr_lines = ''
let bgc_header = '' | let bgc_lines = ''
" Determine number format to use for char-nr column
let use_hex = strpart(b:txtfmt_cfg_starttok_display, 0, 2) == '0x'
let i = 0
while i < 2
" Loop over all format lines (1 hdr and b:txtfmt_num_formats-1 fmt)
let iFmt = -1 " start with header line
while iFmt < b:txtfmt_num_formats
let line = '' " Initialize text for current line
" Column 1
if iFmt == -1
let col1_text = ' char-nr'
else
let col1_text = b:txtfmt_fmt_first_tok + iFmt
if use_hex
" Convert to hex
let col1_text = TxtfmtUtil_num_to_hex_str(col1_text)
endif
" Prepend space for alignment
let col1_text = ' ' . col1_text
endif
if i == 0
" Calculate col width
if strlen(col1_text) > cw1
let cw1 = strlen(col1_text)
endif
else
" Output line
let line = line.(col1_text.s:MakeString(' ', cw1 + 2 - strlen(col1_text)))
endif
" Column 2
if iFmt == -1
let col2_text = 'description'
elseif iFmt == 0
let col2_text = 'no format'
else
let col2_text = b:txtfmt_fmt{iFmt}
endif
if i == 0
" Calculate col width
if strlen(col2_text) > cw2
let cw2 = strlen(col2_text)
endif
else
" Output line
let line = line.(col2_text.s:MakeString(' ', cw2 + 2 - strlen(col2_text)))
endif
" Column 3
if iFmt == -1
let col3_text = 'fmt-spec'
elseif iFmt == 0
let col3_text = '-'
else
let col3_text = b:ubisrc_fmt{iFmt}
endif
if i == 0
" Calculate col width
if strlen(col3_text) > cw3
let cw3 = strlen(col3_text)
endif
else
" Output line
let line = line.(col3_text.s:MakeString(' ', cw3 + 2 - strlen(col3_text)))
endif
" Accumulate line just built into the list of lines
if i == 1
if iFmt == -1
" Store header line separately so that echohl can be used
let fmt_header = line
else
" Regular row in table (non-header)
let fmt_lines = fmt_lines.(iFmt==0?'':"\<NL>").line
endif
endif
let iFmt = iFmt + 1
endwhile
" Loop over fg colors and (if necessary) bg colors
let fgbg_idx = 0
while fgbg_idx < (b:txtfmt_cfg_bgcolor ? 2 : 1)
if fgbg_idx == 0
let first_tok = b:txtfmt_clr_first_tok
let colormask = b:txtfmt_cfg_fgcolormask
else
let first_tok = b:txtfmt_bgc_first_tok
let colormask = b:txtfmt_cfg_bgcolormask
endif
" Loop over all color tokens (even inactive ones)
" Index note: In this loop, index 0 refers to 'no color', while index
" 1 refers to txtfmtColor{1} (default rgb=0x000000).
let iClr = -1
while iClr < b:txtfmt_num_colors
let line = '' " Initialize text for current line
" Column 1
if iClr == -1
let col1_text = ' char-nr'
else
if iClr >= 0
let col1_text = (first_tok + iClr)
if use_hex
" Convert to hex
let col1_text = TxtfmtUtil_num_to_hex_str(col1_text)
endif
" If color is inactive, prepend char-nr with asterisk
if iClr > 0 && strpart(colormask, iClr - 1, 1) != '1'
" This color is inactive
let col1_text = '*' . col1_text
else
" Prepend space for alignment
let col1_text = ' ' . col1_text
endif
endif
endif
if i == 0
" Calculate col width
if strlen(col1_text) > cw1
let cw1 = strlen(col1_text)
endif
else
" Output line
let line = line.(col1_text.s:MakeString(' ', cw1 + 2 - strlen(col1_text)))
endif
" Column 2
if iClr == -1
let col2_text = 'description'
elseif iClr == 0
let col2_text = 'no color'
else
let col2_text = 'Color'.iClr
if strpart(colormask, iClr - 1, 1) != '1'
let col2_text = col2_text . ' (inactive)'
endif
endif
if i == 0
" Calculate col width
if strlen(col2_text) > cw2
let cw2 = strlen(col2_text)
endif
else
" Output line
let line = line.(col2_text.s:MakeString(' ', cw2 + 2 - strlen(col2_text)))
endif
" Column 3
if iClr == -1
let col3_text = 'clr-pattern'
elseif iClr == 0
let col3_text = '-'
else
let col3_text = b:txtfmt_{clr_or_bgc{fgbg_idx}}_namepat{iClr}
endif
if i == 0
" Calculate col width
if strlen(col3_text) > cw3
let cw3 = strlen(col3_text)
endif
else
" Output line
let line = line.(col3_text.s:MakeString(' ', cw3 + 2 - strlen(col3_text)))
endif
" Column 4
if iClr == -1
let col4_text = 'clr-def'
elseif iClr == 0
let col4_text = 'N.A.'
else
let col4_text = b:txtfmt_{clr_or_bgc{fgbg_idx}}{iClr}
endif
if i == 0
" Calculate col width
if strlen(col4_text) > cw4
let cw4 = strlen(col4_text)
endif
else
" Output line
let line = line.(col4_text.s:MakeString(' ', cw4 + 2 - strlen(col4_text)))
endif
" Accumulate line just built into the list of lines
if i == 1
if iClr == -1
" Store header line separately so that echohl can be used
if fgbg_idx == 0
let clr_header = line
else
let bgc_header = line
endif
else
" Regular row in table (non-header)
if fgbg_idx == 0
let clr_lines = clr_lines.(iClr==0?'':"\<NL>").line
else
let bgc_lines = bgc_lines.(iClr==0?'':"\<NL>").line
endif
endif
endif
let iClr = iClr + 1
endwhile
let fgbg_idx = fgbg_idx + 1
endwhile
let i = i + 1
endwhile
echohl Title
echo b:txtfmt_cfg_bgcolor ? ' === FG COLORS ===' : ' === COLORS ==='
echo clr_header
echohl None
echo clr_lines
echohl Title
echo ' === FORMAT ==='
echo fmt_header
echohl None
echo fmt_lines
" If bg colors are not active, we're done
if b:txtfmt_cfg_bgcolor
echohl Title
echo ' === BG COLORS ==='
echo bgc_header
echohl None
echo bgc_lines
endif
endfu
" >>>
" Function: s:MoveStartTok() <<<
" IMPORTANT NOTE: Special care must be taken when defining this function, as
" it invokes :Refresh command, which causes the script to be re-sourced. This
" leads to E127 'Cannot redefine function' when fu[!] is encountered, since
" the function is in the process of executing.
if !exists('*s:MoveStartTok')
fu! s:MoveStartTok(moveto, ...)
if a:0
" Validate and process optional version value
if a:0 != 1
echoerr 'Incorrect # of arguments supplied to :MoveStartTok (1 or 2 expected)'
return
elseif (0 + a:1) =~ '^[1-9][0-9]\{2}$'
" Use version supplied by user
let old_ver = a:1
else
echoerr a:1.' is not a valid Vim version number. Should be same format as v:version'
return
endif
else
" Assume current version
let old_ver = v:version
endif
" Validate the new starttok
if a:moveto !~ '^\s*'.b:txtfmt_re_number_atom.'\s*$'
echoerr "Invalid 'starttok' value supplied: `".a:moveto."'"
return
endif
" Get current settings from buffer-local vars
" Assumption: This function can be invoked only from an active txtfmt
" buffer
let old_starttok = b:txtfmt_cfg_starttok
" Determine new settings
let new_starttok = a:moveto
" Determine amount of shift (signed value)
let l:offset = new_starttok - old_starttok
" Before proceeding, cache 'effective' values for bgcolor, longformats and
" undercurl. Note that 'effective' values are those that would be in
" effect if current Vim version were old_ver. Note that effective
" undercurl may differ from b:txtfmt_cfg_undercurl.
let bgcolor = b:txtfmt_cfg_bgcolor
let longformats = b:txtfmt_cfg_longformats
if old_ver != v:version
" Effective undercurl could differ from b:txtfmt_cfg_undercurl
if b:txtfmt_cfg_undercurlpref && old_ver >= b:txtfmt_const_vimver_undercurl
" Undercurl desired and supported
let undercurl = 1
else
" Undercurl either not desired or not supported
let undercurl = 0
endif
else
let undercurl = b:txtfmt_cfg_undercurl
endif
" Set a flag that indicates whether we will be reserving space for long
" formats before the start of bgc range. Note that this value can be true
" even if longformats is false. Also note that its value is N/A if
" bgcolors are disabled.
let lf_reserved = bgcolor && (longformats || !b:txtfmt_cfg_pack)
" Determine size of the entire range
let rangelen =
\ b:txtfmt_const_tokrange_size_{bgcolor}{lf_reserved}{lf_reserved}
" Perform upper-bound check on new range
if !(new_starttok + rangelen - 1 <=
\ b:txtfmt_const_tokrange_limit_{b:txtfmt_cfg_enc_class})
" Invalid destination for move!
echoerr "Starttok value of `".new_starttok."' causes upper bound for encoding `"
\.b:txtfmt_cfg_enc."' to be exceeded"
return
endif
" If here, move is legal.
" Record whether buffer is modified before we start modifying it. This
" information is used by modeline processing to determine whether save is
" required.
let b:txtfmt_ml_save_modified = &modified
" Build 2 character class interiors (i.e., without the [ ]):
" 1) all chars that are tokens under old range
" 2) all chars that are tokens under new range
" Begin the first range, which begins with fg color and ends either with
" formats (no bg colors or discontinuity between formats and bg colors) or
" bg colors.
" Note: The end of the first range is determined independently of
" lf_reserved, as the range includes only tokens actually used.
let re_old_tokrange = nr2char(old_starttok).'-'
let re_new_tokrange = nr2char(new_starttok).'-'
if !bgcolor || !(longformats && undercurl)
" End first range after format tokens
" Calculate length of range
let end_offset = b:txtfmt_const_tokrange_size_{0}{longformats}{undercurl} - 1
" Close the range
let re_old_tokrange = re_old_tokrange.nr2char(old_starttok + end_offset)
let re_new_tokrange = re_new_tokrange.nr2char(new_starttok + end_offset)
" If bgcolor is enabled, start a new range so that logic after this if
" block needn't know or care whether it was entered
if bgcolor
" Determine offset to start of bgc range
let start_offset = b:txtfmt_const_tokrange_size_{0}{lf_reserved}{lf_reserved}
let re_old_tokrange = re_old_tokrange.nr2char(old_starttok + start_offset).'-'
let re_new_tokrange = re_new_tokrange.nr2char(new_starttok + start_offset).'-'
endif
endif
" If bgcolor is enabled, need to close the first or second range. (If no
" bgcolor, first and only range has already been closed.)
if bgcolor
let end_offset = b:txtfmt_const_tokrange_size_{1}{lf_reserved}{lf_reserved} - 1
let re_old_tokrange = re_old_tokrange.nr2char(old_starttok + end_offset)
let re_new_tokrange = re_new_tokrange.nr2char(new_starttok + end_offset)
endif
" STEP 1: (If and only if escaping is permitted)
" Before translating any tokens, need to escape characters that are not
" currently tokens, but will be after the move. Escapes, if applicable,
" must be taken into account.
" Note: Also, need to escape any escape chars that would be considered escaping
" or escaped chars after the move. E.g. (if esc=bslash)
" <Bslash><Tok> should become <Bslash><Bslash><Bslash><Tok> to ensure that
" the effective sequence `<Bslash><Tok>' is preserved.
" The algorithm for `esc=bslash' may be expressed as follows: Escape each
" char in a sequence consisting of any number of backslashes terminated
" with a token. Note that it doesn't matter whether number of backslashes
" is even or odd, since the assumption is that prior to the move, neither
" the backslashes nor the token chars have any special meaning.
if b:txtfmt_cfg_escape != 'none'
" Note: This concat order is *much* more efficient than the
" alternative (since tokens are less common than non-token chars)
let re_need_esc =
\'\%(['.re_new_tokrange.']'
\.'\&[^'.re_old_tokrange.']\)'
if b:txtfmt_cfg_escape == 'bslash'
silent! exe '%s/\%(\\\%(\\*'.re_need_esc.'\)\@=\|'.re_need_esc.'\)/\\\0/g'
elseif b:txtfmt_cfg_escape == 'self'
" TODO_BG: Decide whether to use escape() on re_need_esc or
" whether to hardcode the extra escapes...
silent! exe '%s/'.substitute(b:re_no_self_esc, 'placeholder',
\ escape(re_need_esc, '&\'), '').'/\0\0/g'
endif
endif
" STEP 2: Translate token range
let re_move = '['.re_old_tokrange.']'
if b:txtfmt_cfg_escape != 'none'
if b:txtfmt_cfg_escape == 'bslash'
let re_move = b:re_no_bslash_esc.re_move
elseif b:txtfmt_cfg_escape == 'self'
let re_move = substitute(b:re_no_self_esc, 'placeholder', re_move, '')
endif
endif
silent! exe '%s/'.re_move.'/\='
\.'nr2char(char2nr(submatch(0)) + l:offset)'
\.'/g'
" STEP 3: (If and only if escaping is permitted)
" Remove escape chars for characters that are txtfmt tokens under old
" tokrange setting, but not under new. Also, since this post-unescaping
" step is the complement of the pre-escaping performed above, we must
" unescape backslashes that occur in sequences leading up to the escaped
" token. E.g.,
" <Bslash><Bslash><Bslash><Tok> would become <Bslash><Tok>, since neither
" the <Bslash> nor the subsequent <Tok> is significant after the move.
" Note that there's no need to check for even/odd number of backslashes
" preceding tokens. The number will always be odd. For proof, see the
" Rationale below.
" Note: Any character that is in the old tokrange but not the new is an
" escaped token that no longer needs escaping.
" Rationale: All unescaped tokens of the old range have been translated,
" and hence will be tokens in the new range as well. Thus, any token that
" is within the old range but not within the new must, by definition, have
" been escaped (else it would have been translated to the new range).
" Design Decision: An escape is an escape if it's escaping any txtfmt
" token, even a useless 'no-format' or 'no-color' token appearing outside
" a region. (Recall that I don't highlight these to facilitate removal by
" user...)
" Rationale: The goal of this function is not to clean up user's file, but
" simply to translate tokrange
if b:txtfmt_cfg_escape != 'none'
" Note: This concat order is *much* more efficient than the
" alternative (since tokens are less common than non-token chars)
let re_noneed_esc =
\'\%(['.re_old_tokrange.']'
\.'\&[^'.re_new_tokrange.']\)'
" Perform substitution
if b:txtfmt_cfg_escape == 'bslash'
" Note: The nature of global substitutions is such that the first
" char matched will always be an escaping (not an escaped) char.
silent! exe '%s/\\\(\\\%(\\*'.re_noneed_esc.'\)\@=\|'.re_noneed_esc.'\)/\1/g'
else " self-escape
silent! exe '%s/\('.re_noneed_esc.'\)\(\1\)/\1/g'
endif
endif
" Cause buffer to be refreshed with the new settings
" Note: The following events are consumed by modeline processing logic,
" which may need to alter the starttok value in a modeline
" Note: <f-args> ensures that new_starttok is a string. This is important
" because it permits the modeline processing function to respect user's
" choice of hex or dec when altering the modeline.
let b:txtfmt_ml_new_starttok = new_starttok
:Refresh
endfu
endif " if !exists('*s:MoveStartTok')
" >>>
" Function: s:GetTokInfo() <<<
" Purpose: Return a string, which gives information about a token at a
" specific line/col. If optional line/col pair is not supplied, cursor
" location will be assumed.
" Inputs:
" [line] Optional arg #1. Line number of char for which info is desired. If
" present, 2nd optional arg (col) must also be supplied.
" [col] Optional arg #2. Column number of char for which info is desired.
" Note: This number is actually a byte index, such as would be
" returned by Vim's col() function.
" Return: Variable format string as follows:
" *** fg color token ***
" c<clr_num> [(inactive)]
" Note: <clr_num> is 1 based.
" Also Note: `(inactive)' is appended if the token corresponds to a color that
" is not in the active color mask
" *** bg color token ***
" k<clr_num> [(inactive)]
" Note: <clr_num> is 1 based.
" Also Note: `(inactive)' is appended if the token corresponds to a color that
" is not in the active color mask
" *** format token ***
" f<[u][b][i]>
" i.e., the format descriptor in fiducial form
" *** non-token ***
" <char_code>
" *** invalid char location ***
" 'NUL' (just like Vim's ga builtin)
" *** invalid inputs ***
" <empty string> (and echoerr a warning)
" Note: Will show warning to user if inputs were invalid in a syntactical
" sense. (No error msg for nonexistent char position.)
" Interface note: This function is meant to be used both from a mapping (which
" assumes cursor position) and from a command (which permits user to specify
" position).
" IMPORTANT NOTE: This function is multibyte-safe.
fu! s:GetTokInfo(...)
" The output of the if/else will be line/col of character of interest,
" assuming the inputs are valid.
if a:0 == 0
" Character of interest is at cursor position
let line = line('.')
let col = col('.')
elseif a:0 == 1
" Makes no sense to supply line but not column!
echoerr 'GetTokInfo(): Attempt to specify line without column'
return ''
elseif a:0 == 2
" Check for nonnegative line number
if a:1 =~ '^[1-9][0-9]*$'
let line = a:1
else
echoerr 'GetTokInfo(): '.a:1.' is not a valid line #'
return ''
endif
" Check for nonnegative col number
if a:2 =~ '^[1-9][0-9]*$'
let col = a:2
else
echoerr 'GetTokInfo(): '.a:2.' is not a valid col #'
return ''
endif
else
echoerr 'GetTokInfo(): Wrong # of args - should be 0 or 2'
return ''
endif
" If here, inputs are syntactically valid and line/col represents the
" position of character about which information is desired. Obtain a
" string whose first character is the character of interest.
" Note: char2nr considers only first character in a string, so we don't
" need to strip subsequent characters yet (and we can't do so with
" byte-aware strpart anyway).
let ch = strpart(getline(line), col - 1)
" Note: If input position was invalid, ch will contain empty string.
if ch == ''
" Char pos doesn't exist - not an error
return 'NUL'
endif
" If here, we have a character! Get its character code.
let char_nr = char2nr(ch)
" Get *single* char corresponding to the char code.
" Note: strpart() and expr-[] deal with bytes not chars!
let ch = nr2char(char_nr)
" Determine the range within which token lies
if char_nr >= b:txtfmt_fmt_first_tok && char_nr <= b:txtfmt_fmt_last_tok
" fmt token
return 'f'.b:ubisrc_fmt{char_nr - b:txtfmt_fmt_first_tok}
elseif char_nr >= b:txtfmt_clr_first_tok && char_nr <= b:txtfmt_clr_last_tok
" clr token
" offset 0 = 'no color', represented by 'c-'
" offset i = txtfmtColor{i}
" Note: User-visible array is 1-based, and b:txtfmt_clr_first_tok
" corresponds to the default fg color token
let offset = char_nr - b:txtfmt_clr_first_tok
let ret_str = 'c'.(offset == 0 ? '-' : ''.offset.'')
" Distinguish between active/inactive start color tokens
if char_nr > b:txtfmt_clr_first_tok && ch !~ '['.b:txtfmt_re_clr_stok_atom.']'
let ret_str = ret_str.' (inactive)'
endif
return ret_str
elseif char_nr >= b:txtfmt_bgc_first_tok && char_nr <= b:txtfmt_bgc_last_tok
" bgc token
" offset 0 = 'no color', represented by 'k-'
" offset i = txtfmtColor{i}
" Note: User-visible array is 1-based, and b:txtfmt_bgc_first_tok
" corresponds to the default bg color token
let offset = char_nr - b:txtfmt_bgc_first_tok
let ret_str = 'k'.(offset == 0 ? '-' : ''.offset.'')
" Distinguish between active/inactive start color tokens
if char_nr > b:txtfmt_bgc_first_tok && ch !~ '['.b:txtfmt_re_bgc_stok_atom.']'
let ret_str = ret_str.' (inactive)'
endif
return ret_str
else
" Not a txtfmt token - just return ascii value
return ''.char_nr.''
endif
endfu
" >>>
" >>>
" Configuration <<<
" Needed only for ftplugin
" Note: Performed after the Common Configuration, which sets the 'starttok'
" option, needed when processing user maps
" Function: s:Expand_user_map_macro() <<<
" Purpose: Expand the input string, which is assumed to be the `...' in one of
" the user-map expansion sequences of the form <...>.
" Return: If the macro is valid, return the expanded text, just as it would
" appear in the rhs of the map; otherwise, an empty string.
fu! s:Expand_user_map_macro(s)
let re_ins_tok_i = '^i\\:\(.\+\)$'
let re_ins_tok_n = '^n\([1-9]\d*\)\?\\\(v\?\)\([iIaAoOs]\):\(.\+\)$'
let re_jump_to_tok = '^\([nvio]\)\([1-9]\d*\)\?\([][]\)\(t\?\)\([be]\?[fkca]\)'
" Determine which macro type we have
if a:s =~ re_ins_tok_n . '\|' . re_ins_tok_i
" Insert-token macro
if a:s[0] == 'n'
" Insert-token macro (normal)
let l:count = substitute(a:s, re_ins_tok_n, '\1', '')
let end_in_norm = substitute(a:s, re_ins_tok_n, '\2', '') == 'v'
let enter_ins_cmd = substitute(a:s, re_ins_tok_n, '\3', '')
let fmtclr_list = substitute(a:s, re_ins_tok_n, '\4', '')
else
" Insert-token macro (insert)
let fmtclr_list = substitute(a:s, re_ins_tok_i, '\1', '')
endif
" Validate / Translate the fmt/clr list
let tokstr = s:Translate_fmt_clr_list(fmtclr_list)
if tokstr==''
" Invalid fmt/clr list
" TODO: Perhaps fix up the error string.
let s:err_str = "Invalid fmt/clr list in user map rhs: ".s:err_str
return ''
endif
" Create the mode-specific expansion text
if a:s[0] == 'n'
" normal mode
let seq = ":call <SID>Insert_tokstr('"
\.tokstr."', '".enter_ins_cmd."', 1, ".end_in_norm
\.(strlen(l:count) ? (", ".l:count) : "")
\.")<CR>"
\.":call <SID>Adjust_cursor()<CR>"
else
" insert mode
let seq = "<C-R>=<SID>Insert_tokstr('".tokstr."', 'i', 1, 0)<CR>"
\."<C-R>=<SID>Adjust_cursor()<CR>"
endif
elseif a:s =~ re_jump_to_tok
" Jump to token macro
let l:mode = substitute(a:s, re_jump_to_tok, '\1', '')
let l:count = substitute(a:s, re_jump_to_tok, '\2', '')
let l:dir = substitute(a:s, re_jump_to_tok, '\3', '') == '[' ? 'b' : 'f'
let l:till = substitute(a:s, re_jump_to_tok, '\4', '') == 't' ? 1 : 0
let l:target = substitute(a:s, re_jump_to_tok, '\5', '')
if l:mode =~ '[nvo]'
let l:seq = ":<C-U>call <SID>Jump_to_tok('"
\.l:mode."', '".l:target."', '".l:dir."', ".l:till
\.(strlen(l:count) ? (", ".l:count) : "")
\.")<CR>"
else
" TODO - Permit insert-mode?
let l:seq = "<C-R>=<SID>Jump_to_tok('"
\.l:mode."', '".l:target."', '".l:dir."', ".l:till
\.(strlen(l:count) ? (", ".l:count) : "")
\.")<CR>"
endif
else
let s:err_str = "Invalid user-map expansion sequence: `<".a:s.">'"
return ''
endif
" If here, expansion was successul. Return the expanded text.
return seq
endfu
" >>>
" Function: s:Translate_user_map_rhs() <<<
" Purpose: Convert the rhs specified in a user map definition string to the
" rhs that will be used in the actual map command. Special <<...>> sequences
" are expanded.
" Input: rhs string as it would appear in a user-map string
" Return: The rhs as it would appear in a map command (with user-map macros
" expanded)
" Error: Set s:err_str and return empty string
fu! s:Translate_user_map_rhs(rhs)
let s = a:rhs
" Catch empty (or all ws) strings - shouldn't be input
if s =~ '^[[:space:]]*$'
let s:err_str = "f:User map rhs must contain at least 1 non-whitespace char"
return ''
endif
" Loop until special sequences are all expanded
let ret_str = '' " build up in loop
let len = strlen(s)
let i1 = 0
let i2 = 0
while i2 < len
" Find start of <<...>> sequence - this is safe even if i2 is index of
" next '<'
"let i1 = matchend(s, '\%(\\\_.\|[^<]\)*', i2)
let i1 = matchend(s, '<<', i2)
if i1 < 0
" String is exhausted - accumulate up to end
let ret_str = ret_str.strpart(s, i2)
break
else
" Accumulate, prior to processing <<...>>
let ret_str = ret_str.strpart(s, i2, i1-i2-2)
endif
" Now find closing `>>' (it's not optional at this point)
let i2 = match(s, '>>', i1)
if i2 < 0
let s:err_str = "Unmatched `<<' in user map rhs"
return ''
endif
" Extract stuff inside <<...>>
" i1 points to 1st char beyond `<<'
" i2 points to first `>'
" i1 == i2 implies empty ...
if i2 > i1
let seq = strpart(s, i1, i2-i1)
else
let s:err_str = "Empty fmt/clr map sequence"
return ''
endif
" We have a non-empty sequence. Convert txtfmt-specific <rt> to `>'
" before passing to Expand_user_map_macro for expansion.
"let seq = substitute(seq, '\\\(.\)', '\1', 'g')
let seq = substitute(seq, '<rt>', '>', 'g')
" Expand the macro
let expseq = s:Expand_user_map_macro(seq)
" Was it valid?
if expseq == ''
let s:err_str = "Invalid usermap rhs: " . seq
return ''
endif
" Append the expanded text to the return string
let ret_str = ret_str.expseq
" Make i2 point just past `>>' (it's on the 1st `>')
let i2 = i2+2
endwhile
" Return the now completely expanded string
return ret_str
endfu
" >>>
" Function: s:Do_user_maps() <<<
" Purpose: Process any special global variables set by user, for the purpose
" of allowing him to build his own map sequences from primitives.
" How:
fu! s:Do_user_maps()
" In the following regex, \1=map command, \2=lhs, \3=rhs
" RULES:
" map_cmd must be imap, inoremap, nmap or nnoremap
" map_lhs is terminated by first unescaped whitespace
" -whitespace may appear in lhs if preceded by <C-V>
" map_rhs is everything else in the string
" -must have extra level of escaping for \ and <
" -may contain special <[in]:...> sequences
" TODO - Fix up the regex...
let re_usermap = '^\s*\([in]\%(nore\)\?map\)\s\+'
\.'\(\%('."\<C-V>.".'\|\S\)\+\)\s\+\(.\+\)'
" Allow up to configurable number of user maps
" Note: txtfmtUsermaplimit option may be set globally or buflocally, with
" precedence given to buflocal set.
let bset = exists('b:txtfmtUsermaplimit')
let gset = exists('g:txtfmtUsermaplimit')
if bset || gset
let user_map_limit = bset ? b:txtfmtUsermaplimit : g:txtfmtUsermaplimit
" Validate the limit set by user
if user_map_limit !~ '^\s*\([1-9]\d*\|0x\x\+\)\s*$'
" Invalid format - Warn and abort user-map processing
echoerr "Aborting user-defined map processing: "
\.(bset ? 'b:' : 'g:').'txtfmtUsermaplimit set to invalid value: '
\."`".user_map_limit."'"
return
endif
else
" Set default
let user_map_limit = 25
endif
" Loop over all possible maps
let i = 1
while i <= user_map_limit
" Determine whether buflocal or global setting exists for this element
let bset = exists('b:txtfmtUsermap'.i)
let gset = exists('g:txtfmtUsermap'.i)
if bset || gset
" Obtain the buflocal or global element
let s = bset ? b:txtfmtUsermap{i} : g:txtfmtUsermap{i}
" Validate and process the user map string
if s !~ re_usermap
echoerr 'Ignoring malformed user-defined map specified by '
\.(bset ? 'b:' : 'g:').'txtfmtUsermap{'.i.'}: '
\.'help txtfmt-user-map-fmt'
else
" Extract the map command and the map lhs/rhs
let map_cmd = substitute(s, re_usermap, '\1', '')
let map_lhs = substitute(s, re_usermap, '\2', '')
let map_rhs = substitute(s, re_usermap, '\3', '')
" Process non-empty rhs for special sequences
" NOTE: rhs has extra level of \ and < escaping because of the
" special embedded <...> sequences
let map_rhs = s:Translate_user_map_rhs(map_rhs)
if map_rhs==''
echoerr "User-defined map #".i." ignored due to error: ".s:err_str
else
" Attempt to define the map
exe map_cmd.' <buffer> '.map_lhs.' '.map_rhs
" Add corresponding undo action (n or i unmap)
" TODO - Figure out how to use s:Undef_map and avoid "no
" such mapping error.
call s:Add_undo(map_cmd[0].'unmap <buffer> '.map_lhs)
endif
endif
endif
" Progress to next possible user map
let i = i + 1
endwhile
endfu
" >>>
" Function: s:Set_mapwarn() <<<
" Purpose: Set txtfmt_cfg_mapwarn option either from user-supplied
" g:txtfmtMapwarn or to default value. Global var txtfmtMapwarn is a character
" flag option, which may contain the following flags: mMeEcCoO. Although the
" flags may appear in any combination and in any order, there are certain
" combinations that make no sense and should (arguably) result in a warning:
" -m and e should not be used together
" -M and E should not be used together
" Note: If either of the above 2 rules are violated, the last supplied flag
" takes precedence.
" -o should not be used without either e or m
" -O should not be used without either E or M
fu! s:Set_mapwarn()
" The following buffer-local config option is the output of this function,
" and must be set before return.
unlet! b:txtfmt_cfg_mapwarn
if exists('g:txtfmtMapwarn')
" Process value supplied by user, storing to buffer-local config
" variable a valid and normalized set of character flags.
" Design Decision: Preserve the order of flags being retained rather
" than arranging them in fiducial order.
" Note: Existence of l:mapwarn after the loop implies that no error
" was found with user-supplied option value. (Note that empty string
" is a valid setting.)
let mapwarn = ''
let i = strlen(g:txtfmtMapwarn) - 1
while i >= 0
let ch = g:txtfmtMapwarn[i]
if ch !~ '[mMeEcCoO]'
" Invalid flag!
unlet mapwarn
break
endif
" Make sure flags already in mapwarn don't preclude addition of
" this one.
if (
\-1 == stridx(mapwarn, ch) &&
\(ch != 'm' || -1 == stridx(mapwarn, 'e')) &&
\(ch != 'e' || -1 == stridx(mapwarn, 'm')) &&
\(ch != 'M' || -1 == stridx(mapwarn, 'E')) &&
\(ch != 'E' || -1 == stridx(mapwarn, 'M'))
\)
" Prepend the flag to preserve order. (Recall that loop is in
" reverse order.)
let mapwarn = ch . mapwarn
endif
" Retreat to preceding character flag
let i = i - 1
endwhile
if exists('l:mapwarn')
" No errors were encountered in the set of mapwarn.
let b:txtfmt_cfg_mapwarn = mapwarn
else
" Warn user that his setting was not valid
echomsg "Ignoring invalid setting of txtfmtMapwarn: `".g:txtfmtMapwarn
\."' (:he txtfmtMapwarn)"
endif
endif
" If option was not set by user to a valid value, set to default
if !exists('b:txtfmt_cfg_mapwarn')
" Use default
let b:txtfmt_cfg_mapwarn = 'mMoOcC'
endif
endfu
" >>>
" Function: s:Define_user_map_defaults() <<<
" Purpose: Set up some default user maps for testing...
fu! s:Define_user_map_defaults()
" User map definition examples for test <<<
" Map CTRL-B in insert mode to start and terminate a 'bold' region,
" leaving the cursor positioned in the region interior, ready to type bold
" text.
" Hint: Similar maps might be created for underline and italic
let g:txtfmtUsermap1 = 'inoremap <C-B> <<i\:fb.f->>'
" Map CTRL-\f in insert mode to end current format region.
let g:txtfmtUsermap2 = 'inoremap <C-\>f <<i\:f->>'
" Map CTRL-\k in insert mode to end current bg color region.
let g:txtfmtUsermap3 = 'inoremap <C-\>k <<i\:k->>'
" Map \t in normal mode to embolden, underline and center (i.e.
" 'title-ize') the current line
let g:txtfmtUsermap4 =
\'nnoremap <Bslash>t <<n\vI:fbu>><<n\vA:f->>:ce<CR>'
" Map \cf in normal mode to change all text within the current format
" region (without deleting the tokens that begin and end the region).
" Note: Since the default jump-to-token mappings are used in the rhs
" (rather than the special expansion macros), nmap must be used (rather
" than nnoremap).
" Note: The reason the ]f does not cause the format 'end region' token to
" be deleted is that the operator-pending jump-to-token maps work
" 'exclusively' when there is no 'v' between operator and motion.
let g:txtfmtUsermap5 =
\'nmap <Bslash>cf [tbfc]f'
" Same as preceding map but for current color region.
" Note: This one demonstrates the use of the 'jump-to-token' expansion
" macros.
let g:txtfmtUsermap6 =
\'nnoremap <Bslash>cc <<n[tbc>>c<<o]c>>'
" Map <LocalLeader>bw in normal mode to embolden the word under the
" cursor. (The extra complexity is needed to ensure that you can invoke
" with cursor anywhere on the word.)
let g:txtfmtUsermap7 =
\'nnoremap <LocalLeader>bw :if col(".")!=1 && '
\.'getline(".")[col(".")-2]=~"\\w"<Bar>exe "norm! b"<Bar>'
\.'endif<CR><<n\vi:fb>>e<<n\va:f->>b'
" Map \vf in normal mode to select all of the current format region
" visually.
" Note: Unlike the earlier one for changing the current format region,
" this one doesn't constrain the backwards jump to a 'begin' region token;
" hence, it will also highlight the text between regions.
let g:txtfmtUsermap8 =
\'nnoremap <LocalLeader>vf <<n[tf>>v<<v]tf>>'
" Map <C-\>vf in insert mode to do the same in insert mode
let g:txtfmtUsermap9 =
\'inoremap <C-\>vf <<i[tf>><Esc>lv<<v]tf>>'
" Map <LocalLeader><Space> in normal mode to jump forward to the 3rd
" 'begin format region' token. (Not overly practical, but demonstrates the
" use of whitespace in the lhs, as well as the use of the optional count
" with the jump-to-token expansion macros.)
let g:txtfmtUsermap10 =
\'nnoremap <LocalLeader><Space> <<n3]bf>>'
" Map <LocalLeader>_ in normal mode to substitute the next 4 characters
" with a 'bold' format token followed by a 'no format' token, leaving the
" cursor positioned between the two.
" (This map is not intended to be useful, but merely to demonstrate the
" specification of a count with an insert-token expansion macro.)
let g:txtfmtUsermap11 =
\'nnoremap <LocalLeader>_ <<n4\s:fb.f->>'
" Map <LocalLeader>rb in normal mode to make the current line bold with a
" red background.
let g:txtfmtUsermap12 =
\'nnoremap <LocalLeader>rb <<n\vI:kr,fb>><<n\vA:f-,k->>'
" >>>
endfu
" >>>
" Function: s:Do_config() <<<
" Purpose: Set script local variables, taking into account whether user has
" overriden via txtfmt globals.
fu! s:Do_config()
" set vim 'iskeyword' option <<<
" Exclude the special tokens from iskeyword option, so that word movement
" normal commands will work intuitively. (Recall that the delimiters will
" appear as space characters.)
" IMPORTANT NOTE: Ideally, we would be able to have the tokens treated
" just like whitespace, from the standpoint of word and WORD motions;
" unfortunately, we can't instruct Vim to do this - the best we can do is
" make them non-keyword, which means they'll be treated like punctation;
" i.e., word motions will stop on them and on the beginning of subsequent
" word.
" IMPORTANT TODO: Vim doesn't allow multi-byte characters above 255 to be
" excluded!
" Decide whether there's a workaround. For now, don't do this if we're
" dealing with tokens above 255.
" Note: I'm intentionally including inactive color tokens in the ranges.
" Rationale: I don't feel that the complexity that would be added by the
" logic to exclude them is justified by any advantage doing so would
" provide.
if (b:txtfmt_last_tok <= 255)
let val = '^'.b:txtfmt_clr_first_tok.'-'.b:txtfmt_last_tok
exe 'setlocal iskeyword+='.val
call s:Add_undo('setlocal iskeyword-='.val)
endif
" >>>
" Process txtfmtMapwarn option <<<
call s:Set_mapwarn()
" >>>
" txtfmtUsermaplimit: Max # of user maps that will be checked <<<
" Allow nonnegative dec, hex, or oct
" Cannot set from modeline
if exists('g:txtfmtUsermaplimit') && g:txtfmtUsermaplimit =~ '^\%(0[xX]\)\?[0-9]\+$'
let s:txtfmtUsermaplimit = g:txtfmtUsermaplimit
else
" Set to reasonable default
let s:txtfmtUsermaplimit = 25
endif
" >>>
" TEST ONLY: Define some default user-maps for testing <<<
"call s:Define_user_map_defaults()
" >>>
" Process any user-defined maps <<<
call s:Do_user_maps()
" >>>
endfu
" >>>
call s:Do_config()
" >>>
" Public-interface functions <<<
" Function: g:Txtfmt_GetTokInfo() <<<
" !!!!!!!!!!!!!!!!!!!!!!
" !!!!! DEPRECATED !!!!!
" !!!!!!!!!!!!!!!!!!!!!!
" Purpose: Return a string, which gives information about a token at a
" specific line/col. If optional line/col pair is not supplied, cursor
" location will be assumed.
" Important Note: This function is conceptually a wrapper for script-local
" s:GetTokInfo. For backwards-compatibility reasons, however, the meaning of
" the 'col' parameter is slightly different. For this function, col represents
" a 1-based char index; for s:GetTokInfo it is a 1-based byte index.
" Note: See s:GetTokInfo for additional description
" Interface note: This function is meant to be used by plugin user; e.g., from
" mappings.
" IMPORTANT NOTE: This function now works for multibyte encodings.
fu! Txtfmt_GetTokInfo(...)
" Call s:GetTokInfo with the appropriate arguments
if a:0 == 0
return s:GetTokInfo()
elseif a:0 == 1
" Makes no sense to supply line but not column!
echoerr 'Txtfmt_GetTokInfo(): Attempt to specify line without column'
return ''
elseif a:0 == 2
" Check for nonnegative line number
if a:1 =~ '^[1-9][0-9]*$'
let line = a:1
else
echoerr 'Txtfmt_GetTokInfo(): '.a:1.' is not a valid line #'
return ''
endif
" Check for nonnegative col number
if a:2 =~ '^[1-9][0-9]*$'
" Note: Input col is 1-based character index. Use byteidx to convert
" to 1-based byte index for strpart.
let col = byteidx(getline(line), a:2 - 1) + 1
if col == 0
" Invalid (too large) col position - not error...
return 'NUL'
else
return s:GetTokInfo(line, col)
endif
else
echoerr 'Txtfmt_GetTokInfo(): '.a:2.' is not a valid col #'
return ''
endif
else
echoerr 'Txtfmt_GetTokInfo(): Wrong # of args - should be 0 or 2'
return ''
endif
endfu
" >>>
" Function: g:OldTxtfmt_GetTokInfo() <<<
" Purpose: Return a string, which gives information about a token at a
" specific line/col. If optional line/col pair is not supplied, cursor
" location will be assumed.
" Inputs:
" [line] Optional arg #1. Line number of char for which info is desired. If
" present, 2nd optional arg (col) must also be supplied.
" [col] Optional arg #2. Column number of char for which info is desired.
" NOTE: Currently, even when a multi-byte encoding is used, [col] is used as a
" byte offset rather than a character offset.
" TODO: Decide whether I should stop obtaining the character via
" getline()[pos] in favor of a multi-byte safe way.
" Return: Variable format string as follows:
" *** color token ***
" c<clr_num>
" Note: <clr_num> is 1 based.
" *** format token ***
" f<[u][b][i]>
" i.e., the format descriptor in fiducial form
" *** non-token ***
" <ascii_char_code>
" *** invalid char location or wrong # of inputs ***
" <empty string>
" Note: Will show warning to user if inputs were invalid in a syntactical
" sense. (No error msg for nonexistent char position.)
" Interface note: This function is meant to be used by plugin user; e.g., from
" mappings.
" IMPORTANT NOTE: This function now works for multibyte encodings.
" TODO_BG: Delete this "old" version of the function if I haven't rolled back
" prior to the release of 2.0...
fu! OldTxtfmt_GetTokInfo(...)
" The output of the if/else will be a variable (ch) whose first character
" is the token about which information is requested
if a:0 == 0
let ch = strpart(getline('.'), col('.') - 1)
elseif a:0 == 1
" Makes no sense to supply line but not column!
echoerr 'Txtfmt_GetTokInfo(): Attempt to specify line without column'
return ''
elseif a:0 == 2
" Check for nonnegative line number
if a:1 =~ '^[1-9][0-9]*$'
let line = a:1
else
echoerr 'Txtfmt_GetTokInfo(): '.a:1.' is not a valid line #'
return ''
endif
" Check for nonnegative col number
if a:2 =~ '^[1-9][0-9]*$'
" Note: Input col is 1-based character index. Use byteidx to convert
" to byte index for strpart.
let col0 = byteidx(getline(line), a:2 - 1)
if col0 == -1
" Invalid (too large) col position - not error...
let ch = ''
else
let ch = strpart(getline(line), col0)
endif
else
echoerr 'Txtfmt_GetTokInfo(): '.a:2.' is not a valid col #'
return ''
endif
else
echoerr 'Txtfmt_GetTokInfo(): Wrong # of args - should be 0 or 2'
return ''
endif
" If here, inputs are syntactically valid and ch holds a string whose
" first character is the one about which info is requested, or empty
" string if the requested position is invalid.
if ch == ''
" Char pos doesn't exist - not an error
return ''
endif
let char_nr = char2nr(ch)
" Determine the range within which token lies
if char_nr >= b:txtfmt_fmt_first_tok && char_nr <= b:txtfmt_fmt_last_tok
" fmt token
return 'f'.b:ubisrc_fmt{char_nr - b:txtfmt_fmt_first_tok}
elseif char_nr >= b:txtfmt_clr_first_tok && char_nr <= b:txtfmt_clr_last_tok
" clr token
" offset 0 = 'no color', represented by 'c-'
" offset i = color{i-1}
let offset = char_nr - b:txtfmt_clr_first_tok
return 'c'.(offset == 0 ? '-' : ''.(offset-1).'')
else
" Not a txtfmt token - just return ascii value
return ''.char_nr.''
endif
endfu
" >>>
" Function: g:Txtfmt_GetTokStr() <<<
" Purpose: Translate the input fmt/clr spec list and return the resulting
" token string.
" Inputs:
" s fmt/clr spec list to be translated
" Return: If input spec list is valid, the corresponding literal token
" sequence is returned as a string; otherwise, empty string is returned and
" error msg is output.
fu! Txtfmt_GetTokStr(s)
" Make sure this is a txtfmt buffer
if !exists('b:loaded_txtfmt')
echoerr "Function Txtfmt_GetTokStr can be used only within a 'txtfmt' buffer"
return ''
endif
" Call script-local function to perform the translation
let tokstr = s:Translate_fmt_clr_list(a:s)
if (tokstr == '')
echoerr "`".a:s."' is not a valid fmt/clr spec list"
return ''
else
" We have a translated fmt/clr spec comprising an offset followed by
" the actual fmt/clr token sequence. Extract the literal token string
" and throw the offset away.
" TODO - Embed this in a special accessor function that may be used
" elsewhere...
let tokstr = substitute(tokstr, '\(\-\?[[:digit:]]\+\),\(.*\)', '\2', '')
return tokstr
endif
endfu
" >>>
" >>>
" Public-interface commands <<<
com! -buffer ShowTokenMap call <SID>ShowTokenMap()
com! -buffer -nargs=? MoveStartTok call <SID>MoveStartTok(<f-args>)
com! -buffer -nargs=* GetTokInfo echo <SID>GetTokInfo(<f-args>)
" >>>
" MAPS: LEVEL 1 & 2 (reconfig): normal/insert mode --> <Plug>... mappings <<<
" Note: <C-R> used (rather than <C-O>) to prevent side-effect when insert-mode
" mapping invoked past end of line (cursor pos off by 1)
" normal mode jump 'to' token mappings <<<
" Align sequence <<<
" AlignCtrl default
" AlignCtrl w=p0P1 ,
" AlignCtrl g ^call
" '<,'>Align
" >>>
call s:Def_map('n', '[bf', '<Plug>TxtfmtBckToFmtBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bf', 'b', 0)<CR>")
call s:Def_map('n', ']bf', '<Plug>TxtfmtFwdToFmtBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bf', 'f', 0)<CR>")
call s:Def_map('n', '[bc', '<Plug>TxtfmtBckToClrBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bc', 'b', 0)<CR>")
call s:Def_map('n', ']bc', '<Plug>TxtfmtFwdToClrBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bc', 'f', 0)<CR>")
call s:Def_map('n', '[bk', '<Plug>TxtfmtBckToBgcBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bk', 'b', 0)<CR>")
call s:Def_map('n', ']bk', '<Plug>TxtfmtFwdToBgcBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bk', 'f', 0)<CR>")
call s:Def_map('n', '[ba', '<Plug>TxtfmtBckToAnyBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'ba', 'b', 0)<CR>")
call s:Def_map('n', ']ba', '<Plug>TxtfmtFwdToAnyBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'ba', 'f', 0)<CR>")
call s:Def_map('n', '[f' , '<Plug>TxtfmtBckToFmtTok' , ":<C-U>call <SID>Jump_to_tok('n', 'f' , 'b', 0)<CR>")
call s:Def_map('n', ']f' , '<Plug>TxtfmtFwdToFmtTok' , ":<C-U>call <SID>Jump_to_tok('n', 'f' , 'f', 0)<CR>")
call s:Def_map('n', '[c' , '<Plug>TxtfmtBckToClrTok' , ":<C-U>call <SID>Jump_to_tok('n', 'c' , 'b', 0)<CR>")
call s:Def_map('n', ']c' , '<Plug>TxtfmtFwdToClrTok' , ":<C-U>call <SID>Jump_to_tok('n', 'c' , 'f', 0)<CR>")
call s:Def_map('n', '[k' , '<Plug>TxtfmtBckToBgcTok' , ":<C-U>call <SID>Jump_to_tok('n', 'k' , 'b', 0)<CR>")
call s:Def_map('n', ']k' , '<Plug>TxtfmtFwdToBgcTok' , ":<C-U>call <SID>Jump_to_tok('n', 'k' , 'f', 0)<CR>")
call s:Def_map('n', '[a' , '<Plug>TxtfmtBckToAnyTok' , ":<C-U>call <SID>Jump_to_tok('n', 'a' , 'b', 0)<CR>")
call s:Def_map('n', ']a' , '<Plug>TxtfmtFwdToAnyTok' , ":<C-U>call <SID>Jump_to_tok('n', 'a' , 'f', 0)<CR>")
call s:Def_map('n', '[ef', '<Plug>TxtfmtBckToFmtEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ef', 'b', 0)<CR>")
call s:Def_map('n', ']ef', '<Plug>TxtfmtFwdToFmtEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ef', 'f', 0)<CR>")
call s:Def_map('n', '[ec', '<Plug>TxtfmtBckToClrEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ec', 'b', 0)<CR>")
call s:Def_map('n', ']ec', '<Plug>TxtfmtFwdToClrEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ec', 'f', 0)<CR>")
call s:Def_map('n', '[ek', '<Plug>TxtfmtBckToBgcEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ek', 'b', 0)<CR>")
call s:Def_map('n', ']ek', '<Plug>TxtfmtFwdToBgcEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ek', 'f', 0)<CR>")
call s:Def_map('n', '[ea', '<Plug>TxtfmtBckToAnyEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ea', 'b', 0)<CR>")
call s:Def_map('n', ']ea', '<Plug>TxtfmtFwdToAnyEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ea', 'f', 0)<CR>")
" >>>
" visual mode jump 'to' token mappings <<<
call s:Def_map('v', '[bf', '<Plug>TxtfmtBckToFmtBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bf', 'b', 0)<CR>")
call s:Def_map('v', ']bf', '<Plug>TxtfmtFwdToFmtBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bf', 'f', 0)<CR>")
call s:Def_map('v', '[bc', '<Plug>TxtfmtBckToClrBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bc', 'b', 0)<CR>")
call s:Def_map('v', ']bc', '<Plug>TxtfmtFwdToClrBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bc', 'f', 0)<CR>")
call s:Def_map('v', '[bk', '<Plug>TxtfmtBckToBgcBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bk', 'b', 0)<CR>")
call s:Def_map('v', ']bk', '<Plug>TxtfmtFwdToBgcBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bk', 'f', 0)<CR>")
call s:Def_map('v', '[ba', '<Plug>TxtfmtBckToAnyBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'ba', 'b', 0)<CR>")
call s:Def_map('v', ']ba', '<Plug>TxtfmtFwdToAnyBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'ba', 'f', 0)<CR>")
call s:Def_map('v', '[f' , '<Plug>TxtfmtBckToFmtTok' , ":<C-U>call <SID>Jump_to_tok('v', 'f' , 'b', 0)<CR>")
call s:Def_map('v', ']f' , '<Plug>TxtfmtFwdToFmtTok' , ":<C-U>call <SID>Jump_to_tok('v', 'f' , 'f', 0)<CR>")
call s:Def_map('v', '[c' , '<Plug>TxtfmtBckToClrTok' , ":<C-U>call <SID>Jump_to_tok('v', 'c' , 'b', 0)<CR>")
call s:Def_map('v', ']c' , '<Plug>TxtfmtFwdToClrTok' , ":<C-U>call <SID>Jump_to_tok('v', 'c' , 'f', 0)<CR>")
call s:Def_map('v', '[k' , '<Plug>TxtfmtBckToBgcTok' , ":<C-U>call <SID>Jump_to_tok('v', 'k' , 'b', 0)<CR>")
call s:Def_map('v', ']k' , '<Plug>TxtfmtFwdToBgcTok' , ":<C-U>call <SID>Jump_to_tok('v', 'k' , 'f', 0)<CR>")
call s:Def_map('v', '[a' , '<Plug>TxtfmtBckToAnyTok' , ":<C-U>call <SID>Jump_to_tok('v', 'a' , 'b', 0)<CR>")
call s:Def_map('v', ']a' , '<Plug>TxtfmtFwdToAnyTok' , ":<C-U>call <SID>Jump_to_tok('v', 'a' , 'f', 0)<CR>")
call s:Def_map('v', '[ef', '<Plug>TxtfmtBckToFmtEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ef', 'b', 0)<CR>")
call s:Def_map('v', ']ef', '<Plug>TxtfmtFwdToFmtEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ef', 'f', 0)<CR>")
call s:Def_map('v', '[ec', '<Plug>TxtfmtBckToClrEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ec', 'b', 0)<CR>")
call s:Def_map('v', ']ec', '<Plug>TxtfmtFwdToClrEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ec', 'f', 0)<CR>")
call s:Def_map('v', '[ek', '<Plug>TxtfmtBckToBgcEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ek', 'b', 0)<CR>")
call s:Def_map('v', ']ek', '<Plug>TxtfmtFwdToBgcEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ek', 'f', 0)<CR>")
call s:Def_map('v', '[ea', '<Plug>TxtfmtBckToAnyEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ea', 'b', 0)<CR>")
call s:Def_map('v', ']ea', '<Plug>TxtfmtFwdToAnyEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ea', 'f', 0)<CR>")
" >>>
" operator-pending mode jump 'to' token mappings <<<
" Note: 'v' can be used with these to toggle inclusive/exclusive
call s:Def_map('o', '[bf', '<Plug>TxtfmtBckToFmtBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bf', 'b', 0)<CR>")
call s:Def_map('o', ']bf', '<Plug>TxtfmtFwdToFmtBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bf', 'f', 0)<CR>")
call s:Def_map('o', '[bc', '<Plug>TxtfmtBckToClrBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bc', 'b', 0)<CR>")
call s:Def_map('o', ']bc', '<Plug>TxtfmtFwdToClrBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bc', 'f', 0)<CR>")
call s:Def_map('o', '[bk', '<Plug>TxtfmtBckToBgcBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bk', 'b', 0)<CR>")
call s:Def_map('o', ']bk', '<Plug>TxtfmtFwdToBgcBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bk', 'f', 0)<CR>")
call s:Def_map('o', '[ba', '<Plug>TxtfmtBckToAnyBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'ba', 'b', 0)<CR>")
call s:Def_map('o', ']ba', '<Plug>TxtfmtFwdToAnyBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'ba', 'f', 0)<CR>")
call s:Def_map('o', '[f' , '<Plug>TxtfmtBckToFmtTok' , ":<C-U>call <SID>Jump_to_tok('o', 'f' , 'b', 0)<CR>")
call s:Def_map('o', ']f' , '<Plug>TxtfmtFwdToFmtTok' , ":<C-U>call <SID>Jump_to_tok('o', 'f' , 'f', 0)<CR>")
call s:Def_map('o', '[c' , '<Plug>TxtfmtBckToClrTok' , ":<C-U>call <SID>Jump_to_tok('o', 'c' , 'b', 0)<CR>")
call s:Def_map('o', ']c' , '<Plug>TxtfmtFwdToClrTok' , ":<C-U>call <SID>Jump_to_tok('o', 'c' , 'f', 0)<CR>")
call s:Def_map('o', '[k' , '<Plug>TxtfmtBckToBgcTok' , ":<C-U>call <SID>Jump_to_tok('o', 'k' , 'b', 0)<CR>")
call s:Def_map('o', ']k' , '<Plug>TxtfmtFwdToBgcTok' , ":<C-U>call <SID>Jump_to_tok('o', 'k' , 'f', 0)<CR>")
call s:Def_map('o', '[a' , '<Plug>TxtfmtBckToAnyTok' , ":<C-U>call <SID>Jump_to_tok('o', 'a' , 'b', 0)<CR>")
call s:Def_map('o', ']a' , '<Plug>TxtfmtFwdToAnyTok' , ":<C-U>call <SID>Jump_to_tok('o', 'a' , 'f', 0)<CR>")
call s:Def_map('o', '[ef', '<Plug>TxtfmtBckToFmtEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ef', 'b', 0)<CR>")
call s:Def_map('o', ']ef', '<Plug>TxtfmtFwdToFmtEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ef', 'f', 0)<CR>")
call s:Def_map('o', '[ec', '<Plug>TxtfmtBckToClrEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ec', 'b', 0)<CR>")
call s:Def_map('o', ']ec', '<Plug>TxtfmtFwdToClrEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ec', 'f', 0)<CR>")
call s:Def_map('o', '[ek', '<Plug>TxtfmtBckToBgcEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ek', 'b', 0)<CR>")
call s:Def_map('o', ']ek', '<Plug>TxtfmtFwdToBgcEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ek', 'f', 0)<CR>")
call s:Def_map('o', '[ea', '<Plug>TxtfmtBckToAnyEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ea', 'b', 0)<CR>")
call s:Def_map('o', ']ea', '<Plug>TxtfmtFwdToAnyEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ea', 'f', 0)<CR>")
" >>>
" normal mode jump 'till' token mappings <<<
call s:Def_map('n', '[tbf', '<Plug>TxtfmtBckTillFmtBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bf', 'b', 1)<CR>")
call s:Def_map('n', ']tbf', '<Plug>TxtfmtFwdTillFmtBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bf', 'f', 1)<CR>")
call s:Def_map('n', '[tbc', '<Plug>TxtfmtBckTillClrBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bc', 'b', 1)<CR>")
call s:Def_map('n', ']tbc', '<Plug>TxtfmtFwdTillClrBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bc', 'f', 1)<CR>")
call s:Def_map('n', '[tbk', '<Plug>TxtfmtBckTillBgcBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bk', 'b', 1)<CR>")
call s:Def_map('n', ']tbk', '<Plug>TxtfmtFwdTillBgcBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'bk', 'f', 1)<CR>")
call s:Def_map('n', '[tba', '<Plug>TxtfmtBckTillAnyBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'ba', 'b', 1)<CR>")
call s:Def_map('n', ']tba', '<Plug>TxtfmtFwdTillAnyBegTok', ":<C-U>call <SID>Jump_to_tok('n', 'ba', 'f', 1)<CR>")
call s:Def_map('n', '[tf' , '<Plug>TxtfmtBckTillFmtTok' , ":<C-U>call <SID>Jump_to_tok('n', 'f' , 'b', 1)<CR>")
call s:Def_map('n', ']tf' , '<Plug>TxtfmtFwdTillFmtTok' , ":<C-U>call <SID>Jump_to_tok('n', 'f' , 'f', 1)<CR>")
call s:Def_map('n', '[tc' , '<Plug>TxtfmtBckTillClrTok' , ":<C-U>call <SID>Jump_to_tok('n', 'c' , 'b', 1)<CR>")
call s:Def_map('n', ']tc' , '<Plug>TxtfmtFwdTillClrTok' , ":<C-U>call <SID>Jump_to_tok('n', 'c' , 'f', 1)<CR>")
call s:Def_map('n', '[tk' , '<Plug>TxtfmtBckTillBgcTok' , ":<C-U>call <SID>Jump_to_tok('n', 'k' , 'b', 1)<CR>")
call s:Def_map('n', ']tk' , '<Plug>TxtfmtFwdTillBgcTok' , ":<C-U>call <SID>Jump_to_tok('n', 'k' , 'f', 1)<CR>")
call s:Def_map('n', '[ta' , '<Plug>TxtfmtBckTillAnyTok' , ":<C-U>call <SID>Jump_to_tok('n', 'a' , 'b', 1)<CR>")
call s:Def_map('n', ']ta' , '<Plug>TxtfmtFwdTillAnyTok' , ":<C-U>call <SID>Jump_to_tok('n', 'a' , 'f', 1)<CR>")
call s:Def_map('n', '[tef', '<Plug>TxtfmtBckTillFmtEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ef', 'b', 1)<CR>")
call s:Def_map('n', ']tef', '<Plug>TxtfmtFwdTillFmtEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ef', 'f', 1)<CR>")
call s:Def_map('n', '[tec', '<Plug>TxtfmtBckTillClrEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ec', 'b', 1)<CR>")
call s:Def_map('n', ']tec', '<Plug>TxtfmtFwdTillClrEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ec', 'f', 1)<CR>")
call s:Def_map('n', '[tek', '<Plug>TxtfmtBckTillBgcEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ek', 'b', 1)<CR>")
call s:Def_map('n', ']tek', '<Plug>TxtfmtFwdTillBgcEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ek', 'f', 1)<CR>")
call s:Def_map('n', '[tea', '<Plug>TxtfmtBckTillAnyEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ea', 'b', 1)<CR>")
call s:Def_map('n', ']tea', '<Plug>TxtfmtFwdTillAnyEndTok', ":<C-U>call <SID>Jump_to_tok('n', 'ea', 'f', 1)<CR>")
" >>>
" visual mode jump 'till' token mappings <<<
call s:Def_map('v', '[tbf', '<Plug>TxtfmtBckTillFmtBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bf', 'b', 1)<CR>")
call s:Def_map('v', ']tbf', '<Plug>TxtfmtFwdTillFmtBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bf', 'f', 1)<CR>")
call s:Def_map('v', '[tbc', '<Plug>TxtfmtBckTillClrBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bc', 'b', 1)<CR>")
call s:Def_map('v', ']tbc', '<Plug>TxtfmtFwdTillClrBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bc', 'f', 1)<CR>")
call s:Def_map('v', '[tbk', '<Plug>TxtfmtBckTillBgcBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bk', 'b', 1)<CR>")
call s:Def_map('v', ']tbk', '<Plug>TxtfmtFwdTillBgcBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'bk', 'f', 1)<CR>")
call s:Def_map('v', '[tba', '<Plug>TxtfmtBckTillAnyBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'ba', 'b', 1)<CR>")
call s:Def_map('v', ']tba', '<Plug>TxtfmtFwdTillAnyBegTok', ":<C-U>call <SID>Jump_to_tok('v', 'ba', 'f', 1)<CR>")
call s:Def_map('v', '[tf' , '<Plug>TxtfmtBckTillFmtTok' , ":<C-U>call <SID>Jump_to_tok('v', 'f' , 'b', 1)<CR>")
call s:Def_map('v', ']tf' , '<Plug>TxtfmtFwdTillFmtTok' , ":<C-U>call <SID>Jump_to_tok('v', 'f' , 'f', 1)<CR>")
call s:Def_map('v', '[tc' , '<Plug>TxtfmtBckTillClrTok' , ":<C-U>call <SID>Jump_to_tok('v', 'c' , 'b', 1)<CR>")
call s:Def_map('v', ']tc' , '<Plug>TxtfmtFwdTillClrTok' , ":<C-U>call <SID>Jump_to_tok('v', 'c' , 'f', 1)<CR>")
call s:Def_map('v', '[tk' , '<Plug>TxtfmtBckTillBgcTok' , ":<C-U>call <SID>Jump_to_tok('v', 'k' , 'b', 1)<CR>")
call s:Def_map('v', ']tk' , '<Plug>TxtfmtFwdTillBgcTok' , ":<C-U>call <SID>Jump_to_tok('v', 'k' , 'f', 1)<CR>")
call s:Def_map('v', '[ta' , '<Plug>TxtfmtBckTillAnyTok' , ":<C-U>call <SID>Jump_to_tok('v', 'a' , 'b', 1)<CR>")
call s:Def_map('v', ']ta' , '<Plug>TxtfmtFwdTillAnyTok' , ":<C-U>call <SID>Jump_to_tok('v', 'a' , 'f', 1)<CR>")
call s:Def_map('v', '[tef', '<Plug>TxtfmtBckTillFmtEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ef', 'b', 1)<CR>")
call s:Def_map('v', ']tef', '<Plug>TxtfmtFwdTillFmtEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ef', 'f', 1)<CR>")
call s:Def_map('v', '[tec', '<Plug>TxtfmtBckTillClrEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ec', 'b', 1)<CR>")
call s:Def_map('v', ']tec', '<Plug>TxtfmtFwdTillClrEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ec', 'f', 1)<CR>")
call s:Def_map('v', '[tek', '<Plug>TxtfmtBckTillBgcEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ek', 'b', 1)<CR>")
call s:Def_map('v', ']tek', '<Plug>TxtfmtFwdTillBgcEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ek', 'f', 1)<CR>")
call s:Def_map('v', '[tea', '<Plug>TxtfmtBckTillAnyEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ea', 'b', 1)<CR>")
call s:Def_map('v', ']tea', '<Plug>TxtfmtFwdTillAnyEndTok', ":<C-U>call <SID>Jump_to_tok('v', 'ea', 'f', 1)<CR>")
" >>>
" operator-pending mode jump 'till' token mappings <<<
" Note: 'v' can be used with these to toggle inclusive/exclusive
call s:Def_map('o', '[tbf', '<Plug>TxtfmtBckTillFmtBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bf', 'b', 1)<CR>")
call s:Def_map('o', ']tbf', '<Plug>TxtfmtFwdTillFmtBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bf', 'f', 1)<CR>")
call s:Def_map('o', '[tbc', '<Plug>TxtfmtBckTillClrBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bc', 'b', 1)<CR>")
call s:Def_map('o', ']tbc', '<Plug>TxtfmtFwdTillClrBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bc', 'f', 1)<CR>")
call s:Def_map('o', '[tbk', '<Plug>TxtfmtBckTillBgcBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bk', 'b', 1)<CR>")
call s:Def_map('o', ']tbk', '<Plug>TxtfmtFwdTillBgcBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'bk', 'f', 1)<CR>")
call s:Def_map('o', '[tba', '<Plug>TxtfmtBckTillAnyBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'ba', 'b', 1)<CR>")
call s:Def_map('o', ']tba', '<Plug>TxtfmtFwdTillAnyBegTok', ":<C-U>call <SID>Jump_to_tok('o', 'ba', 'f', 1)<CR>")
call s:Def_map('o', '[tf' , '<Plug>TxtfmtBckTillFmtTok' , ":<C-U>call <SID>Jump_to_tok('o', 'f' , 'b', 1)<CR>")
call s:Def_map('o', ']tf' , '<Plug>TxtfmtFwdTillFmtTok' , ":<C-U>call <SID>Jump_to_tok('o', 'f' , 'f', 1)<CR>")
call s:Def_map('o', '[tc' , '<Plug>TxtfmtBckTillClrTok' , ":<C-U>call <SID>Jump_to_tok('o', 'c' , 'b', 1)<CR>")
call s:Def_map('o', ']tc' , '<Plug>TxtfmtFwdTillClrTok' , ":<C-U>call <SID>Jump_to_tok('o', 'c' , 'f', 1)<CR>")
call s:Def_map('o', '[tk' , '<Plug>TxtfmtBckTillBgcTok' , ":<C-U>call <SID>Jump_to_tok('o', 'k' , 'b', 1)<CR>")
call s:Def_map('o', ']tk' , '<Plug>TxtfmtFwdTillBgcTok' , ":<C-U>call <SID>Jump_to_tok('o', 'k' , 'f', 1)<CR>")
call s:Def_map('o', '[ta' , '<Plug>TxtfmtBckTillAnyTok' , ":<C-U>call <SID>Jump_to_tok('o', 'a' , 'b', 1)<CR>")
call s:Def_map('o', ']ta' , '<Plug>TxtfmtFwdTillAnyTok' , ":<C-U>call <SID>Jump_to_tok('o', 'a' , 'f', 1)<CR>")
call s:Def_map('o', '[tef', '<Plug>TxtfmtBckTillFmtEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ef', 'b', 1)<CR>")
call s:Def_map('o', ']tef', '<Plug>TxtfmtFwdTillFmtEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ef', 'f', 1)<CR>")
call s:Def_map('o', '[tec', '<Plug>TxtfmtBckTillClrEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ec', 'b', 1)<CR>")
call s:Def_map('o', ']tec', '<Plug>TxtfmtFwdTillClrEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ec', 'f', 1)<CR>")
call s:Def_map('o', '[tek', '<Plug>TxtfmtBckTillBgcEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ek', 'b', 1)<CR>")
call s:Def_map('o', ']tek', '<Plug>TxtfmtFwdTillBgcEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ek', 'f', 1)<CR>")
call s:Def_map('o', '[tea', '<Plug>TxtfmtBckTillAnyEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ea', 'b', 1)<CR>")
call s:Def_map('o', ']tea', '<Plug>TxtfmtFwdTillAnyEndTok', ":<C-U>call <SID>Jump_to_tok('o', 'ea', 'f', 1)<CR>")
" >>>
" normal mode insert token mappings <<<
" These mappings may be used from normal mode to insert special tokens.
" Note: The first set leaves cursor in insert mode, and is probably the most
" useful. The second set enters insert mode to do the insert and puts cursor
" at correct offset prior to returning to normal mode. Works just like
" inserting the token, then hitting <Esc>.
" TODO - This one is redundant to the \vi one - use the latter instead for
" notational consistency?
call s:Def_map('n', '<C-\><C-\>', '<Plug>TxtfmtInsertTok_n',
\":<C-U>call <SID>Insert_tokstr('', 'i', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
" Start in normal / End in insert
call s:Def_map('n', '<LocalLeader>i', '<Plug>TxtfmtInsertTok_i',
\":<C-U>call <SID>Insert_tokstr('', 'i', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>I', '<Plug>TxtfmtInsertTok_I',
\":<C-U>call <SID>Insert_tokstr('', 'I', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>a', '<Plug>TxtfmtInsertTok_a',
\":<C-U>call <SID>Insert_tokstr('', 'a', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>A', '<Plug>TxtfmtInsertTok_A',
\":<C-U>call <SID>Insert_tokstr('', 'A', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>o', '<Plug>TxtfmtInsertTok_o',
\":<C-U>call <SID>Insert_tokstr('', 'o', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>O', '<Plug>TxtfmtInsertTok_O',
\":<C-U>call <SID>Insert_tokstr('', 'O', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>s', '<Plug>TxtfmtInsertTok_s',
\":<C-U>call <SID>Insert_tokstr('', 's', 0, 0)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
" Start in normal / End in normal
call s:Def_map('n', '<LocalLeader>vi', '<Plug>TxtfmtInsertTok_vi',
\":<C-U>call <SID>Insert_tokstr('', 'i', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>vI', '<Plug>TxtfmtInsertTok_vI',
\":<C-U>call <SID>Insert_tokstr('', 'I', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>va', '<Plug>TxtfmtInsertTok_va',
\":<C-U>call <SID>Insert_tokstr('', 'a', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>vA', '<Plug>TxtfmtInsertTok_vA',
\":<C-U>call <SID>Insert_tokstr('', 'A', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>vo', '<Plug>TxtfmtInsertTok_vo',
\":<C-U>call <SID>Insert_tokstr('', 'o', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>vO', '<Plug>TxtfmtInsertTok_vO',
\":<C-U>call <SID>Insert_tokstr('', 'O', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
call s:Def_map('n', '<LocalLeader>vs', '<Plug>TxtfmtInsertTok_vs',
\":<C-U>call <SID>Insert_tokstr('', 's', 0, 1)<CR>"
\.":call <SID>Adjust_cursor()<CR>")
" >>>
" insert mode insert token mappings <<<
" NOTE: Default is to use something that wouldn't be typed as text for the
" insert mode map. User may wish to remap this one to a Function key or
" something else entirely. I find <C-\><C-\> very easy to type...
call s:Def_map('i', '<C-\><C-\>', '<Plug>TxtfmtInsertTok_i',
\"<C-R>=<SID>Insert_tokstr('', 'i', 0, 0)<CR>"
\."<C-R>=<SID>Adjust_cursor()<CR>")
" >>>
" normal mode get token info mapping <<<
call s:Def_map('n', '<LocalLeader>ga', '<Plug>TxtfmtGetTokInfo',
\":<C-U>echo <SID>GetTokInfo()<CR>")
" >>>
" NOTES <<<
" -enterinsert default is 'i'
" -mode default is 'ni'
" -<C-0> can't be used in insert-mode mapping for some reason...
" >>>
" TODO <<<
" -Convert ASCII only pattern character classes to ones that will work with
" multi-byte chars
" -Add commands/functions for detecting and altering the range of character
" codes used for txtfmt tokens.
" -Use syntax clusters instead of the double definition trickery I used when I
" didn't know about syntax clusters.
" >>>
" >>>
" Restore compatibility options <<<
" Restore compatibility options to what they were
let &cpo = s:save_cpo
" >>>
" vim: sw=4 ts=4 foldmethod=marker foldmarker=<<<,>>> :