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

3105 lines
123 KiB
VimL
Raw Permalink 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 global plugin file, which contains configuration code
" needed by both the ftplugin and the syntax files.
" Creation: 2004 Nov 06
" Last Change: 2010 Sep 18
" Maintainer: Brett Pershing Stahlman <brettstahlman@comcast.net>
" License: This file is placed in the public domain.
" Note: The following line is required by a packaging script
let g:Txtfmt_Version = "2.4"
" Autocommands needed by refresh mechanism <<<
au FileType * call s:Txtfmt_save_filetype()
au Syntax * call s:Txtfmt_save_syntax()
fu! s:Txtfmt_save_filetype()
let l:filetype = expand("<amatch>")
if l:filetype =~ '\%(^\|\.\)txtfmt\%(\.\|$\)'
let b:txtfmt_filetype = l:filetype
endif
endfu
fu! s:Txtfmt_save_syntax()
let l:syntax = expand("<amatch>")
if l:syntax =~ '\%(^\|\.\)txtfmt\%(\.\|$\)'
let b:txtfmt_syntax = l:syntax
endif
endfu
" >>>
" Constants needed regardless of whether common config is being performed <<<
" The following is passed to s:New_window() to construct the test page
let s:TESTPAGE_WINHEIGHT = 20
" >>>
" Functions needed regardless of whether common config is being performed <<<
" Function: s:Is_txtfmt_modeline() <<<
" Purpose: Return nonzero if and only if the line whose text is input "looks
" like" a txtfmt modeline.
" Note: Option names and values are NOT validated. That function is performed
" by s:Process_txtfmt_modeline.
" Inputs:
" linestr -A line of text that may or may not represent a txtfmt modeline.
" Note: Format of the modeline is as follows:
" (Based upon 1st Vim modeline format)
" [text]{white}txtfmt:[white]{options}
" {options} can be the following:
" {tokrange|rng}=<hex>|<dec>[sSlL]
" {escape|esc}={self|bslash|none}
" {sync}={<hex>|<dec>|fromstart|none}
" {bgcolor|bg}=<color>
" {nested|nst}
" {concealcursor|cocu}=[n][v][i][c]
" Must be no whitespace surrounding the '='
" Also: Since Vim will not choke on a trailing ':' (though it's not
" technically part of the 1st modeline format), neither will I.
" Define regexes used to process modeline
" Also: ' txtfmt:' with no options is not an error, but will not be recognized
" as a modeline. Rationale: If user doesn't define any options in a modeline,
" assume it's just text that looks like the start of a modeline.
let s:re_ml_start = '\%(.\{-}\%(\s\+txtfmt:\s*\)\)'
let s:re_ml_name = '\%(\I\i*\)'
let s:re_ml_val = '\%(\%(\\.\|[^:[:space:]]\)*\)'
let s:re_ml_el = '\%('.s:re_ml_name.'\%(='.s:re_ml_val.'\)\?\)'
let s:re_ml_elsep = '\%(\s\+\|\s*:\s*\)'
let s:re_ml_end = '\%(\s*:\?\s*$\)'
" Construct pattern matching entire line, which will capture all leading text
" in \1, all options in \2, and all trailing text in \3
let s:re_modeline = '\('.s:re_ml_start.'\)\('
\.s:re_ml_el.'\%('.s:re_ml_elsep.s:re_ml_el.'\)*\)'
\.'\('.s:re_ml_end.'\)'
fu! s:Is_txtfmt_modeline(linestr)
return a:linestr =~ s:re_modeline
endfu
" >>>
" Function: s:New_window <<<
" Description: Attempt to create display window of at least the
" specified size for things like test page and tutorial. If possible,
" don't change the height of existing windows (other than the current
" one). Note that this is easy to accomplish, even when 'ea' is set,
" because the `:N new' form effectively overrides 'ea'. If the current
" window is small, we may not be able to avoid shrinking the height of
" existing windows; fortunately, when the `:N new' form is used, Vim
" shrinks as few windows as possible, starting with the current one.
" Note On Size Calculation: Making a *precise" determination as to
" whether we could make the new window higher than requested height by
" halfing the current window would require special logic involving the
" 'laststatus' option. Fortunately, it is not necessary to make a
" precise determination. We can simply assume worst-case: i.e., the
" original winheight() will be reduced by 2 statuslines when the new
" window is created. This corresponds to the case in which 'laststatus'
" == 1, and there's only 1 window before the new one is created.
" Rationale: Using this simplification may result in our sometimes
" creating a window with exactly the requested height, when we might
" have made it a line or so higher, but this is not a problem, and the
" extra logic required to 'fix' it is not justified.
" Important Note: We need to check for error generated by the :new
" command. In particular, E36 will be generated if there's not enough
" space to create the new window. Note that the rules Vim uses to
" determine whether space is sufficient depend upon current setting of
" 'ea').
" Return: Nonzero on success, zero on failure
" Error: Set s:err_str and return zero
fu! s:New_window(height)
let avail_height = winheight(0) - 2
" Determine size of window to create
if avail_height > 2 * a:height
" Make test page half current window height (which should be >=
" requested height)
let create_height = avail_height / 2
else
" Make test page exactly the requested height
let create_height = a:height
endif
" Create the window
let v:errmsg = ''
exe 'silent! ' . create_height . 'new'
" Check for E36
if v:errmsg != ''
" Make error string available to caller and return false
let s:err_str = v:errmsg
return 0
else
" return true for Success
return 1
endif
endfu
" >>>
" >>>
" IMPORTANT NOTE: The common configuration code in this file is intended to be
" executed only upon request by either the txtfmt ftplugin or syntax file.
" Since this file resides in the Vim plugin directory, it will be sourced by
" Vim automatically whenever Vim is started; the common configuration code,
" however, will not be executed at Vim startup because the special txtfmt
" variable b:txtfmt_do_common_config will not be set at that time. When either
" the ftplugin or syntax file wishes to execute the code in this script, it
" sets b:txtfmt_do_common_config and uses :runtime to source this file. When
" the common configuration code within this file executes, it makes its output
" available to both ftplugin and syntax files via buffer-local variables.
if exists('b:txtfmt_do_common_config')
" Needed for both ftplugin and syntax
" Command: Refresh <<<
com! -buffer Refresh call s:Txtfmt_refresh()
" >>>
" Autocommands <<<
" Ensure that common configuration will be redone whenever the txtfmt buffer
" is re-read (e.g. after a txtfmt-modeline has been changed).
" Note: This autocmd allows user simply to do ":e" at the Vim command line to
" resource everything, including the option processing in common config.
augroup TxtfmtCommonConfig
au BufReadPre <buffer> :unlet! b:txtfmt_did_common_config
augroup END
" >>>
" Common constant definitions <<<
" Define first Vim version that supported undercurl
let b:txtfmt_const_vimver_undercurl = 700
" Define several sets of constants needed by the functions used to process
" 'tokrange' option.
" Define tokrange defaults (decomposed into 'starttok' and 'formats' values)
" as a function of encoding class.
" Note: starttok encoded as string to preserve dec or hex display preference
let b:txtfmt_const_starttok_def_{'1'} = '180'
let b:txtfmt_const_formats_def_{'1'} = 'X'
let b:txtfmt_const_starttok_def_{'2'} = '180'
let b:txtfmt_const_formats_def_{'2'} = 'X'
let b:txtfmt_const_starttok_def_{'u'} = '0xE000'
let b:txtfmt_const_formats_def_{'u'} = 'X'
" Define the number of tokens in the txtfmt token range as a function of
" the format variant flags (which determine num_attributes in the equations
" below):
" --no background colors--
" N = 2 ^ {num_attributes} + 9
" --background colors--
" N = 2 ^ {num_attributes} + 9 + 9
" Important Note: The numbers are not dependent upon the number of active fg
" and bg colors, since we always reserve 8 colors.
" The 3 parameters in the following constants represent the following 3 format
" variant flags:
" txtfmt_cfg_bgcolor
" txtfmt_cfg_longformats
" txtfmt_cfg_undercurl
" Note: Value supplied for longformats and undercurl indices must take 'pack'
" option into account.
" TODO: Fix the final 3 elements or get rid of this array altogether. Note
" that it doesn't take 'pack' option into account
let b:txtfmt_const_tokrange_size_{0}{0}{0} = 17
let b:txtfmt_const_tokrange_size_{0}{1}{0} = 41
let b:txtfmt_const_tokrange_size_{0}{1}{1} = 73
let b:txtfmt_const_tokrange_size_{1}{0}{0} = 26
let b:txtfmt_const_tokrange_size_{1}{1}{0} = 82
let b:txtfmt_const_tokrange_size_{1}{1}{1} = 82
" Make sure we can easily deduce the suffix from the format variant flags
let b:txtfmt_const_tokrange_suffix_{0}{0}{0} = 'S'
let b:txtfmt_const_tokrange_suffix_{0}{1}{0} = 'L'
let b:txtfmt_const_tokrange_suffix_{0}{1}{1} = 'L'
let b:txtfmt_const_tokrange_suffix_{1}{0}{0} = 'X'
let b:txtfmt_const_tokrange_suffix_{1}{1}{0} = 'XL'
let b:txtfmt_const_tokrange_suffix_{1}{1}{1} = 'XL'
" Define the maximum character code that may be used as a txtfmt token as a
" function of encoding class. (Encoding class is specified as '1' for single
" byte, '2' for 16-bit and 'u' for unicode.)
" Note: Vim doesn't support unicode chars larger than 16-bit.
let b:txtfmt_const_tokrange_limit_{'1'} = 0xFF
let b:txtfmt_const_tokrange_limit_{'2'} = 0xFFFF
let b:txtfmt_const_tokrange_limit_{'u'} = 0xFFFF
" The following regex describes a {number} that may be used to set a numeric
" option. The txtfmt help specifies that only decimal or hexadecimal formats
" are permitted.
let b:txtfmt_re_number_atom = '\([1-9]\d*\|0x\x\+\)'
" >>>
" General utility functions <<<
" Function: s:Repeat() <<<
" Purpose: Generate a string consisting of the input string repeated the
" requested number of times.
" Note: Vim7 added a repeat() function, but I'm intentionally not using
" anything added post-Vim6.
" Input:
" str - string to repeat
" cnt - number of times to repeat it
" Return: The generated string
fu! s:Repeat(str, cnt)
let ret_str = ''
let i = 0
while i < a:cnt
let ret_str = ret_str . a:str
let i = i + 1
endwhile
return ret_str
endfu
" >>>
" >>>
" Parse_<...> functions <<<
" Function: s:Parse_init()
" Purpose:
" Input:
" text - string to be parsed
" re_tok - regex matching a token
" Return: ID that must be passed to the other parse functions (-1 indicates
" error)
" Simulated struct:
" text
" len
" re
" pos
fu! s:Parse_init(text, re_tok)
let i = 0
let MAX_PARSE_INSTANCES = 1000
while i < MAX_PARSE_INSTANCES
if !exists('s:parsedata_'.i.'_text')
" Found a free one
let s:parsedata_{i}_text = a:text
let s:parsedata_{i}_len = strlen(a:text) " for speed
let s:parsedata_{i}_re = a:re_tok
let s:parsedata_{i}_pos = 0
return i
endif
let i = i + 1
endwhile
if i >= MAX_PARSE_INSTANCES
echoerr "Internal Parse_init error - contact developer"
return -1
endif
endfu
" Function: s:Parse_nexttok()
" Purpose:
" Input:
" Return: The next token as a string (or empty string if no more tokens)
" Error: Not possible when used correctly, and since this is an internally
" used function, we will not check for error.
fu! s:Parse_nexttok(parse_id)
" Note: Structures used and generated internally in controlled manner, so
" assume parse_id points to valid struct
let text = s:parsedata_{a:parse_id}_text
let re = s:parsedata_{a:parse_id}_re
let pos = s:parsedata_{a:parse_id}_pos
let len = s:parsedata_{a:parse_id}_len
let parse_complete = 0 " set if text exhausted
" Did last call exhaust text?
if pos >= len
let parse_complete = 1
let ret_str = ''
else
" text not exhausted yet - get past any whitespace
" ^\s* will return pos if no whitespace at pos (cannot return -1)
let pos = matchend(text, '^\s*', pos)
" Did we move past trailing whitespace?
if pos >= len
let parse_complete = 1
let ret_str = ''
else
" We're sitting on first char to be returned.
" re determines how many more will be part of return str
" Note: Force re to match at current pos if at all
let pos2 = matchend(text, '^'.re, pos)
if pos2 < 0
" curr char is not part of a token, so just return it by itself
let ret_str = text[pos]
let pos = pos + 1
else
" Return the token whose end was located
let ret_str = strpart(text, pos, pos2-pos)
let pos = pos2
endif
endif
endif
" Only way out of this function
if parse_complete
call s:Parse_free(a:parse_id)
else
" Update pos in structure for next call...
let s:parsedata_{a:parse_id}_pos = pos
endif
return ret_str
endfu
" Function: s:Parse_free()
" Purpose: Free the data structures for a particular parse instance (denoted
" by input id)
" Input: parse_id - parse instance whose data is to be freed
" Return: none
" Error: not possible
fu! s:Parse_free(parse_id)
" Using unlet! ensures that error not possible
unlet! s:parsedata_{a:parse_id}_text
unlet! s:parsedata_{a:parse_id}_re
unlet! s:parsedata_{a:parse_id}_pos
unlet! s:parsedata_{a:parse_id}_len
endfu
" >>>
" Configuration utility functions (common) <<<
" >>>
" encoding utility functions (common) <<<
let s:re_encs_1 = '^\%('
\.'latin1\|iso-8859-n\|koi8-r\|koi8-u'
\.'\|macroman\|cp437\|cp737\|cp775'
\.'\|cp850\|cp852\|cp855\|cp857'
\.'\|cp860\|cp861\|cp862\|cp863'
\.'\|cp865\|cp866\|cp869\|cp874'
\.'\|cp1250\|cp1251\|cp1253\|cp1254'
\.'\|cp1255\|cp1256\|cp1257\|cp1258'
\.'\)$'
let s:re_encs_2 = '^\%('
\.'cp932\|euc-jp\|sjis\|cp949'
\.'\|euc-kr\|cp936\|euc-cn\|cp950'
\.'\|big5\|euc-tw'
\.'\)'
let s:re_encs_u = '^\%('
\.'utf-8\|ucs-2\|ucs-2le\|utf-16\|utf-16le\|ucs-4\|ucs-4le'
\.'\)'
" Function: TxtfmtCommon_Encoding_get_class() <<<
" Purpose: Return single character indicating whether the input encoding name
" represents a 1-byte encoding ('1'), a 2-byte encoding ('2'), or a unicode
" encoding ('u'). If input encoding name is unrecognized, return empty string.
fu! TxtfmtCommon_Encoding_get_class(enc)
if a:enc =~ s:re_encs_1
return '1'
elseif a:enc =~ s:re_encs_2
return '2'
elseif a:enc =~ s:re_encs_u
return 'u'
else
return ''
endif
endfu
" >>>
" >>>
" 'tokrange' utility functions <<<
" Construct pattern that will capture char code in \1 and optional size
" specification (sSlLxX) in \2.
if exists('g:txtfmtAllowxl') && g:txtfmtAllowxl
" Note: By default, XL suffix is illegal, but user has overridden the
" default
let s:re_tokrange_spec = '^\([1-9]\d*\|0x\x\+\)\(\%([sSlL]\|[xX][lL]\?\)\?\)$'
else
let s:re_tokrange_spec = '^\([1-9]\d*\|0x\x\+\)\([sSlLxX]\?\)$'
endif
" Function: s:Tokrange_is_valid() <<<
" Purpose: Indicate whether input string is a valid tokrange spec.
fu! s:Tokrange_is_valid(spec)
return a:spec =~ s:re_tokrange_spec
endfu
" >>>
" Function: s:Tokrange_get_formats() <<<
" Purpose: Return 'formats' component of the input tokrange spec.
" Note: If optional 'formats' component is omitted, empty string will be
" returned.
" Note: formats spec will be returned in canonical form (uppercase).
" Assumption: Input string has already been validated by s:Tokrange_is_valid.
fu! s:Tokrange_get_formats(spec)
return substitute(a:spec, s:re_tokrange_spec, '\U\2', '')
endfu
" >>>
" Function: s:Tokrange_translate_tokrange() <<<
" Description: Decompose the input tokrange into its constituent parts,
" setting all applicable option variables:
" b:txtfmt_cfg_starttok
" b:txtfmt_cfg_bgcolor
" b:txtfmt_cfg_longformats
" b:txtfmt_cfg_undercurl
" b:txtfmt_cfg_starttok_display
" b:txtfmt_cfg_formats_display
fu! s:Tokrange_translate_tokrange(tokrange)
" Extract starttok and formats from input tokrange
let starttok_str = substitute(a:tokrange, s:re_tokrange_spec, '\1', '')
let formats_str = substitute(a:tokrange, s:re_tokrange_spec, '\2', '')
" Decompose starttok into a numeric and a display portion
let b:txtfmt_cfg_starttok = 0 + starttok_str
let b:txtfmt_cfg_starttok_display = starttok_str
" Decompose formats into constituent flags and a display portion
" Note: Formats is a bit special. For one thing, it can be omitted from a
" tokrange spec, in which case we'll choose a default. Also, long formats
" ('L') can mean either 'all' or 'all_but_undercurl', depending upon Vim
" version and b:txtfmt_cfg_undercurlpref. (Undercurl was not supported
" until Vim version 7.0, and we allow user to disable it with the
" 'undercurl' option even when it is supported.)
" Note: b:txtfmt_cfg_undercurl is always set explicitly to 0 when it
" doesn't apply, so that it can be used in parameterized variable names.
if strlen(formats_str) == 0
" Format suffix was omitted. Default to 'extended' formats (background
" colors with short formats)
let b:txtfmt_cfg_bgcolor = 1
let b:txtfmt_cfg_longformats = 0
let b:txtfmt_cfg_undercurl = 0
let b:txtfmt_cfg_formats_display = 'X'
elseif formats_str ==? 'L'
" Long formats with no background colors
let b:txtfmt_cfg_bgcolor = 0
let b:txtfmt_cfg_longformats = 1
if v:version >= b:txtfmt_const_vimver_undercurl && b:txtfmt_cfg_undercurlpref
let b:txtfmt_cfg_undercurl = 1
else
let b:txtfmt_cfg_undercurl = 0
endif
let b:txtfmt_cfg_formats_display = 'L'
elseif formats_str ==? 'X'
" Background colors with short formats
let b:txtfmt_cfg_bgcolor = 1
let b:txtfmt_cfg_longformats = 0
let b:txtfmt_cfg_undercurl = 0
let b:txtfmt_cfg_formats_display = 'X'
elseif formats_str ==? 'XL'
" Background colors with long formats
let b:txtfmt_cfg_bgcolor = 1
let b:txtfmt_cfg_longformats = 1
if v:version >= b:txtfmt_const_vimver_undercurl && b:txtfmt_cfg_undercurlpref
let b:txtfmt_cfg_undercurl = 1
else
let b:txtfmt_cfg_undercurl = 0
endif
" Note: This is no longer legal!!!!
let b:txtfmt_cfg_formats_display = 'XL'
else
" Short formats
let b:txtfmt_cfg_bgcolor = 0
let b:txtfmt_cfg_longformats = 0
let b:txtfmt_cfg_undercurl = 0
let b:txtfmt_cfg_formats_display = 'S'
endif
endfu
" >>>
" Function: s:Tokrange_size() <<<
" Note: Now that I'm reserving 8 colors even when numfgcolors and numbgcolors
" are less than 8, this function can probably be removed, or at least renamed
" (e.g., Tokrange_used_size).
fu! s:Tokrange_size(formats)
return b:txtfmt_const_tokrange_size_{a:formats}
endfu
" >>>
" >>>
" 'sync' utility functions <<<
" Construct pattern that will validate the option value.
let s:re_sync_number_spec = '^\([1-9]\d*\|0x\x\+\)$'
let s:re_sync_name_spec = '^fromstart\|none$'
" Function: s:Sync_is_valid() <<<
" Purpose: Indicate whether input string is a valid sync option value
fu! s:Sync_is_valid(spec)
return a:spec =~ s:re_sync_number_spec || a:spec =~ s:re_sync_name_spec
endfu
" >>>
" Function: s:Sync_get_method() <<<
" Purpose: Determine the syncmethod represented by the input sync value and
" return its name. Possible values: 'minlines', 'fromstart', 'none'
" Assumption: Input string has already been validated by s:Sync_is_valid.
fu! s:Sync_get_method(spec)
if a:spec =~ s:re_sync_name_spec
return a:spec
else
return 'minlines'
endif
endfu
" >>>
" >>>
" 'escape' utility functions <<<
" Construct pattern that will validate the option value.
let s:re_escape_optval = '^\%(none\|bslash\|self\)$'
" Function: s:Escape_is_valid() <<<
" Purpose: Indicate whether input string is a valid escape option value
fu! s:Escape_is_valid(optval)
return a:optval =~ s:re_escape_optval
endfu
" >>>
" >>>
" 'concealcursor' utility functions <<<
" Construct pattern that will validate the option value.
let s:re_concealcursor_optval = '^[nvic]*$'
" Function: s:Concealcursor_is_valid() <<<
" Purpose: Indicate whether input string is a valid concealcursor option value
fu! s:Concealcursor_is_valid(optval)
return a:optval =~ s:re_concealcursor_optval
endfu
" >>>
" >>>
" Number validation utility functions <<<
" Function: s:Number_is_valid(s)
" Purpose: Indicate whether the input string represents a valid number
fu! s:Number_is_valid(s)
return a:s =~ '^\s*'.b:txtfmt_re_number_atom.'\s*$'
endfu
" >>>
" Num fg/bg colors validation utility function <<<
let s:re_num_clrs = '^\%(0x\)\?[0-8]$'
fu! s:Numclrs_is_valid(s)
return a:s =~ s:re_num_clrs
endfu
" >>>
" fg/bg color mask validation utility function <<<
let s:re_clr_mask = '^[01]\{8}$'
fu! s:Clrmask_is_valid(s)
return a:s =~ s:re_clr_mask
endfu
" >>>
" Function: s:Set_tokrange() <<<
" Purpose: Set b:txtfmt_cfg_starttok, b:txtfmt_cfg_bgcolor,
" b:txtfmt_cfg_longformats and b:txtfmt_cfg_undercurl options, taking into
" account any user-setting of 'tokrange' option, and if necessary, the txtfmt
" defaults, which may take 'encoding' into consideration.
" Note: If set via modeline, tokrange option value must be a literal tokrange
" specification; however, buf-local and global option variables may be set to
" either a valid tokrange spec, or a Vim expression that evaluates to one.
" Permitting arbitrary Vim expressions facilitates the use of complex tokrange
" selection logic, implemented by a user-defined expression or function.
" Examples:
" 'g:My_tokrange_calculator()'
" '&enc == "utf-8" ? "1400l" : "130s"'
fu! s:Set_tokrange()
" Undef variables that are outputs of this function. Note that these
" variables are the decomposition of b:txtfmt_cfg_tokrange, which may or
" may not be set at this point.
unlet! b:txtfmt_cfg_starttok
\ b:txtfmt_cfg_bgcolor b:txtfmt_cfg_longformats b:txtfmt_cfg_undercurl
\ b:txtfmt_cfg_starttok_display b:txtfmt_cfg_formats_display
" Cache the 'encoding' in effect
let enc = &encoding
" Determine the corresponding encoding class
let enc_class = TxtfmtCommon_Encoding_get_class(enc)
if !exists('b:txtfmt_cfg_tokrange') || strlen(b:txtfmt_cfg_tokrange) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value.
if exists('b:txtfmt_cfg_tokrange') && strlen(b:txtfmt_cfg_tokrange) == 0
" Bad modeline set
let l:warnmsg =
\"Warning: Ignoring invalid modeline value for txtfmt `tokrange' option"
elseif exists('b:txtfmtTokrange')
" User overrode buf-local option. Save the option for validation
" below...
let b:txtfmt_cfg_tokrange = b:txtfmtTokrange
let l:set_by = 'b'
elseif exists('g:txtfmtTokrange')
" User overrode global option. Save the option for validation
" below...
let b:txtfmt_cfg_tokrange = g:txtfmtTokrange
let l:set_by = 'g'
endif
endif
if exists('l:set_by') && (l:set_by == 'b' || l:set_by == 'g')
" Perform special validation for buf-local/global settings, which
" permits either a tokrange spec or a Vim expression that evaluates to
" one.
if !s:Tokrange_is_valid(b:txtfmt_cfg_tokrange)
" Not a valid tokrange literal. Let's see whether it evaluates to
" one.
let v:errmsg = ''
" Evaluate expression, using silent! to prevent problems in the
" event that rhs is invalid.
silent! exe 'let l:tokrange = '.b:txtfmt_cfg_tokrange
if v:errmsg != ''
" Bad expression
let l:warnmsg =
\"Warning: Ignoring invalid ".(l:set_by == 'b' ? 'buf-local' : 'global')
\." value for txtfmt 'tokrange' option: ".b:txtfmt_cfg_tokrange
" Discard the invalid setting
let b:txtfmt_cfg_tokrange = ''
else
" It was a valid Vim expression. Did it produce a valid
" tokrange spec?
if !s:Tokrange_is_valid(l:tokrange)
let l:warnmsg =
\"Ignoring ".(l:set_by == 'b' ? 'buf-local' : 'global')
\." set of txtfmt `tokrange' option: `".b:txtfmt_cfg_tokrange
\."' produced invalid option value: ".l:tokrange
" Discard the invalid setting
let b:txtfmt_cfg_tokrange = ''
else
" Save the valid setting
let b:txtfmt_cfg_tokrange = l:tokrange
endif
endif
endif
endif
" Warn user if invalid user-setting is about to be overridden
if exists('l:warnmsg')
echoerr l:warnmsg
endif
" Decompose any valid user setting stored in b:txtfmt_cfg_tokrange.
" Note: The output from the preceding stage is b:txtfmt_cfg_tokrange,
" which we must now decompose into b:txtfmt_cfg_starttok,
" b:txtfmt_cfg_bgcolor, b:txtfmt_cfg_longformats, b:txtfmt_cfg_undercurl,
" b:txtfmt_cfg_starttok_display and b:txtfmt_cfg_formats_display. If
" b:txtfmt_cfg_tokrange is nonexistent or null, there is no valid user
" setting, in which case, we'll supply default.
if exists('b:txtfmt_cfg_tokrange') && strlen(b:txtfmt_cfg_tokrange)
" Decompose valid tokrange setting via s:Tokrange_translate_tokrange,
" which sets all constituent variables.
call s:Tokrange_translate_tokrange(b:txtfmt_cfg_tokrange)
" Perform upper-bound validation
if b:txtfmt_cfg_starttok +
\ b:txtfmt_const_tokrange_size_{b:txtfmt_cfg_bgcolor}{b:txtfmt_cfg_longformats}{b:txtfmt_cfg_undercurl}
\ - 1
\ > b:txtfmt_const_tokrange_limit_{enc_class}
" Warn user and use default
echoerr
\ "Warning: Tokrange value '".b:txtfmt_cfg_tokrange."' causes upper"
\." bound to be exceeded for encoding ".&enc
" Make sure we set to default below
" Note: It suffices to unlet b:txtfmt_cfg_starttok, since its
" nonexistence will ensure that all constituent vars are set below
unlet! b:txtfmt_cfg_starttok
endif
endif
" If b:txtfmt_cfg_starttok is still undefined, see whether there's an
" encoding-specific default.
" Note: b:txtfmt_cfg_starttok was unlet at the top of this function, so it
" will be undefined unless it's been set successfully.
if !exists('b:txtfmt_cfg_starttok')
" TODO - Put any logic that depends upon specific encoding here...
" .
" .
endif
" If b:txtfmt_cfg_starttok is still undefined, see whether there's an
" encoding-class-specific default.
if !exists('b:txtfmt_cfg_starttok')
" If encoding class is unrecognized, default to '1'
if enc_class == ''
let use_enc_class = '1'
else
let use_enc_class = enc_class
endif
" Pass default tokrange to function that will decompose it and set all
" constituent variables.
call s:Tokrange_translate_tokrange(
\ b:txtfmt_const_starttok_def_{use_enc_class}
\ . b:txtfmt_const_formats_def_{use_enc_class}
\)
endif
" Note: If b:txtfmt_cfg_tokrange exists, we are done with it now that it
" has been completely decomposed
unlet! b:txtfmt_cfg_tokrange
" Save the encoding class for later use (will be needed by Define_syntax
" logic used to determine whether syn match offsets are byte or
" char-based)
let b:txtfmt_cfg_enc_class = enc_class
" Also save encoding itself. If we're using a cached copy of encoding
" class, we should be able to verify that the encoding upon which it is
" based is the currently active one.
let b:txtfmt_cfg_enc = enc
endfu
" >>>
" Function: s:Set_syncing() <<<
" Purpose: Set b:txtfmt_cfg_syncmethod and (if applicable) b:txtfmt_cfg_synclines
" options, according to the following logic:
" 1) If user set sync option via modeline, buffer-local option, or global
" option, attempt to use the setting with the highest priority.
" 2) If step 1 fails to set the option, either because of error or because the
" user made no attempt to set, default to minlines=250
" Note: From a user perspective, there is only the 'sync' option. For
" convenience within the plugin, we break this single option into two options:
" 'syncmethod' and 'synclines'. Currently, 'synclines' is used only when
" syncmethod=minlines.
fu! s:Set_syncing()
if !exists('b:txtfmt_cfg_sync') || strlen(b:txtfmt_cfg_sync) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value.
if exists('b:txtfmt_cfg_sync') && strlen(b:txtfmt_cfg_sync) == 0
" Bad modeline set
let l:bad_set_by = 'm'
elseif exists('b:txtfmtSync')
" User overrode buf-local option
if s:Sync_is_valid(b:txtfmtSync)
let b:txtfmt_cfg_sync = b:txtfmtSync
else
let l:bad_set_by = 'b'
endif
elseif exists('g:txtfmtSync')
" User overrode global option
if s:Sync_is_valid(g:txtfmtSync)
let b:txtfmt_cfg_sync = g:txtfmtSync
else
let l:bad_set_by = 'g'
endif
endif
endif
" Warn user if invalid user-setting is about to be overridden
if exists('l:bad_set_by')
" Note: Display the offending option value for buf-local or global
" option, but not for modeline, since modeline processing has already
" reported the error.
echoerr "Warning: Ignoring invalid ".(
\ l:bad_set_by == 'm' ? "modeline" :
\ l:bad_set_by == 'b' ? "buf-local" :
\ "global") . " value for txtfmt `sync' option" . (
\ l:bad_set_by == 'm' ? '' :
\ l:bad_set_by == 'b' ? (': ' . b:txtfmtSync) :
\ (': ' . g:txtfmtSync))
endif
if !exists('b:txtfmt_cfg_sync') || strlen(b:txtfmt_cfg_sync) == 0
" Set to default
let b:txtfmt_cfg_syncmethod = 'minlines'
let b:txtfmt_cfg_synclines = 250
else
" Decompose validated 'sync' option into 'syncmethod' and (if
" applicable) 'synclines'
let b:txtfmt_cfg_syncmethod = s:Sync_get_method(b:txtfmt_cfg_sync)
if b:txtfmt_cfg_syncmethod == 'minlines'
" Save the number of lines
let b:txtfmt_cfg_synclines = (0 + b:txtfmt_cfg_sync)
endif
endif
" We're done with b:txtfmt_cfg_sync now that it has been completely
" decomposed.
unlet! b:txtfmt_cfg_sync
endfu
" >>>
" Function: s:Translate_color_optstr() <<<
" Purpose: Process the string representing a single element from the array
" txtfmtColor{1..8}, and return the extracted information.
" Return: A comma-separated string containing the extracted information as
" follows:
" <namepat>,<ctermfg_rhs>,<guifg_rhs>
" Note that ctermfg_rhs and/or guifg_rhs may be blank, in the event that user
" specified term patterns and none of them matched. (If no term patterns are
" specified, there is an implied match with current &term value.)
" Note that return string will have commas and backslashes escaped.
" Note that the last color def that matches for each of guifg and ctermfg is
" the one that is returned to caller.
" Error: Set s:err_str and return empty string
" Details:
" Here is the format of a single string in the txtfmtColor{} array:
" <namepat>,<clrdef1>[,<clrdef2>,...,<clrdefN>]
" <clrdef> :=
" <c|g>[<termpatlist>]:<clrstr>
" <termpatlist> :=
" :<termpat1>:<termpat2>:...:<termpatN>
" *** Parse table ***
" st next_st can_end? tok
" 0 1 n <namepat>
" 1 2 n ,
" 2 3 n <c|g>
" 3 4 n :
" 4 5 n str
" 5 4 y :
" 5 2 y ,
" Example color optstr:
" red,c:xterm:dosterm:DarkRed,g:builtin_gui:other_gui:#FF0000
"
" Here are the meanings of the fields:
" <namepat> -regex used to recognize the token used to specify a
" certain color; e.g., when prompted by one of the insert token maps. May
" not contain spaces or commas.
" Example: k\|b\%[lack]
" <c|g> -specifies whether clrstr will be the rhs of a
" "ctermfg=" ('c'), or "guifg=" ('g')
" <termpat> -pattern used to match against &term option. It is a regex pattern
" which will be applied as ^<termpat>$
" <clrstr> -rhs of a "ctermfg=" or "guifg=" assignment.
" :help gui-colors | help cterm-colors
" Additional Note:
" Commas, colons, and backslashes appearing in fields must be
" backslash-escaped.
" Note: Due to the extra backslash escaping, it is recommended to use a
" string-literal, rather than double-quoted string.
fu! s:Translate_color_optstr(optstr)
" optstr is the string to be parsed
" Initialize the state machine
let pst = 0
" Initialize the parse engine to consider tokens to be broken only at
" unescaped commas and colons.
let pid = s:Parse_init(a:optstr, '\%(\\.\|[^,:]\)\+')
if pid < 0
let s:err_str = 'Internal error within s:Translate_color_optstr(). Contact developer.'
echomsg 'Internal error'
return ''
endif
" Extract and handle tokens in a loop
let parse_complete = 0
" The following 2 will be set in loop (hopefully at least 1 anyways)
let ctermfg_rhs = ''
let guifg_rhs = ''
while !parse_complete
let tok = s:Parse_nexttok(pid)
" Note: Could handle end of string here for states in which end of
" string is illegal - but that would make it more difficult to
" formulate meaningful error messages.
"if tok == '' && pst != 5
"endif
" Switch on the current state
if pst == 0 " Extract non empty namepat
let namepat = substitute(tok, '\\\(.\)', '\1', 'g')
if namepat =~ '^[[:space:]]*$'
let s:err_str = "Color def string must contain at least 1 non-whitespace char"
return ''
endif
let pst = 1
elseif pst == 1
if tok == ','
let pst = 2
elseif tok == ''
let s:err_str = "Expected comma, encountered end of color def string"
return ''
else
let s:err_str = "Expected comma, got '".tok."'"
return ''
endif
elseif pst == 2
if tok == 'c' || tok == 'g'
let pst = 3
let c_or_g = tok
" Do some initializations for this cterm/gui
let tp_or_cs_cnt = 0
let got_term_match = 0
elseif tok == ''
let s:err_str = "Expected 'c' or 'g', encountered end of color def string"
return ''
else
let s:err_str = "Expected 'c' or 'g', got '".tok."'"
return ''
endif
elseif pst == 3
if tok == ':'
let pst = 4
elseif
let s:err_str = "Expected ':', encountered end of color def string"
return ''
else
let s:err_str = "Expected ':', got '".tok."'"
return ''
endif
elseif pst == 4
let pst = 5
" Do some processing with this and possibly previous termpat or
" clrstr token. Note that if previous one exists, it is termpat;
" we can't yet know what current one is.
let termpat_or_clrstr = substitute(tok, '\\\(.\)', '\1', 'g')
if termpat_or_clrstr =~ '^[[:space:]]*$'
let s:err_str = "Term patterns and color strings must contain at least one non-whitespace char"
return ''
endif
" If here, update the count. Note that we won't know whether this
" is termpat or clrstr until next time here.
let tp_or_cs_cnt = tp_or_cs_cnt + 1
if !got_term_match && tp_or_cs_cnt > 1
" Process saved string as termpat
" Pattern has implied ^ and $. Also, termpat may contain \c
if &term =~ '^'.tp_or_cs_str.'$'
" Found a match!
let got_term_match = 1
endif
endif
" Save current token for processing next time
let tp_or_cs_str = termpat_or_clrstr
elseif pst == 5
if tok == ':' " another termpat or clrstr
let pst = 4
elseif tok == ',' " another cterm/gui
let pst = 2
elseif tok == '' " end of string - legal in state 5
let parse_complete = 1
else " illegal token
let s:err_str = "Unexpected input in color def string: ".tok
return ''
endif
if tok == ',' || tok == ''
" Need to process saved data from pst 4, which we now know to
" be a clrstr.
if tp_or_cs_cnt == 1 || got_term_match
" Either no termpats were specified (implied match with
" &term) or one of the termpats matched.
" Note that prior ctermfg/guifg rhs strings may be
" overwritten here, if more than one cterm/gui def exists
" and has a match.
if c_or_g == 'c'
let ctermfg_rhs = tp_or_cs_str
else
let guifg_rhs = tp_or_cs_str
endif
endif
endif
endif
endwhile
" Construct the return string:
" <namepat>,<ctermfg_rhs>,<guifg_rhs>
let ret_str = escape(namepat, ',\').','
\.escape(ctermfg_rhs, ',\').','.escape(guifg_rhs, ',\')
return ret_str
endfu
" >>>
" Function: s:Get_color_uniq_idx() <<<
" Purpose: Convert the rhs of b:txtfmt_clr and b:txtfmt_bgc (if applicable) to
" a single string. Look the string up in global array
" g:txtfmt_color_configs{}. If the color config already exists, return its
" index; otherwise, append the color config to a new element at the end of
" g:txtfmt_color_configs{}, and return the corresponding index.
" Note: The index returned will be used to ensure that the highlight groups
" used for this buffer are specific to the color configuration.
" Input: none
" Return: Uniqueness index corresponding to the color configuration stored in
" b:txtfmt_clr and b:txtfmt_bgc (if applicable). In the unlikely event that no
" colors are active in the current configuration (i.e., numfgcolors and
" numbgcolors both equal 0), we return an empty string.
fu! s:Get_color_uniq_idx()
" Build the string to be looked up
let s = ''
let fgbg_idx = 0
let clr_or_bgc{0} = 'clr'
let clr_or_bgc{1} = 'bgc'
let fg_or_bg{0} = 'fg'
let fg_or_bg{1} = 'bg'
while fgbg_idx < (b:txtfmt_cfg_bgcolor ? 2 : 1)
" Loop over all used colors
" TODO_BG: Does it make sense to include unused ones? It shouldn't be
" necessary...
" Note: Index 1 corresponds to first non-default color
let i = 1
while i <= b:txtfmt_cfg_num{fg_or_bg{fgbg_idx}}colors
let s = s.b:txtfmt_{clr_or_bgc{fgbg_idx}}{i}."\<NL>"
let i = i + 1
endwhile
let fgbg_idx = fgbg_idx + 1
endwhile
" In the unlikely event that string is still empty, config is such that no
" colors are in use, in which case, we return an empty string, since no
" uniqueness index applies.
if strlen(s) == 0
return ''
endif
" Look up the newly-generated string in g:txtfmt_color_configs
if !exists('g:txtfmt_color_configs_len')
let g:txtfmt_color_configs_len = 0
endif
let i = 0
while i < g:txtfmt_color_configs_len
if g:txtfmt_color_configs{i} == s
" Color config exists already - return its index
return i
endif
let i = i + 1
endwhile
" If here, color config doesn't exist yet, so append it
let g:txtfmt_color_configs{i} = s
let g:txtfmt_color_configs_len = g:txtfmt_color_configs_len + 1
" Return index of appended element
return i
endfu
" >>>
" Function: s:Process_color_options() <<<
" Purpose: Process the special color definition arrays to determine the color
" arrays in effect for the current buffer. There are global and buffer-local
" versions of both txtfmtColor and txtfmtBgcolor, as well as a default in
" s:txtfmt_clr. From these, we construct the 8 element buf-local arrays,
" described under 'Return:' below.
" Algorithm: Each color index is considered independently of all others, with
" a buf-local definition taking precedence over a global one. A txtfmtBgcolor
" element is always preferred for background color, but txtfmtColor will be
" used for a background color when no background-color-specific element
" exists. If no other element can be found, defaults from s:txtfmt_clr will be
" used.
" Cterm and gui may be overridden separately, and color definition strings may
" even take into account the value of &term. Note that for each possible
" element in the array, there is a default element (in s:txtfmt_clr{1..8}),
" which will be used if user has not overriden.
" Return: indirect only
" Builds the following buffer-scope arrays: (indexed by 1-based color index)
" b:txtfmt_clr_namepat{}, b:txtfmt_clr{}
" b:txtfmt_bgc_namepat{}, b:txtfmt_bgc{}
" Note: bgc arrays will not be built if background colors are disabled.
" Details: The format of the color array is as follows:
" The array has 8 elements (1..8), each of which represents a color region
" begun with one of the 8 successive color tokens in the token range. Each
" element is a string whose format is described in header of
" s:Translate_color_optstr()
" Rules: In outer loop over fg and bg cases, and inner loop over all color
" indices in range i = 1..8, check all applicable variants of the txtfmtColor
" array (as defined in arrname{}) for an element corresponding to the current
" color index. Set b:txtfmt_clr{} and move to next color index as soon as a
" suitable definition is found. Suitability is determined by checking
" has('gui_running') against the 'g:' or 'c:' in the color definition string,
" and if necessary, by matching the current value of 'term' against a 'term'
" pattern in the color definition string.
" Note: This function was rewritten on 10May2008, and then again on 31Jan2009.
" A corresponding partial rewrite of s:Translate_color_optstr is probably in
" order, but is not necessary, so I've left the latter function completely
" intact for now. (We don't really need both ctermfg and guifg values any
" more, but s:Translate_color_optstr still returns both...)
fu! s:Process_color_options()
let arrname{0} = 'b:txtfmtBgcolor'
let arrname{1} = 'g:txtfmtBgcolor'
let arrname{2} = 'b:txtfmtColor'
let arrname{3} = 'g:txtfmtColor'
let arrname{4} = 's:txtfmt_clr'
" TODO_BG: Decide whether s:txtfmt_bgc should be removed - if so, may get
" rid of arr_end{}
" Define strings used to build appropriate var names for both fg and bg
let clr_or_bgc{0} = 'clr'
let clr_or_bgc{1} = 'bgc'
" Frame the array for fg and bg
" Note: Could use a common value for end.
let arr_beg{0} = 2
let arr_end{0} = 4
let arr_beg{1} = 0
let arr_end{1} = 4
" Determine arrays that don't apply to fg color
let skip{0}_{0} = 1
let skip{0}_{1} = 1
let skip{0}_{2} = 0
let skip{0}_{3} = 0
let skip{0}_{4} = 0
if b:txtfmt_cfg_bgcolor
" Determine arrays that don't apply to bg color
let skip{1}_{0} = 0
let skip{1}_{1} = 0
let skip{1}_{2} = 0
let skip{1}_{3} = 0
let skip{1}_{4} = 0
endif
" Loop over fg and bg color (if applicable)
let fgbg_idx = 0
while fgbg_idx < (b:txtfmt_cfg_bgcolor ? 2 : 1)
let i = 1
" Loop over all colors (1st non-default color at index 1)
while i < b:txtfmt_num_colors
" Init strings to value signifying not specified or error
let namepat = '' | let clr_rhs = ''
" Loop over the possible color arrays
let j = arr_beg{fgbg_idx}
while j <= arr_end{fgbg_idx}
" Skip inapplicable arrays
if skip{fgbg_idx}_{j}
let j = j + 1
continue
endif
" Skip nonexistent color definitions
if exists(arrname{j}.'{'.i.'}')
exe 'let l:el = '.arrname{j}.'{'.i.'}'
" If here, color definition exists. Let's see whether it contains
" a match...
let s = s:Translate_color_optstr(el)
if s != ''
" Extract fields from the escaped return string (which is
" internally generated, and hence, does not require
" validation)
" TODO - Perhaps standardize this in one spot (function or
" static return variables)
let re_fld = '\%(\%(\\.\|[^,]\)*\)'
let re_sfld = '\(\%(\\.\|[^,]\)*\)'
let namepat = substitute(s, re_sfld.'.*', '\1', '')
if has('gui_running')
let clr_rhs = substitute(s, re_fld.','.re_fld.','.re_sfld, '\1', '')
else
let clr_rhs = substitute(s, re_fld.','.re_sfld.'.*', '\1', '')
endif
" Note: clr_rhs may be null at this point; if so, there
" was no applicable color definition, though the color def
" element was valid
if strlen(clr_rhs)
" Remove extra level of backslashes
let namepat = substitute(namepat, '\\\(.\)', '\1', 'g')
let clr_rhs = substitute(clr_rhs, '\\\(.\)', '\1', 'g')
endif
elseif arrname{j}[0] == 'b' || arrname{j}[0] == 'g'
echomsg "Ignoring invalid user-specified color def ".arrname{j}.i." due to error: "
\.s:err_str
else
" Shouldn't get here! Problem with defaults...
echomsg "Internal error within Process_color_options - bad default for txtfmtColors"
\.i." - contact developer."
endif
" Are we done yet?
if strlen(clr_rhs)
break
endif
endif
let j = j + 1
endwhile
" Assumption: Lack of color rhs at this point implies internal error.
" Build the buffer-specific array used in syntax file...
" Note: In the following 2 arrays, an index of 1 corresponds to the
" first non-default color.
let b:txtfmt_{clr_or_bgc{fgbg_idx}}_namepat{i} = namepat
let b:txtfmt_{clr_or_bgc{fgbg_idx}}{i} = clr_rhs
" Advance to next color
let i = i + 1
endwhile
let fgbg_idx = fgbg_idx + 1
endwhile
" Now that the color configuration is completely determined (and loaded
" into b:txtfmt_clr{} (and b:txtfmt_bgc{} if applicable)), determine the
" 'uniqueness index' for this color configuration. The uniqueness index is
" used to ensure that each color configuration has its own set of syntax
" groups.
" TODO_BG: Is the _cfg_ infix appropriate for such variables? For now, I'm
" using it only for option vars (with one exception that needs looking at)
let b:txtfmt_color_uniq_idx = s:Get_color_uniq_idx()
endfu
" >>>
" Function: s:Process_txtfmt_modeline() <<<
" Purpose: Determine whether input line is a valid txtfmt modeline. Process
" options if so. If required by s:txtfmt_ml_new_<...> variables, change
" options in the modeline itself. (Example: change 'tokrange' as requested by
" preceding call to :MoveStartTok command.)
" Note: Input line may be either a valid line number or a string representing
" a valid modeline (which is constructed by the caller)
" Return:
" 0 - no txtfmt modeline found
" -1 - txtfmt modeline containing error (bad option)
" 1 - valid txtfmt modeline found and processed
" Note: Modeline format is described under function s:Is_txtfmt_modeline.
fu! s:Process_txtfmt_modeline(line)
if a:line =~ '^[1-9][0-9]*$'
" Obtain the line to be processed
let l:line = a:line
let linestr = getline(a:line)
else
" The line to be processed is not in the buffer
let l:line = 0
let linestr = a:line
endif
" Is the line a modeline?
if !s:Is_txtfmt_modeline(linestr)
" Note: This is not considered error - the line is simply not a
" modeline
return 0
endif
" If here, overall format is correct. Are all options valid?
" Assume valid modeline until we find out otherwise...
let ret_val = 1
" Save the leading text, in case we need to create a version of the line
" with changed options (e.g., starttok change)
let leading = substitute(linestr, s:re_modeline, '\1', '')
" The middle (options) part will be built up as we go
let middle = ''
" Save the trailing stuff
let trailing = substitute(linestr, s:re_modeline, '\3', '')
" Extract the {options} portion (leading/trailing stuff removed) for
" processing
let optstr = substitute(linestr, s:re_modeline, '\2', '')
" Extract pieces from head of optstr as long as unprocessed options exist
" Note: The following pattern may be used to extract element separators
" into \1, opt name into \2, equal sign (if it exists) into \3, opt value
" (if it exists) into \4, and to strip all three from the head of the
" string. (The remainder of the modeline will be in \5.)
" Note: Element separator is optional in the following re, since it won't
" exist for first option, and we've already verified that it exists
" between all other options.
let re_opt = '\('.s:re_ml_elsep.'\)\?\('
\.s:re_ml_name.'\)\%(\(=\)\('.s:re_ml_val.'\)\)\?\(.*\)'
" If this is a real buffer line, do some special processing required only
" when modeline options are being changed or added
if l:line > 0
" Set this flag if we make a requested change to an option, which needs to
" be reflected in the actual modeline text in the buffer (e.g., starttok
" change)
unlet! line_changed
" If we haven't done so already, save location at which new options can be
" added.
if !exists('s:txtfmt_ml_addline')
let s:txtfmt_ml_addline = l:line
" Note: The +1 changes 0-based byte index to 1-based col index
" Note: Saved value represents the col number of the first char to be
" added
let s:txtfmt_ml_addcol = matchend(linestr, s:re_ml_start) + 1
endif
endif
" Loop over all options, exiting loop early if error occurs
while strlen(optstr) > 0 && ret_val != -1
" Accumulate the option separator text
let middle = middle.substitute(optstr, re_opt, '\1', '')
" Extract option name and value
let optn = substitute(optstr, re_opt, '\2', '')
let has_eq = '=' == substitute(optstr, re_opt, '\3', '')
let optv = substitute(optstr, re_opt, '\4', '')
" Remove the option about to be processed from head of opt str
let optstr = substitute(optstr, re_opt, '\5', '')
" IMPORTANT TODO: The following if/else needs major refactoring to
" avoid duplication. There are ways of doing this that require far
" less brute-force. I'm saving it for a subsequent release to mitigate
" testing.
" Validate the option(s)
if optn == 'tokrange' || optn == 'rng'
"format: tokrange=<char_code>[sSlLxX]
"Examples: '130s' '1500l' '130' '0x2000L'
if !has_eq
let s:err_str = "Value required for non-boolean txtfmt option 'tokrange'"
let b:txtfmt_cfg_tokrange = ''
let ret_val = -1
elseif !s:Tokrange_is_valid(optv)
" 2 cases when option is invalid:
" 1) We can fix the invalid tokrange by changing to the value
" specified by user in call to :MoveStartTok
" 2) We must display error and use default
if exists('s:txtfmt_ml_new_starttok')
" Design Decision: Since option value is not valid, don't
" attempt to preserve any existing 'formats' specifier
" Assumption: b:txtfmt_cfg_bgcolor,
" b:txtfmt_cfg_longformats, and b:txtfmt_cfg_undercurl are
" unlet *only* at the top of Set_tokrange; thus, we can
" assume they will be valid here.
let optv = s:txtfmt_ml_new_starttok
\.b:txtfmt_const_tokrange_suffix_{b:txtfmt_cfg_bgcolor}{b:txtfmt_cfg_longformats}{b:txtfmt_cfg_undercurl}
" Record fact that change was made
unlet s:txtfmt_ml_new_starttok
let line_changed = 1
else
let s:err_str = "Invalid 'tokrange' value - must be hex or dec"
\." char code value optionally followed by one of [sSlLxX]"
" Note: 'XL' suffix is currently illegal, but give a special
" warning if user attempts to use it.
if optv =~? 'xl$'
let s:err_str = s:err_str
\." (The XL suffix is currently illegal, due to a memory resource limitation"
\." affecting current versions of Vim; this suffix may, however, be supported in"
\." a future release.)"
endif
" Record the attempt to set b:txtfmt_cfg_tokrange from modeline
let b:txtfmt_cfg_tokrange = ''
let ret_val = -1
endif
else
if exists('s:txtfmt_ml_new_starttok')
" Change the starttok value, preserving both the starttok
" number format (note that s:txtfmt_ml_new_starttok is
" actually a string) and any existing 'formats'
" specification
" Note: Since modeline setting trumps all others, an
" existing setting should agree with current setting
" anyway.
let optv = substitute(optv, b:txtfmt_re_number_atom, s:txtfmt_ml_new_starttok, '')
" Record fact that change was made
unlet s:txtfmt_ml_new_starttok
let line_changed = 1
endif
" Save the option value, deferring processing till later...
let b:txtfmt_cfg_tokrange = optv
endif
elseif optn == 'fgcolormask' || optn == 'fcm'
if !has_eq
let s:err_str = "Value required for non-boolean txtfmt option 'fgcolormask'"
let b:txtfmt_cfg_fgcolormask = ''
let ret_val = -1
elseif !s:Clrmask_is_valid(optv)
" Invalid number of colors
let s:err_str = "Invalid foreground color mask - must be string of 8 ones and zeroes"
let b:txtfmt_cfg_fgcolormask = ''
let ret_val = -1
else
let b:txtfmt_cfg_fgcolormask = optv
endif
elseif optn == 'bgcolormask' || optn == 'bcm'
if !has_eq
let s:err_str = "Value required for non-boolean txtfmt option 'bgcolormask'"
let b:txtfmt_cfg_bgcolormask = ''
let ret_val = -1
elseif !s:Clrmask_is_valid(optv)
" Invalid number of colors
let s:err_str = "Invalid background color mask - must be string of 8 ones and zeroes"
let b:txtfmt_cfg_bgcolormask = ''
let ret_val = -1
else
let b:txtfmt_cfg_bgcolormask = optv
endif
elseif optn == 'sync'
"format: sync={<hex>|<dec>|fromstart|none}
"Examples: 'sync=300' 'sync=0x1000' 'sync=fromstart' 'sync=none'
if !has_eq
let s:err_str = "Value required for non-boolean txtfmt option 'sync'"
let b:txtfmt_cfg_sync = ''
let ret_val = -1
elseif !s:Sync_is_valid(optv)
let s:err_str = "Invalid 'sync' value - must be one of the"
\." following: <numeric literal>, 'fromstart'"
\.", 'none'"
" Record the attempt to set b:txtfmt_cfg_sync from modeline
let b:txtfmt_cfg_sync = ''
let ret_val = -1
else
" Defer processing of sync till later
let b:txtfmt_cfg_sync = optv
endif
elseif optn =~ '^\(no\)\?\(pack\|pck\)$'
" Make sure no option value was supplied to binary option
if has_eq
let s:err_str = "Cannot assign value to boolean txtfmt option 'pack'"
let b:txtfmt_cfg_pack = ''
let ret_val = -1
else
" Option has been explicitly turned on or off
let b:txtfmt_cfg_pack = optn =~ '^no' ? 0 : 1
endif
elseif optn =~ '^\(no\)\?\(undercurl\|uc\)$'
" Make sure no option value was supplied to binary option
if has_eq
let s:err_str = "Cannot assign value to boolean txtfmt option 'undercurl'"
let b:txtfmt_cfg_undercurlpref = ''
let ret_val = -1
else
" Option has been explicitly turned on or off
let b:txtfmt_cfg_undercurlpref = optn =~ '^no' ? 0 : 1
endif
elseif optn =~ '^\(no\)\?\(nested\|nst\)$'
" Make sure no option value was supplied to binary option
if has_eq
let s:err_str = "Cannot assign value to boolean txtfmt option 'nested'"
let b:txtfmt_cfg_nested = ''
let ret_val = -1
else
" Option has been explicitly turned on or off
let b:txtfmt_cfg_nested = optn =~ '^no' ? 0 : 1
endif
elseif optn =~ '^\(no\)\?\(conceal\|cncl\)$'
" Make sure no option value was supplied to binary option
if has_eq
let s:err_str = "Cannot assign value to boolean txtfmt option 'conceal'"
let b:txtfmt_cfg_conceal = ''
let ret_val = -1
else
" Option has been explicitly turned on or off
let b:txtfmt_cfg_conceal = optn =~ '^no' || !has('conceal') ? 0 : 1
endif
elseif optn == 'concealcursor' || optn == 'cocu'
" format: cocu=[n][v][i][c]
if !has_eq
let s:err_str = "Value required for non-boolean txtfmt option 'concealcursor'"
let b:txtfmt_cfg_concealcursor_invalid = 1
let ret_val = -1
elseif !s:Concealcursor_is_valid(optv)
let s:err_str = "Invalid 'concealcursor' option: `" . optv
\ . "' - only the following flags are permitted: nvic"
" Note: This one is different from the others: empty string is
" valid option value, so we can't use null string to indicate
" error
let b:txtfmt_cfg_concealcursor_invalid = 1
let ret_val = -1
else
" Option has been explicitly set
" Vim Issue: With 'concealcursor', duplicate flags aren't
" removed as they are for other such options (and as
" documentation indicates they should be).
" Note: Intentially skipping check for duplicates since
" they're harmless.
let b:txtfmt_cfg_concealcursor = optv
endif
elseif optn == 'escape' || optn == 'esc'
"format: escape=[bslash|self|none]
"TODO: Perhaps use s:Escape_is_valid() for validation
if !has_eq
let s:err_str = "Value required for non-boolean txtfmt option 'escape'"
let b:txtfmt_cfg_escape = ''
let ret_val = -1
elseif optv == 'bslash'
let b:txtfmt_cfg_escape = 'bslash'
elseif optv == 'self'
let b:txtfmt_cfg_escape = 'self'
elseif optv == 'none'
let b:txtfmt_cfg_escape = 'none'
else
let s:err_str = "Invalid 'escape' value - must be 'bslash', 'self', or 'none'"
let b:txtfmt_cfg_escape = ''
let ret_val = -1
endif
else
let s:err_str = "Unknown txtfmt modeline option: ".optn
let ret_val = -1
endif
" Append optn[=optv] to middle
let middle = middle . optn . (has_eq ? '=' . optv : '')
endwhile
" Processed txtfmt modeline without error
if l:line > 0 && exists('line_changed')
" Alter the line to reflect any option changes
" Note: If error occurred above, optstr may be non-empty, in which
" case, we need to append it to the already processed options in
" middle.
call setline(l:line, leading.middle.optstr.trailing)
endif
return ret_val
endfu
" >>>
" Function: s:Do_txtfmt_modeline() <<<
" Purpose: Look for txtfmt "modelines" of the following form:
" .\{-}<whitespace>txtfmt:<definitions>:
" which appear within the first or last 'modelines' lines in the buffer.
" Note: Function will search a number of lines (at start and end of buffer),
" as determined from 'modelines', provided that this value is nonzero. If
" 'modelines' is 0, default of 5 lines at beginning and end will be searched.
" Return:
" 0 - no txtfmt modeline found
" N - N valid txtfmt modelines found and processed
" -N - txtfmt modeline processing error occurred on the Nth modeline
" processed
" Error_handling: If error is encountered in a modeline, the remainder of
" the offending modeline is discarded, and modeline processing is aborted;
" i.e., no more lines are searched. This is consistent with the way Vim's
" modeline processing works.
" Modeline_modifications: The following buf-local variables are considered to
" be inputs that request changes to existing modelines:
" b:txtfmt_ml_new_starttok
" ...
" If the requested change can't be made, we will attempt to add the requested
" setting to an existing modeline. If there are no existing modelines, we will
" add a new one (warning user if he has modeline processing turned off). If
" modifications are made to the buffer, we will use b:txtfmt_ml_save_modified
" to determine whether the buffer was in a modified state prior to our
" changes, and will save our changes if and only if doing so doesn't commit
" any unsaved user changes.
" Assumption: Another Txtfmt function sets b:txtfmt_ml_save_modified
" appropriately before the Txtfmt-initiated changes begin.
fu! s:Do_txtfmt_modeline()
" Check for request to change starttok
if exists('b:txtfmt_ml_new_starttok')
" Communicate the request to modeline processing function.
let s:txtfmt_ml_new_starttok = b:txtfmt_ml_new_starttok
" Unlet the original to ensure that if we abort with error, we don't
" do this next time
unlet! b:txtfmt_ml_new_starttok
else
" Clean up after any previous failed attempts
unlet! s:txtfmt_ml_new_starttok
endif
" Did someone anticipate that we might be modifying the buffer?
if exists('b:txtfmt_ml_save_modified')
let l:save_modified = b:txtfmt_ml_save_modified
unlet b:txtfmt_ml_save_modified
endif
" The following apply to any option change request. They're set within
" Process_txtfmt_modeline the first time a suitable add location is found
unlet! s:txtfmt_ml_addline
unlet! s:txtfmt_ml_addcol
" Keep up with # of modelines actually encountered
let l:ml_seen = 0
" How many lines should we search?
" Priority is given to txtfmtModelines user option if it exists
if exists('b:txtfmtModelines') || exists('g:txtfmtModelines')
" User can disable by setting to 0
let mls_use = exists('b:txtfmtModelines') ? b:txtfmtModelines : g:txtfmtModelines
else
" Use 'modelines' option+1 unless it's not a valid nonzero value, in
" which case, we use default of 5
" NOTE: 1 is added to 'modelines' option so that if modeline is at
" highest or lowest possible line, putting a txtfmt modeline above or
" below it, respectively, will work.
let mls_use = &modelines > 0 ? &modelines+1 : 5
endif
let nlines = line('$')
" Check first modelines lines
" TODO - Combine the 2 loops into one, which can alter the loop counter in
" a stepwise manner.
let i = 1
while i <= mls_use && i <= nlines
let status = s:Process_txtfmt_modeline(i)
if status == 1
" Successfully extracted options
let l:ml_seen = l:ml_seen + 1
elseif status == -1
" Error processing the modeline
echoerr "Ignoring txtfmt modeline on line ".i.": ".s:err_str
return -(l:ml_seen + 1)
endif
" Keep looking...
let i = i + 1
endwhile
" Check last modelines lines
let i = nlines - mls_use + 1
" Adjust if necessary to keep from checking already checked lines
if i <= mls_use
let i = mls_use + 1
endif
while i <= nlines
let status = s:Process_txtfmt_modeline(i)
if status == 1
" Successfully extracted options
let l:ml_seen = l:ml_seen + 1
elseif status == -1
" Error processing the modeline
echoerr "Ignoring txtfmt modeline on line ".i.": ".s:err_str
return -(l:ml_seen + 1)
endif
" Keep looking...
let i = i + 1
endwhile
" Deal with any unprocessed requests for modeline option changes
" Note: Process_txtfmt_modeline unlets s:txtfmt_ml_new_<...> vars that
" have been completely handled.
let l:ml_new_opts = ''
if exists('s:txtfmt_ml_new_starttok')
" TODO: Decide whether it matters that this strategy will put a
" useless space at end of modeline in the unlikely event that the
" original modeline contained no options
" Assumption: b:txtfmt_cfg_bgcolor, b:txtfmt_cfg_longformats, and
" b:txtfmt_cfg_undercurl are unlet *only* at the top of Set_tokrange;
" thus, we can assume they will be valid here.
let l:ml_new_opts = l:ml_new_opts
\.'tokrange='
\.s:txtfmt_ml_new_starttok
\.b:txtfmt_const_tokrange_suffix_{b:txtfmt_cfg_bgcolor}{b:txtfmt_cfg_longformats}{b:txtfmt_cfg_undercurl}
\.' '
endif
" Any additional requests would go here
" ...
" Do we have any options to add?
if strlen(l:ml_new_opts)
" Create the modeline that will be passed to Process_txtfmt_modeline
let l:ml_process = "\<Tab>txtfmt:".l:ml_new_opts
" Add what needs to be added to the buffer
if exists('s:txtfmt_ml_addline')
" Add new options to existing modeline
let linestr = getline(s:txtfmt_ml_addline)
" Recreate the line
call setline(s:txtfmt_ml_addline,
\ strpart(linestr, 0, s:txtfmt_ml_addcol - 1)
\ . l:ml_new_opts
\ . strpart(linestr, s:txtfmt_ml_addcol - 1))
else
" Create a new txtfmt modeline on first line. Note that it's the
" same as what will be passed to Process_txtfmt_modeline for
" processing.
call append(0, l:ml_process)
if mls_use == 0
" Warn user that he should change his modelines setting
" TODO_BG: Figure out how to prevent the warning from
" disappearing after a second or two.
echomsg "Warning: Txtfmt has added option settings to a modeline at the beginning"
\." of the buffer, but your modeline setting is such that this modeline"
\." will be ignored next time the buffer is opened."
\." (:help txtfmtModelines)"
endif
" Record fact that we've processed another modeline
let l:ml_seen = l:ml_seen + 1
endif
" Process only the options just added
let status = s:Process_txtfmt_modeline(l:ml_process)
" Note: Error should be impossible here, but just in case...
if status < 0
echoerr "Internal error: Unexpected error while processing txtfmt-generated modeline: ".s:err_str.". Contact the developer"
return -(l:ml_seen)
elseif status == 0
echoerr "Internal error: Failed to extract option(s) while processing txtfmt-generated modeline. Contact the developer"
return -(l:ml_seen)
endif
endif
" If modeline processing made an unmodified buffer modified, save our
" changes now. (Rationale: Leave the buffer in the state it was in prior
" to modeline processing. This avoids making user's unsaved changes
" permanent.)
if exists('l:save_modified') && !l:save_modified && &modified
write
endif
" If here, we encountered no error. Return the number of modelines
" processed (could be zero)
return l:ml_seen
endfu
" >>>
" Function: s:Process_clr_masks() <<<
" Inputs:
" b:txtfmt_cfg_fgcolormask
" b:txtfmt_cfg_bgcolormask
" Description: Each mask is a string of 8 chars, each of which must be either
" '0' or '1'
" Outputs:
" b:txtfmt_cfg_fgcolor{} b:txtfmt_cfg_numfgcolors
" b:txtfmt_cfg_bgcolor{} b:txtfmt_cfg_numbgcolors
" Description: The <fg|bg>color arrays are 1-based indirection arrays, which
" contain a single element for each of the active colors. The elements of
" these arrays are the 1-based indices of the corresponding color in the
" actual color definition array (which always contains 8 elements).
" Note: num<fg|bg>colors will be 0 and corresponding array will be empty if
" there are no 1's in the <fg|bg>colormask
" Note: If 'tokrange' setting precludes background colors, the bg colormask
" option will be all 0's at this point, regardless of how user has configured
" the option.
fu! s:Process_clr_masks()
" Loop over fg and bg
let fgbg_idx = 0
let fg_or_bg{0} = 'fg'
let fg_or_bg{1} = 'bg'
while fgbg_idx < 2
" Cache the mask to be processed
let mask = b:txtfmt_cfg_{fg_or_bg{fgbg_idx}}colormask
" Note: To be on the safe side, I'm going to zero the bg color mask
" whenever bg colors are disabled, just in case caller forgot.
if fg_or_bg{fgbg_idx} == 'bg' && !b:txtfmt_cfg_bgcolor && mask =~ '1'
let mask = '00000000'
endif
" Loop over all 8 'bits' in the mask
" Assumption: The mask length has already been validated
" Note: All color arrays processed are 1-based (since index 0, if it
" exists, corresponds to 'no color'), but mask bit 'array' is
" inherently 0-based (because it's a string)
let i = 0
let iadd = 0
while i < 8
if mask[i] == '1'
" Append this color's (1-based) index to active color array
" (which is also 1-based)
let iadd = iadd + 1
let b:txtfmt_cfg_{fg_or_bg{fgbg_idx}}color{iadd} = i + 1
endif
let i = i + 1
endwhile
" Store number of active colors
let b:txtfmt_cfg_num{fg_or_bg{fgbg_idx}}colors = iadd
" Prepare for next iteration
let fgbg_idx = fgbg_idx + 1
endwhile
endfu
" >>>
" Function: s:Define_fmtclr_vars() <<<
fu! s:Define_fmtclr_vars()
" Format definition array <<<
" NOTE: This array is used for rhs of syntax definitions, but also for display
" by ShowTokenMap.
let b:txtfmt_fmt{0} = 'NONE'
let b:txtfmt_fmt{1} = 'underline'
let b:txtfmt_fmt{2} = 'bold'
let b:txtfmt_fmt{3} = 'underline,bold'
let b:txtfmt_fmt{4} = 'italic'
let b:txtfmt_fmt{5} = 'underline,italic'
let b:txtfmt_fmt{6} = 'bold,italic'
let b:txtfmt_fmt{7} = 'underline,bold,italic'
if !b:txtfmt_cfg_longformats
" short formats
let b:txtfmt_num_formats = 8
else
" long formats
let b:txtfmt_fmt{8} = 'standout'
let b:txtfmt_fmt{9} = 'underline,standout'
let b:txtfmt_fmt{10} = 'bold,standout'
let b:txtfmt_fmt{11} = 'underline,bold,standout'
let b:txtfmt_fmt{12} = 'italic,standout'
let b:txtfmt_fmt{13} = 'underline,italic,standout'
let b:txtfmt_fmt{14} = 'bold,italic,standout'
let b:txtfmt_fmt{15} = 'underline,bold,italic,standout'
let b:txtfmt_fmt{16} = 'reverse'
let b:txtfmt_fmt{17} = 'underline,reverse'
let b:txtfmt_fmt{18} = 'bold,reverse'
let b:txtfmt_fmt{19} = 'underline,bold,reverse'
let b:txtfmt_fmt{20} = 'italic,reverse'
let b:txtfmt_fmt{21} = 'underline,italic,reverse'
let b:txtfmt_fmt{22} = 'bold,italic,reverse'
let b:txtfmt_fmt{23} = 'underline,bold,italic,reverse'
let b:txtfmt_fmt{24} = 'standout,reverse'
let b:txtfmt_fmt{25} = 'underline,standout,reverse'
let b:txtfmt_fmt{26} = 'bold,standout,reverse'
let b:txtfmt_fmt{27} = 'underline,bold,standout,reverse'
let b:txtfmt_fmt{28} = 'italic,standout,reverse'
let b:txtfmt_fmt{29} = 'underline,italic,standout,reverse'
let b:txtfmt_fmt{30} = 'bold,italic,standout,reverse'
let b:txtfmt_fmt{31} = 'underline,bold,italic,standout,reverse'
" If using undercurl (introduced in Vim 7.0), there will be twice as
" many formats.
if !b:txtfmt_cfg_undercurl
let b:txtfmt_num_formats = 32
else
let b:txtfmt_fmt{32} = 'undercurl'
let b:txtfmt_fmt{33} = 'underline,undercurl'
let b:txtfmt_fmt{34} = 'bold,undercurl'
let b:txtfmt_fmt{35} = 'underline,bold,undercurl'
let b:txtfmt_fmt{36} = 'italic,undercurl'
let b:txtfmt_fmt{37} = 'underline,italic,undercurl'
let b:txtfmt_fmt{38} = 'bold,italic,undercurl'
let b:txtfmt_fmt{39} = 'underline,bold,italic,undercurl'
let b:txtfmt_fmt{40} = 'standout,undercurl'
let b:txtfmt_fmt{41} = 'underline,standout,undercurl'
let b:txtfmt_fmt{42} = 'bold,standout,undercurl'
let b:txtfmt_fmt{43} = 'underline,bold,standout,undercurl'
let b:txtfmt_fmt{44} = 'italic,standout,undercurl'
let b:txtfmt_fmt{45} = 'underline,italic,standout,undercurl'
let b:txtfmt_fmt{46} = 'bold,italic,standout,undercurl'
let b:txtfmt_fmt{47} = 'underline,bold,italic,standout,undercurl'
let b:txtfmt_fmt{48} = 'reverse,undercurl'
let b:txtfmt_fmt{49} = 'underline,reverse,undercurl'
let b:txtfmt_fmt{50} = 'bold,reverse,undercurl'
let b:txtfmt_fmt{51} = 'underline,bold,reverse,undercurl'
let b:txtfmt_fmt{52} = 'italic,reverse,undercurl'
let b:txtfmt_fmt{53} = 'underline,italic,reverse,undercurl'
let b:txtfmt_fmt{54} = 'bold,italic,reverse,undercurl'
let b:txtfmt_fmt{55} = 'underline,bold,italic,reverse,undercurl'
let b:txtfmt_fmt{56} = 'standout,reverse,undercurl'
let b:txtfmt_fmt{57} = 'underline,standout,reverse,undercurl'
let b:txtfmt_fmt{58} = 'bold,standout,reverse,undercurl'
let b:txtfmt_fmt{59} = 'underline,bold,standout,reverse,undercurl'
let b:txtfmt_fmt{60} = 'italic,standout,reverse,undercurl'
let b:txtfmt_fmt{61} = 'underline,italic,standout,reverse,undercurl'
let b:txtfmt_fmt{62} = 'bold,italic,standout,reverse,undercurl'
let b:txtfmt_fmt{63} = 'underline,bold,italic,standout,reverse,undercurl'
let b:txtfmt_num_formats = 64
endif
endif
" >>>
" <<< Default color definition array
" These are the original defaults
let s:txtfmt_clr{1} = '^\\%(k\\|bla\\%[ck]\\)$,c:Black,g:#000000'
let s:txtfmt_clr{2} = '^b\\%[lue]$,c:DarkBlue,g:#0000FF'
let s:txtfmt_clr{3} = '^g\\%[reen]$,c:DarkGreen,g:#00FF00'
let s:txtfmt_clr{4} = '^t\\%[urquoise]$,c:DarkCyan,g:#00FFFF'
let s:txtfmt_clr{5} = '^r\\%[ed]$,c:DarkRed,g:#FF0000'
let s:txtfmt_clr{6} = '^v\\%[iolet]$,c:DarkMagenta,g:#FF00FF'
let s:txtfmt_clr{7} = '^y\\%[ellow]$,c:DarkYellow,g:#FFFF00'
let s:txtfmt_clr{8} = '^w\\%[hite]$,c:White,g:#FFFFFF'
" Note: The following variable indicates the total number of colors
" possible, including 'no color', which is not in the txtfmt_clr array.
" TODO: See how this is used to see how to use numfgcolors and numbgcolors...
let b:txtfmt_num_colors = 9
" >>>
" Set fmt/clr specific values for convenience
" txtfmt_<rgn>_first_tok: 1st (default) token
" txtfmt_<rgn>_last_tok: last (reserved) token
" txtfmt_last_tok: last token that could be used (if
" txtfmt_cfg_numbgcolors is applicable,
" assumes it to be 8)
" TODO NOTE - If desired, could allow the fmt/clr ranges to be split, in
" which case, the following 2 would be user-configurable. For now, just
" base them on txtfmtStarttok.
" TODO: Decide whether to keep this here or move outside this function
call s:Process_clr_masks()
" Save some useful char codes
let b:txtfmt_clr_first_tok = b:txtfmt_cfg_starttok
let b:txtfmt_clr_last_tok = b:txtfmt_cfg_starttok + b:txtfmt_num_colors - 1
let b:txtfmt_fmt_first_tok = b:txtfmt_clr_last_tok + 1
let b:txtfmt_fmt_last_tok = b:txtfmt_fmt_first_tok + b:txtfmt_num_formats - 1
if b:txtfmt_cfg_bgcolor
" Location of bg color range depends upon 'pack' setting as well
" as well as type of formats in effect
" Note: Intentionally hardcoding bgcolor index to 0 and undercurl
" index to 1 (when formats are long) to get desired length
" TODO: Replace ternaries with normal if block
let b:txtfmt_bgc_first_tok = b:txtfmt_cfg_starttok +
\ b:txtfmt_const_tokrange_size_{0}{
\(b:txtfmt_cfg_longformats || !b:txtfmt_cfg_pack ? 1 : 0)}{
\(b:txtfmt_cfg_longformats || !b:txtfmt_cfg_pack ? 1 : 0)}
let b:txtfmt_bgc_last_tok = b:txtfmt_bgc_first_tok + b:txtfmt_num_colors - 1
let b:txtfmt_last_tok = b:txtfmt_bgc_last_tok
else
" nothing after the fmt range
let b:txtfmt_bgc_first_tok = -1
let b:txtfmt_bgc_last_tok = -1
let b:txtfmt_last_tok = b:txtfmt_fmt_last_tok
endif
endfu
" >>>
" Function: s:Define_fmtclr_regexes() <<<
" Purpose: Define regexes involving the special fmt/clr tokens.
" Assumption: The following variable(s) has been defined for the buffer:
" b:txtfmt_cfg_starttok
" Note: The start tok is user-configurable. Thus, this function should be
" called only after processing options.
fu! s:Define_fmtclr_regexes()
" Cache bgc enabled flag for subsequent tests
let bgc = b:txtfmt_cfg_bgcolor && b:txtfmt_cfg_numbgcolors > 0
let clr = b:txtfmt_cfg_numfgcolors > 0
" 0 1 3 4 5 7 8
" 1 3 4 5 7 8
let fgbg_idx = 0
let clr_or_bgc{0} = 'clr' | let fg_or_bg{0} = 'fg'
let clr_or_bgc{1} = 'bgc' | let fg_or_bg{1} = 'bg'
while fgbg_idx < 2
if b:txtfmt_cfg_num{fg_or_bg{fgbg_idx}}colors
" Note: Handle first active color here, outside the loop. To handle it
" inside the loop, I would need to initialize tok_cnt to 0 and tok_i
" to -1 and handle tok_i == -1 specially within the loop. (tok_i == -1
" indicates that we don't have the potential beginning of a range)
let tok_i = b:txtfmt_cfg_{fg_or_bg{fgbg_idx}}color{1}
let tok_cnt = 1
let i = 2 " first active color already accounted for
" Initialize regex string to be built within loop
let b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_stok_atom = ''
" Loop over active colors (and one fictitious element past end of
" array). Note that first active color was handled in initializations.
while i <= b:txtfmt_cfg_num{fg_or_bg{fgbg_idx}}colors + 1
" We know something is ending (atom or range) if any of the
" following conditions is true:
" -We're on the non-existent element one past end of active color
" array
" -The current active color index is not 1 greater than the last
" TODO_BG: Can't compare with tok_i + 1 since tok_i isn't
" updated through range.
if i >= b:txtfmt_cfg_num{fg_or_bg{fgbg_idx}}colors + 1 || (b:txtfmt_cfg_{fg_or_bg{fgbg_idx}}color{i} != tok_i + tok_cnt)
" Something is ending
if tok_cnt > 1
" Append range if more than 2 chars; otherwise, make
" it a double atom.
let b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_stok_atom = b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_stok_atom
\.nr2char(b:txtfmt_{clr_or_bgc{fgbg_idx}}_first_tok + tok_i)
\.(tok_cnt > 2 ? '-' : '')
\.nr2char(b:txtfmt_{clr_or_bgc{fgbg_idx}}_first_tok + tok_i + tok_cnt - 1)
else
" Append atom
let b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_stok_atom = b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_stok_atom
\.nr2char(b:txtfmt_{clr_or_bgc{fgbg_idx}}_first_tok + tok_i)
endif
" Start something new unless at end
if i <= b:txtfmt_cfg_num{fg_or_bg{fgbg_idx}}colors
let tok_cnt = 1
let tok_i = b:txtfmt_cfg_{fg_or_bg{fgbg_idx}}color{i}
endif
else
" Nothing is ending - record continuation
let tok_cnt = tok_cnt + 1
endif
let i = i + 1
endwhile
" Create the _tok_ version from the _stok_ version by prepending
" default (end) token
" Decision Needed: Do I want to create tok this way, or would it be
" better to gain a slight bit of highlighting efficiency in some cases
" by putting the default tok into a range if possible.
" Note: I'm leaning against this optimization. Consider that it would
" be possible only for color configurations in which the first color
" is active; hence, if there were any speed difference in the
" highlighting (and a significant one is doubtful in my opinion), it
" would depend upon the specific color masks, which seems inconsistent
" and therefore inappropriate.
let b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_tok_atom =
\ nr2char(b:txtfmt_{clr_or_bgc{fgbg_idx}}_first_tok)
\ . b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_stok_atom
let b:txtfmt_re_{clr_or_bgc{fgbg_idx}}_etok_atom =
\ nr2char(b:txtfmt_{clr_or_bgc{fgbg_idx}}_first_tok)
endif
let fgbg_idx = fgbg_idx + 1
endwhile
" Format region tokens
let b:txtfmt_re_fmt_tok_atom = nr2char(b:txtfmt_fmt_first_tok).'-'.nr2char(b:txtfmt_fmt_last_tok)
let b:txtfmt_re_fmt_stok_atom = nr2char(b:txtfmt_fmt_first_tok + 1).'-'.nr2char(b:txtfmt_fmt_last_tok)
let b:txtfmt_re_fmt_etok_atom = nr2char(b:txtfmt_fmt_first_tok)
" Color regions that include inactive colors
if clr
let b:txtfmt_re_CLR_tok_atom = nr2char(b:txtfmt_clr_first_tok).'-'.nr2char(b:txtfmt_clr_last_tok)
let b:txtfmt_re_CLR_stok_atom = nr2char(b:txtfmt_clr_first_tok + 1).'-'.nr2char(b:txtfmt_clr_last_tok)
let b:txtfmt_re_CLR_etok_atom = nr2char(b:txtfmt_clr_first_tok)
endif
if bgc
let b:txtfmt_re_BGC_tok_atom = nr2char(b:txtfmt_bgc_first_tok).'-'.nr2char(b:txtfmt_bgc_last_tok)
let b:txtfmt_re_BGC_stok_atom = nr2char(b:txtfmt_bgc_first_tok + 1).'-'.nr2char(b:txtfmt_bgc_last_tok)
let b:txtfmt_re_BGC_etok_atom = nr2char(b:txtfmt_bgc_first_tok)
endif
" Combined regions
let b:txtfmt_re_any_tok_atom =
\(clr ? b:txtfmt_re_clr_tok_atom : '')
\.nr2char(b:txtfmt_fmt_first_tok).'-'.nr2char(b:txtfmt_fmt_last_tok)
\.(bgc ? b:txtfmt_re_bgc_tok_atom : '')
" TODO: Perhaps get rid of dependence upon b:txtfmt_clr_last_tok?
" TODO: Refactor to use newly-created CLR and BGC atoms
let b:txtfmt_re_ANY_tok_atom =
\(clr ? nr2char(b:txtfmt_clr_first_tok).'-'.nr2char(b:txtfmt_clr_last_tok) : '')
\.nr2char(b:txtfmt_fmt_first_tok).'-'.nr2char(b:txtfmt_fmt_last_tok)
\.(bgc ? nr2char(b:txtfmt_bgc_first_tok).'-'.nr2char(b:txtfmt_bgc_last_tok) : '')
let b:txtfmt_re_any_stok_atom =
\(clr ? b:txtfmt_re_clr_stok_atom : '')
\.nr2char(b:txtfmt_fmt_first_tok + 1).'-'.nr2char(b:txtfmt_fmt_last_tok)
\.(bgc ? b:txtfmt_re_bgc_stok_atom : '')
let b:txtfmt_re_any_etok_atom =
\(clr ? b:txtfmt_re_clr_etok_atom : '')
\.b:txtfmt_re_fmt_etok_atom
\.(bgc ? b:txtfmt_re_bgc_etok_atom : '')
if b:txtfmt_cfg_escape == 'bslash'
" The following pattern is a zero-width look-behind assertion, which
" matches only at a non-backslash-escaped position.
let noesc = '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@<!'
" Make this persistent, as it's used elsewhere...
let b:re_no_bslash_esc = noesc
" clr
if clr
" Active clr only
let b:txtfmt_re_clr_tok = noesc.'['.b:txtfmt_re_clr_tok_atom.']'
let b:txtfmt_re_clr_stok = noesc.'['.b:txtfmt_re_clr_stok_atom.']'
let b:txtfmt_re_clr_etok = noesc.b:txtfmt_re_clr_etok_atom
let b:txtfmt_re_clr_ntok = '\%('.b:txtfmt_re_clr_tok.'\)\@!.'
" Active and inactive clr
let b:txtfmt_re_CLR_tok = noesc.'['.b:txtfmt_re_CLR_tok_atom.']'
let b:txtfmt_re_CLR_stok = noesc.'['.b:txtfmt_re_CLR_stok_atom.']'
let b:txtfmt_re_CLR_etok = noesc.b:txtfmt_re_CLR_etok_atom
let b:txtfmt_re_CLR_ntok = '\%('.b:txtfmt_re_CLR_tok.'\)\@!.'
endif
" bgc
if bgc
" Active bgc only
let b:txtfmt_re_bgc_tok = noesc.'['.b:txtfmt_re_bgc_tok_atom.']'
let b:txtfmt_re_bgc_stok = noesc.'['.b:txtfmt_re_bgc_stok_atom.']'
let b:txtfmt_re_bgc_etok = noesc.b:txtfmt_re_bgc_etok_atom
let b:txtfmt_re_bgc_ntok = '\%('.b:txtfmt_re_bgc_tok.'\)\@!.'
" Active and inactive bgc
let b:txtfmt_re_BGC_tok = noesc.'['.b:txtfmt_re_BGC_tok_atom.']'
let b:txtfmt_re_BGC_stok = noesc.'['.b:txtfmt_re_BGC_stok_atom.']'
let b:txtfmt_re_BGC_etok = noesc.b:txtfmt_re_BGC_etok_atom
let b:txtfmt_re_BGC_ntok = '\%('.b:txtfmt_re_BGC_tok.'\)\@!.'
endif
" fmt
let b:txtfmt_re_fmt_tok = noesc.'['.b:txtfmt_re_fmt_tok_atom.']'
let b:txtfmt_re_fmt_stok = noesc.'['.b:txtfmt_re_fmt_stok_atom.']'
let b:txtfmt_re_fmt_etok = noesc.b:txtfmt_re_fmt_etok_atom
let b:txtfmt_re_fmt_ntok = '\%('.b:txtfmt_re_fmt_tok.'\)\@!.'
" clr/bgc/fmt combined
let b:txtfmt_re_any_tok = noesc.'['.b:txtfmt_re_any_tok_atom.']'
let b:txtfmt_re_ANY_tok = noesc.'['.b:txtfmt_re_ANY_tok_atom.']'
let b:txtfmt_re_any_stok = noesc.'['.b:txtfmt_re_any_stok_atom.']'
let b:txtfmt_re_any_etok =
\ noesc.'['
\ . (clr ? b:txtfmt_re_clr_etok_atom : '')
\ . (bgc ? b:txtfmt_re_bgc_etok_atom : '')
\ . b:txtfmt_re_fmt_etok_atom
\ . ']'
let b:txtfmt_re_any_ntok = '\%('.b:txtfmt_re_any_tok.'\)\@!.'
elseif b:txtfmt_cfg_escape == 'self'
" The following pattern serves as the template for finding tokens that
" are neither escaping nor escaped.
let tmpl = '\%(\(placeholder\)\%(\1\)\@!\)\@=\%(\%(^\|\%(\1\)\@!.\)\%(\1\1\)*\1\)\@<!.'
" Make this persistent, as it's used elsewhere...
let b:re_no_self_esc = tmpl
" clr
if clr
" Active clr only
let b:txtfmt_re_clr_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_clr_tok_atom.']', '')
let b:txtfmt_re_clr_stok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_clr_stok_atom.']', '')
let b:txtfmt_re_clr_etok = substitute(tmpl, 'placeholder', b:txtfmt_re_clr_etok_atom, '')
let b:txtfmt_re_clr_ntok = '\%('.b:txtfmt_re_clr_tok.'\)\@!.'
" Active and inactive clr
let b:txtfmt_re_CLR_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_CLR_tok_atom.']', '')
let b:txtfmt_re_CLR_stok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_CLR_stok_atom.']', '')
let b:txtfmt_re_CLR_etok = substitute(tmpl, 'placeholder', b:txtfmt_re_CLR_etok_atom, '')
let b:txtfmt_re_CLR_ntok = '\%('.b:txtfmt_re_CLR_tok.'\)\@!.'
endif
" bgc
if bgc
" Active bgc only
let b:txtfmt_re_bgc_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_bgc_tok_atom.']', '')
let b:txtfmt_re_bgc_stok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_bgc_stok_atom.']', '')
let b:txtfmt_re_bgc_etok = substitute(tmpl, 'placeholder', b:txtfmt_re_bgc_etok_atom, '')
let b:txtfmt_re_bgc_ntok = '\%('.b:txtfmt_re_bgc_tok.'\)\@!.'
" Active and inactive bgc
let b:txtfmt_re_BGC_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_BGC_tok_atom.']', '')
let b:txtfmt_re_BGC_stok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_BGC_stok_atom.']', '')
let b:txtfmt_re_BGC_etok = substitute(tmpl, 'placeholder', b:txtfmt_re_BGC_etok_atom, '')
let b:txtfmt_re_BGC_ntok = '\%('.b:txtfmt_re_BGC_tok.'\)\@!.'
endif
" fmt
let b:txtfmt_re_fmt_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_fmt_tok_atom.']', '')
let b:txtfmt_re_fmt_stok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_fmt_stok_atom.']', '')
let b:txtfmt_re_fmt_etok = substitute(tmpl, 'placeholder', b:txtfmt_re_fmt_etok_atom, '')
let b:txtfmt_re_fmt_ntok = '\%('.b:txtfmt_re_fmt_tok.'\)\@!.'
" clr/bgc/fmt combined
let b:txtfmt_re_any_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_any_tok_atom.']', '')
let b:txtfmt_re_ANY_tok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_ANY_tok_atom.']', '')
let b:txtfmt_re_any_stok = substitute(tmpl, 'placeholder', '['.b:txtfmt_re_any_stok_atom.']', '')
let b:txtfmt_re_any_etok = substitute(tmpl, 'placeholder',
\'['
\.(clr ? nr2char(b:txtfmt_clr_first_tok) : '')
\.(bgc ? nr2char(b:txtfmt_bgc_first_tok) : '')
\.nr2char(b:txtfmt_fmt_first_tok)
\.']', '')
let b:txtfmt_re_any_ntok = '\%('.b:txtfmt_re_any_tok.'\)\@!.'
else
" No escaping of tokens
" clr
if clr
" Active clr only
let b:txtfmt_re_clr_tok = '['.b:txtfmt_re_clr_tok_atom.']'
let b:txtfmt_re_clr_stok = '['.b:txtfmt_re_clr_stok_atom.']'
let b:txtfmt_re_clr_etok = b:txtfmt_re_clr_etok_atom
let b:txtfmt_re_clr_ntok = '[^'.b:txtfmt_re_clr_tok_atom.']'
" Active and inactive clr
let b:txtfmt_re_CLR_tok = '['.b:txtfmt_re_CLR_tok_atom.']'
let b:txtfmt_re_CLR_stok = '['.b:txtfmt_re_CLR_stok_atom.']'
let b:txtfmt_re_CLR_etok = b:txtfmt_re_CLR_etok_atom
let b:txtfmt_re_CLR_ntok = '[^'.b:txtfmt_re_CLR_tok_atom.']'
endif
" bgc
if bgc
" Active bgc only
let b:txtfmt_re_bgc_tok = '['.b:txtfmt_re_bgc_tok_atom.']'
let b:txtfmt_re_bgc_stok = '['.b:txtfmt_re_bgc_stok_atom.']'
let b:txtfmt_re_bgc_etok = b:txtfmt_re_bgc_etok_atom
let b:txtfmt_re_bgc_ntok = '[^'.b:txtfmt_re_bgc_tok_atom.']'
" Active and inactive bgc
let b:txtfmt_re_BGC_tok = '['.b:txtfmt_re_BGC_tok_atom.']'
let b:txtfmt_re_BGC_stok = '['.b:txtfmt_re_BGC_stok_atom.']'
let b:txtfmt_re_BGC_etok = b:txtfmt_re_BGC_etok_atom
let b:txtfmt_re_BGC_ntok = '[^'.b:txtfmt_re_BGC_tok_atom.']'
endif
" fmt
let b:txtfmt_re_fmt_tok = '['.b:txtfmt_re_fmt_tok_atom.']'
let b:txtfmt_re_fmt_stok = '['.b:txtfmt_re_fmt_stok_atom.']'
let b:txtfmt_re_fmt_etok = b:txtfmt_re_fmt_etok_atom
let b:txtfmt_re_fmt_ntok = '[^'.b:txtfmt_re_fmt_tok_atom.']'
" clr/bgc/fmt combined
let b:txtfmt_re_any_tok = '['.b:txtfmt_re_any_tok_atom.']'
let b:txtfmt_re_ANY_tok = '['.b:txtfmt_re_ANY_tok_atom.']'
let b:txtfmt_re_any_stok = '['.b:txtfmt_re_any_stok_atom.']'
let b:txtfmt_re_any_etok =
\'['
\.(clr ? b:txtfmt_re_clr_etok_atom : '')
\.(bgc ? b:txtfmt_re_bgc_etok_atom : '')
\.b:txtfmt_re_fmt_etok_atom
\.']'
let b:txtfmt_re_any_ntok =
\'[^'
\.(clr ? b:txtfmt_re_clr_tok_atom : '')
\.(bgc ? b:txtfmt_re_bgc_tok_atom : '')
\.b:txtfmt_re_fmt_tok_atom
\.']'
endif
endfu
" >>>
" Function: s:Do_config_common() <<<
" Purpose: Set script local variables, taking into account whether user has
" overriden via txtfmt globals.
fu! s:Do_config_common()
" unlet any buffer-specific options that may be set by txtfmt modeline <<<
unlet! b:txtfmt_cfg_tokrange
\ b:txtfmt_cfg_sync b:txtfmt_cfg_escape
\ b:txtfmt_cfg_pack b:txtfmt_cfg_nested
\ b:txtfmt_cfg_numfgcolors b:txtfmt_cfg_numbgcolors
\ b:txtfmt_cfg_fgcolormask b:txtfmt_cfg_bgcolormask
\ b:txtfmt_cfg_undercurlpref b:txtfmt_cfg_conceal
\ b:txtfmt_cfg_concealcursor b:txtfmt_cfg_concealcursor_invalid
" >>>
" Attempt to process modeline <<<
let ml_status = s:Do_txtfmt_modeline()
" >>>
" 'escape' option <<<
if !exists('b:txtfmt_cfg_escape') || strlen(b:txtfmt_cfg_escape) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
unlet! l:bad_set_by
if exists('b:txtfmt_cfg_escape') && strlen(b:txtfmt_cfg_escape) == 0
" Bad modeline set
let l:bad_set_by = 'm'
elseif exists('b:txtfmtEscape')
" User overrode buf-local option
if s:Escape_is_valid(b:txtfmtEscape)
let b:txtfmt_cfg_escape = b:txtfmtEscape
else
let l:bad_set_by = 'b'
endif
elseif exists('g:txtfmtEscape')
" User overrode global option
if s:Escape_is_valid(g:txtfmtEscape)
let b:txtfmt_cfg_escape = g:txtfmtEscape
else
let l:bad_set_by = 'g'
endif
endif
" Warn user if invalid user-setting is about to be overridden
if exists('l:bad_set_by')
" Note: Display the offending option value for buf-local or global
" option, but not for modeline, since modeline processing has
" already reported the error.
echoerr "Warning: Ignoring invalid ".(
\ l:bad_set_by == 'm' ? "modeline" :
\ l:bad_set_by == 'b' ? "buf-local" :
\ "global") . " value for txtfmt `escape' option" . (
\ l:bad_set_by == 'm' ? '' :
\ l:bad_set_by == 'b' ? (': ' . b:txtfmtEscape) :
\ (': ' . g:txtfmtEscape))
endif
if !exists('b:txtfmt_cfg_escape') || strlen(b:txtfmt_cfg_escape) == 0
" Set to default
let b:txtfmt_cfg_escape = 'none'
endif
endif
" >>>
" 'pack' option <<<
if !exists('b:txtfmt_cfg_pack') || strlen(b:txtfmt_cfg_pack) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
if exists('b:txtfmt_cfg_pack') && strlen(b:txtfmt_cfg_pack) == 0
" Bad modeline set. Warn user that we're about to override. Note
" that modeline processing has already reported the specific
" error.
echoerr "Warning: Ignoring invalid modeline value for txtfmt `pack' option"
elseif exists('b:txtfmtPack')
" User overrode buf-local option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_pack = b:txtfmtPack
elseif exists('g:txtfmtPack')
" User overrode global option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_pack = g:txtfmtPack
endif
if !exists('b:txtfmt_cfg_pack') || strlen(b:txtfmt_cfg_pack) == 0
" Set to default (on)
let b:txtfmt_cfg_pack = 1
endif
endif
" >>>
" 'undercurl' option <<<
if !exists('b:txtfmt_cfg_undercurlpref') || strlen(b:txtfmt_cfg_undercurlpref) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
if exists('b:txtfmt_cfg_undercurlpref') && strlen(b:txtfmt_cfg_undercurlpref) == 0
" Bad modeline set. Warn user that we're about to override. Note
" that modeline processing has already reported the specific
" error.
echoerr "Warning: Ignoring invalid modeline value for txtfmt `undercurl' option"
elseif exists('b:txtfmtUndercurl')
" User overrode buf-local option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_undercurlpref = b:txtfmtUndercurl
elseif exists('g:txtfmtUndercurl')
" User overrode global option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_undercurlpref = g:txtfmtUndercurl
endif
if !exists('b:txtfmt_cfg_undercurlpref') || strlen(b:txtfmt_cfg_undercurlpref) == 0
" Set to default (on)
" Note: This is 'preference' only; if Vim version doesn't support
" undercurl, we won't attempt to enable.
let b:txtfmt_cfg_undercurlpref = 1
endif
endif
" >>>
" 'nested' option <<<
if !exists('b:txtfmt_cfg_nested') || strlen(b:txtfmt_cfg_nested) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
if exists('b:txtfmt_cfg_nested') && strlen(b:txtfmt_cfg_nested) == 0
" Bad modeline set. Warn user that we're about to override. Note
" that modeline processing has already reported the specific
" error.
echoerr "Warning: Ignoring invalid modeline value for txtfmt `nested' option"
elseif exists('b:txtfmtNested')
" User overrode buf-local option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_nested = b:txtfmtNested
elseif exists('g:txtfmtNested')
" User overrode global option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_nested = g:txtfmtNested
endif
if !exists('b:txtfmt_cfg_nested') || strlen(b:txtfmt_cfg_nested) == 0
" Set to default (on)
let b:txtfmt_cfg_nested = 1
endif
endif
" >>>
" 'conceal' option <<<
if !exists('b:txtfmt_cfg_conceal') || strlen(b:txtfmt_cfg_conceal) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
if exists('b:txtfmt_cfg_conceal')
if strlen(b:txtfmt_cfg_conceal) == 0
" Bad modeline set. Warn user that we're about to override. Note
" that modeline processing has already reported the specific
" error.
echoerr "Warning: Ignoring invalid modeline value for txtfmt `conceal' option"
elseif b:txtfmt_cfg_conceal && !has('conceal')
" Modeline enables 'conceal' but the feature isn't supported
" by Vim. Silently override the request.
let b:txtfmt_cfg_conceal = 0
endif
elseif exists('b:txtfmtConceal')
" User overrode buf-local option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_conceal = has('conceal') && b:txtfmtConceal
elseif exists('g:txtfmtConceal')
" User overrode global option
" Note: Invalid setting impossible for boolean
let b:txtfmt_cfg_conceal = has('conceal') && g:txtfmtConceal
endif
if !exists('b:txtfmt_cfg_conceal') || strlen(b:txtfmt_cfg_conceal) == 0
" Enable if support for 'conceal' feature is compiled into Vim.
" Note: This is a slightly non-backwards-compatible change with
" respect to Txtfmt version 2.3. See help for explanation.
let b:txtfmt_cfg_conceal = has('conceal')
endif
endif
" >>>
" 'concealcursor' option (dependent upon 'conceal') <<<
" Note: The value of this option will be ignored if 'conceal' is off (in
" which case, it might be safest to unset it).
if b:txtfmt_cfg_conceal
if !exists('b:txtfmt_cfg_concealcursor') || exists('b:txtfmt_cfg_concealcursor_invalid')
" Either option wasn't set within modeline, or it was set to invalid
" value
unlet! l:bad_set_by
if exists('b:txtfmt_cfg_concealcursor_invalid')
" Bad modeline set
let l:bad_set_by = 'm'
elseif exists('b:txtfmtConcealcursor')
" User overrode buf-local option
if s:Concealcursor_is_valid(b:txtfmtConcealcursor)
let b:txtfmt_cfg_concealcursor = b:txtfmtConcealcursor
else
let l:bad_set_by = 'b'
endif
elseif exists('g:txtfmtConcealcursor')
" User overrode global option
if s:Concealcursor_is_valid(g:txtfmtConcealcursor)
let b:txtfmt_cfg_concealcursor = g:txtfmtConcealcursor
else
let l:bad_set_by = 'g'
endif
endif
" Warn user if invalid user-setting is about to be overridden
if exists('l:bad_set_by')
" Note: Display the offending option value for buf-local or global
" option, but not for modeline, since modeline processing has
" already reported the error.
echoerr "Warning: Ignoring invalid ".(
\ l:bad_set_by == 'm' ? "modeline" :
\ l:bad_set_by == 'b' ? "buf-local" :
\ "global") . " value for txtfmt `concealcursor' option" . (
\ l:bad_set_by == 'm' ? '' :
\ l:bad_set_by == 'b' ? (': ' . b:txtfmtConcealcursor) :
\ (': ' . g:txtfmtConcealcursor))
endif
if !exists('b:txtfmt_cfg_concealcursor') || exists('b:txtfmt_cfg_concealcursor_invalid')
" By default, conceal tokens always, even in cursor line
let b:txtfmt_cfg_concealcursor = 'nvic'
endif
" At this point, b:txtfmt_cfg_concealcursor_invalid has served its
" purpose, and although it's unlet at the head of this function,
" I'd rather not leave an 'invalid' var in existence.
unlet! b:txtfmt_cfg_concealcursor_invalid
endif
else
" This option is N/A when 'conceal' is off
unlet! b:txtfmt_cfg_concealcursor
endif
" >>>
" 'tokrange' option <<<
" Note: 'starttok' and 'formats' are distinct options internally, but may
" be set only as a unit by the plugin user. Even if tokrange was set
" within modeline, there is work yet to be done.
call s:Set_tokrange()
" >>>
" 'fgcolormask' option <<<
if !exists('b:txtfmt_cfg_fgcolormask') || strlen(b:txtfmt_cfg_fgcolormask) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
unlet! l:bad_set_by
if exists('b:txtfmt_cfg_fgcolormask') && strlen(b:txtfmt_cfg_fgcolormask) == 0
" Bad modeline set
let l:bad_set_by = 'm'
elseif exists('b:txtfmtFgcolormask')
" User overrode buf-local option
if s:Clrmask_is_valid(b:txtfmtFgcolormask)
let b:txtfmt_cfg_fgcolormask = b:txtfmtFgcolormask
else
let l:bad_set_by = 'b'
endif
elseif exists('g:txtfmtFgcolormask')
" User overrode global option
if s:Clrmask_is_valid(g:txtfmtFgcolormask)
let b:txtfmt_cfg_fgcolormask = g:txtfmtFgcolormask
else
let l:bad_set_by = 'g'
endif
endif
" Warn user if invalid user-setting is about to be overridden
if exists('l:bad_set_by')
" Note: Display the offending option value for buf-local or global
" option, but not for modeline, since modeline processing has
" already reported the error.
echoerr "Warning: Ignoring invalid ".(
\ l:bad_set_by == 'm' ? "modeline" :
\ l:bad_set_by == 'b' ? "buf-local" :
\ "global") . " value for txtfmt `fgcolormask' option" . (
\ l:bad_set_by == 'm' ? '' :
\ l:bad_set_by == 'b' ? (': ' . b:txtfmtFgcolormask) :
\ (': ' . g:txtfmtFgcolormask))
endif
if !exists('b:txtfmt_cfg_fgcolormask') || strlen(b:txtfmt_cfg_fgcolormask) == 0
" Set to default - all foreground colors active (for backward
" compatibility)
" TODO: Don't hardcode
let b:txtfmt_cfg_fgcolormask = '11111111'
endif
endif
" >>>
" 'bgcolormask' option <<<
if !exists('b:txtfmt_cfg_bgcolormask') || strlen(b:txtfmt_cfg_bgcolormask) == 0
" Either option wasn't set within modeline, or it was set to invalid
" value
unlet! l:bad_set_by
if exists('b:txtfmt_cfg_bgcolormask') && strlen(b:txtfmt_cfg_bgcolormask) == 0
" Bad modeline set
let l:bad_set_by = 'm'
elseif exists('b:txtfmtBgcolormask')
" User overrode buf-local option
if s:Clrmask_is_valid(b:txtfmtBgcolormask)
let b:txtfmt_cfg_bgcolormask = b:txtfmtBgcolormask
else
let l:bad_set_by = 'b'
endif
elseif exists('g:txtfmtBgcolormask')
" User overrode global option
if s:Clrmask_is_valid(g:txtfmtBgcolormask)
let b:txtfmt_cfg_bgcolormask = g:txtfmtBgcolormask
else
let l:bad_set_by = 'g'
endif
endif
" Warn user if invalid user-setting is about to be overridden
if exists('l:bad_set_by')
" Note: Display the offending option value for buf-local or global
" option, but not for modeline, since modeline processing has
" already reported the error.
echoerr "Warning: Ignoring invalid ".(
\ l:bad_set_by == 'm' ? "modeline" :
\ l:bad_set_by == 'b' ? "buf-local" :
\ "global") . " value for txtfmt `bgcolormask' option" . (
\ l:bad_set_by == 'm' ? '' :
\ l:bad_set_by == 'b' ? (': ' . b:txtfmtBgcolormask) :
\ (': ' . g:txtfmtBgcolormask))
endif
if !exists('b:txtfmt_cfg_bgcolormask') || strlen(b:txtfmt_cfg_bgcolormask) == 0
" Set to default of red, green and blue if background colors are
" active; otherwise, disable all colors.
" TODO_BG: Decide whether it makes sense to unlet the variable
" completely.
" TODO_BG: b:txtfmt_cfg_bgcolor is probably not set yet!!!! This
" needs to be moved till after Set_tokrange
if b:txtfmt_cfg_bgcolor
" TODO: Don't hardcode
let b:txtfmt_cfg_bgcolormask = '01101000'
else
" No background color supported
let b:txtfmt_cfg_bgcolormask = '00000000'
endif
endif
endif
" Force mask to all zeroes if background colors are disabled.
" Assumption: Set_tokrange has already run; thus, b:txtfmt_cfg_bgcolor has
" been set.
if !b:txtfmt_cfg_bgcolor
let b:txtfmt_cfg_bgcolormask = '00000000'
endif
" >>>
" 'sync' option <<<
" Note: 'syncmethod' and 'synclines' are distinct options internally, but
" may be set only as a unit by the plugin user. Even if sync was set
" within modeline, there is work yet to be done.
call s:Set_syncing()
" >>>
" Define various buffer-specific variables now that fmt/clr ranges are fixed.
" TODO: Perhaps combine the following 2 functions in some way...
call s:Define_fmtclr_vars()
" Define fmt/clr regexes - used in both syntax and ftplugin <<<
call s:Define_fmtclr_regexes()
" >>>
" Process color options <<<
call s:Process_color_options()
" >>>
endfu
" >>>
call s:Do_config_common()
" Define buffer-local constants <<<
" For convenience, associate format indices with their respective
" '[u][b][i][s][r][c]' string, in fiducial form. Note that fiducial form may
" be used for display, but is also a valid (but not the only) fmt spec.
let b:ubisrc_fmt0 = '-'
let b:ubisrc_fmt1 = 'u'
let b:ubisrc_fmt2 = 'b'
let b:ubisrc_fmt3 = 'bu'
let b:ubisrc_fmt4 = 'i'
let b:ubisrc_fmt5 = 'iu'
let b:ubisrc_fmt6 = 'ib'
let b:ubisrc_fmt7 = 'ibu'
let b:ubisrc_fmt8 = 's'
let b:ubisrc_fmt9 = 'su'
let b:ubisrc_fmt10 = 'sb'
let b:ubisrc_fmt11 = 'sbu'
let b:ubisrc_fmt12 = 'si'
let b:ubisrc_fmt13 = 'siu'
let b:ubisrc_fmt14 = 'sib'
let b:ubisrc_fmt15 = 'sibu'
let b:ubisrc_fmt16 = 'r'
let b:ubisrc_fmt17 = 'ru'
let b:ubisrc_fmt18 = 'rb'
let b:ubisrc_fmt19 = 'rbu'
let b:ubisrc_fmt20 = 'ri'
let b:ubisrc_fmt21 = 'riu'
let b:ubisrc_fmt22 = 'rib'
let b:ubisrc_fmt23 = 'ribu'
let b:ubisrc_fmt24 = 'rs'
let b:ubisrc_fmt25 = 'rsu'
let b:ubisrc_fmt26 = 'rsb'
let b:ubisrc_fmt27 = 'rsbu'
let b:ubisrc_fmt28 = 'rsi'
let b:ubisrc_fmt29 = 'rsiu'
let b:ubisrc_fmt30 = 'rsib'
let b:ubisrc_fmt31 = 'rsibu'
let b:ubisrc_fmt32 = 'c'
let b:ubisrc_fmt33 = 'cu'
let b:ubisrc_fmt34 = 'cb'
let b:ubisrc_fmt35 = 'cbu'
let b:ubisrc_fmt36 = 'ci'
let b:ubisrc_fmt37 = 'ciu'
let b:ubisrc_fmt38 = 'cib'
let b:ubisrc_fmt39 = 'cibu'
let b:ubisrc_fmt40 = 'cs'
let b:ubisrc_fmt41 = 'csu'
let b:ubisrc_fmt42 = 'csb'
let b:ubisrc_fmt43 = 'csbu'
let b:ubisrc_fmt44 = 'csi'
let b:ubisrc_fmt45 = 'csiu'
let b:ubisrc_fmt46 = 'csib'
let b:ubisrc_fmt47 = 'csibu'
let b:ubisrc_fmt48 = 'cr'
let b:ubisrc_fmt49 = 'cru'
let b:ubisrc_fmt50 = 'crb'
let b:ubisrc_fmt51 = 'crbu'
let b:ubisrc_fmt52 = 'cri'
let b:ubisrc_fmt53 = 'criu'
let b:ubisrc_fmt54 = 'crib'
let b:ubisrc_fmt55 = 'cribu'
let b:ubisrc_fmt56 = 'crs'
let b:ubisrc_fmt57 = 'crsu'
let b:ubisrc_fmt58 = 'crsb'
let b:ubisrc_fmt59 = 'crsbu'
let b:ubisrc_fmt60 = 'crsi'
let b:ubisrc_fmt61 = 'crsiu'
let b:ubisrc_fmt62 = 'crsib'
let b:ubisrc_fmt63 = 'crsibu'
" >>>
else " if exists('b:txtfmt_do_common_config')
" Function: s:Txtfmt_refresh() <<<
" Purpose: Invoked by buffer-local command Refresh when user wishes to
" reload txtfmt plugins safely for the current buffer; e.g., after changing
" option settings.
" Important Note: This function must be within the else of an if
" exists('b:txtfmt_do_common_config'); otherwise, we will get an error when this
" function causes the plugins to be re-sourced, since the re-sourcing of this
" file will result in an attempt to redefine the function while it is running!
fu! s:Txtfmt_refresh()
" Ensure that common configuration code will not be skipped next time
unlet! b:txtfmt_did_common_config
" Determine whether txtfmt ftplugin is loaded
if exists('b:loaded_txtfmt')
" b:loaded_txtfmt is set only within ftplugin/txtfmt.vim and unlet by
" b:undo_ftplugin; hence, its existence indicates that txtfmt ftplugin
" is currently loaded. Cache the filetype that was cached at load
" time.
let l:current_filetype = b:txtfmt_filetype
endif
" Determine whether txtfmt syntax plugin is loaded
let v:errmsg = ''
silent! syn sync match Tf_existence_test grouphere Tf_fmt_1 /\%^/
if v:errmsg == ''
" No error means txtfmt syntax plugin is loaded. Cache the syntax name
" that was cached at load time.
let l:current_syntax = b:txtfmt_syntax
endif
" Is there anything to refresh?
if !exists('l:current_filetype') && !exists('l:current_syntax')
echomsg "Warning: Useless call to Refresh: "
\."no txtfmt plugins appear to be loaded."
return
endif
" If here, there was a reason for the Txtfmt_refresh call. Cause ftplugin
" and/or syntax plugin to be reloaded via FileType and Syntax sets, as
" appropriate.
if exists('l:current_syntax')
" We're going to attempt to reload syntax plugin. Unload it now
" (causing b:current_syntax to be unlet). If we set filetype below,
" and b:current_syntax exists afterwards, we'll know syntax was loaded
" via syntaxset autocmd linked to FileType event. Alternatively,
" could simply unlet b:current_syntax here...
set syntax=OFF
endif
if exists('l:current_filetype')
" Set filetype to whatever it was before
exe 'set filetype=' . l:current_filetype
endif
if exists('l:current_syntax')
" Syntax may have been loaded already, but if not, we'll need to do it
" manually...
if exists('b:current_syntax')
" Syntax was loaded as a result of the filetype set. Make sure it
" appears to be the correct one.
if b:current_syntax != l:current_syntax
echomsg "Warning: Txtfmt attempted to restore syntax to `"
\.l:current_syntax."'. Result was `".b:current_syntax."'"
echomsg "I'm guessing you have loaded the txtfmt plugins "
\."in a non-standard manner. See txtfmt help for more information."
endif
else
" Syntax wasn't linked to filetype. Load the desired syntax manually.
exe 'set syntax=' . l:current_syntax
endif
endif
endfu
" >>>
endif " if exists('b:txtfmt_do_common_config')
" General-purpose utilities <<<
" Note: These utilities are defined globally in the plugin file so that they
" might be used by any of the Txtfmt script files.
" Naming Convention: All of these utilities should begin with 'TxtfmtUtil_'
" and should separate internal 'words' with underscore. Internal words should
" not be capitalized.
" Function: TxtfmtUtil_num_to_hex_str <<<
" Description: Take the input value and convert it to a hex string of the form
" 0xXXXX.
" Format Note: Output string will have '0x' prepended, but will omit leading
" zeroes.
fu! TxtfmtUtil_num_to_hex_str(num)
" Get writable copy
let num = a:num
" Loop until the value has been completely processed
let str = ''
let abcdef = "ABCDEF"
while num > 0
let dig = num % 16
" Convert the digit value to a hex digit and prepend to hex str
if dig <= 9
let str = dig . str
else
let str = strpart(abcdef, dig - 10, 1) . str
endif
let num = num / 16
endwhile
" Prepend '0x' to hex string built in loop
" Note: If string is empty, it should be '0'
return '0x' . (strlen(str) == 0 ? '0' : str)
endfu
" >>>
" >>>
" Function: s:MakeTestPage() <<<
" Purpose: Build a "test-page" in a scratch buffer, to show user how color
" and format regions will look with current definitions and on current
" terminal. (Useful to prevent user from having to insert all the color and
" format regions manually with text such as "Here's a little test...")
" How: Create a scratch buffer whose filetype is set to txtfmt. Add some
" explanation lines at the top, followed by one line for each active color, as
" follows:
" color<num> none i b bi u ui ub ubi ...
" Repeat for each active background color...
" Note: The function is script-local, as it is designed to be invoked from a
" command.
" IMPORTANT NOTE: Special care must be taken when defining this function, as
" it creates a buffer with 'ft' set to txtfmt, 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:MakeTestPage')
fu! s:MakeTestPage(...)
if a:0 == 1
" User provided optional modeline arguments. Before opening scratch
" buffer, make sure the modeline constructed from the arguments has at
" least the overall appearance of being valid. (Option name/value
" validation is performed only after opening the test page buffer.)
if !s:Is_txtfmt_modeline("\<Tab>txtfmt:".a:1)
" Warn of invalid modeline and return without creating the test
" buffer
echoerr "Invalid arguments passed to :MakeTestPage command: `".a:1."'"
return
endif
endif
" Attempt to open the buffer (success not guaranteed)
if !s:New_window(s:TESTPAGE_WINHEIGHT)
" If E36, give helpful message about increasing size of current
" window; otherwise, given generic message followed by raw Vim
" error message.
if s:err_str =~ '^E36:'
echoerr "MakeTestPage(): Can't open test page. Increase size of current window and try again."
else
echoerr "MakeTestPage(): Error opening test page: " . s:err_str
endif
" Abort test page
return
endif
set buftype=nofile
set bufhidden=hide
set noswapfile
" The following setlocal is necessary to prevent E21 in the event that
" 'nomodifiable' is set globally.
setlocal modifiable
" If user provided modeline, add it to top of file before setting filetype
" to txtfmt...
if a:0 == 1
let modeline = a:1
if modeline =~ '\S'
call setline(1, "\<Tab>txtfmt:".modeline)
endif
elseif a:0 > 1
" This should never happen, since this function is called from a
" mapping.
echoerr "Too many arguments passed to MakeTestPage."
\." (Note that this function should not be called directly.)"
endif
set filetype=txtfmt
" Set page formatting options
" TODO - Decide whether the following are necessary anymore. (I'm
" formatting things explicitly now...)
set noai ts=4 sw=4 tw=78
set nowrap
" Cache some special tokens that will be used on this page
let tok_fb = Txtfmt_GetTokStr('fb')
let tok_fui = Txtfmt_GetTokStr('fui')
let tok_fu = Txtfmt_GetTokStr('fu')
let tok_fmt_end = nr2char(b:txtfmt_fmt_first_tok)
let tok_clr_end = nr2char(b:txtfmt_clr_first_tok)
" Important Note: Most of the following logic assumes that each token that
" is hidden by a txtfmt concealment group will appear as a single
" whitespace. If the 'conceal' patch is in effect, however, such tokens
" will not appear at all. The problem is that the token width is sometimes
" used to achieve the desired alignment. To facilitate keeping the
" alignment constant, I declare a variable that resolves to a single
" whitespace if and only if the conceal patch is in effect. This variable
" will be appended to tokens that would affect alignment in the absence of
" the conceal patch.
" Note: The space could go before or after the token, but after is best in
" the case of bg colors.
let cncl_ws = b:txtfmt_cfg_conceal ? ' ' : ''
call append(line('$'), tok_fb)
call append(line('$'),
\"************************")
$center
call append(line('$'),
\"*** TXTFMT TEST PAGE ***")
$center
call append(line('$'),
\"************************")
$center
call append(line('$'),
\"=============================================================================")
call append(line('$'),
\"*** Overview ***")
$center
call append(line('$'), tok_fmt_end)
call append(line('$'), "")
call append(line('$'),
\"The purpose of this page is to present an overview of the txtfmt highlighting")
call append(line('$'),
\"that results from the global txtfmt options and any txtfmt modeline settings")
call append(line('$'),
\"passed to the MakeTestPage command.")
call append(line('$'), "")
call append(line('$'),
\" :help txtfmt-MakeTestPage")
call append(line('$'), "")
call append(line('$'),
\"The text on the page has been chosen to display all possible combinations of")
call append(line('$'),
\"color and format regions, and if applicable, to illustrate the escaping of")
call append(line('$'),
\"tokens and the nesting of txtfmt regions.")
call append(line('$'), tok_fb)
call append(line('$'),
\"=============================================================================")
call append(line('$'),
\"*** Colors and Formats ***")
$center
call append(line('$'), tok_fui)
" Possible TODO: Use b:txtfmt_cfg_tokrange so that number format specified
" by user is respected.
call append(line('$'),
\'Configuration:'.tok_fb.cncl_ws
\."tokrange =".tok_fmt_end.cncl_ws
\.b:txtfmt_cfg_starttok_display.b:txtfmt_cfg_formats_display
\)
call append(line('$'), "")
call append(line('$'),
\"\<Tab>start token: ".b:txtfmt_cfg_starttok_display)
call append(line('$'),
\"\<Tab>background colors: ".(b:txtfmt_cfg_bgcolor
\? "enabled (".b:txtfmt_cfg_numbgcolors." active)"
\: "disabled"))
call append(line('$'),
\"\<Tab>".(b:txtfmt_cfg_longformats ? "'long'" : "'short'")." formats "
\.(b:txtfmt_cfg_longformats
\ ?
\ (b:txtfmt_cfg_undercurl
\ ? "with"
\ : "without")
\ ." undercurl"
\ :
\ ""
\ ))
call append(line('$'), '')
" TODO_BG: Decide whether to attempt to be more discriminating: e.g., what
" if bgcolor is enabled, but none are active? Same question for fgcolor?
" Decision: I'm thinking there's no reason to do it. Typically, user won't
" de-activate all colors, but if he does, perhaps we want him to scratch
" his head a bit.
if b:txtfmt_cfg_bgcolor
call append(line('$'),
\"Each line in the table below corresponds to a single permutation of foreground")
call append(line('$'),
\"and background colors. You may use the special global arrays g:txtfmtColor{}")
call append(line('$'),
\"and g:txtfmtBgcolor{} to change these colors.")
else
call append(line('$'),
\"Each line in the table below corresponds to a single foreground color. You may")
call append(line('$'),
\"use the special global array g:txtfmtColor{} to change these colors.")
endif
call append(line('$'), '')
call append(line('$'),
\' :help txtfmt-defining-colors')
call append(line('$'), '')
call append(line('$'),
\"The ".b:txtfmt_num_formats." permutations of the format attributes ")
call append(line('$'),
\'(u=underline, b=bold, i=italic'
\.(b:txtfmt_cfg_longformats
\ ? ', s=standout, r=reverse'
\ .b:txtfmt_cfg_undercurl
\ ? ', c=undercurl'
\ : ''
\ : ''
\ ).')')
call append(line('$'), "are shown on each color line for completeness.")
call append(line('$'), tok_fb)
call append(line('$'),
\"IMPORTANT NOTE:".tok_fmt_end."Txtfmt chooses a default range for clr/fmt tokens, which works")
call append(line('$'),
\"well on most terminals; however, this range may not be suitable for all")
call append(line('$'),
\"terminals. In particular, Vim cannot highlight those characters displayed by")
call append(line('$'),
\"the terminal as special 2-character sequences (e.g., ^M, ^B, etc...). Although")
call append(line('$'),
\"coloring and formatting of text will work when these characters are used as")
call append(line('$'),
\"tokens, their use is discouraged, because txtfmt is unable to conceal them. If")
call append(line('$'),
\"any such control sequences are visible in the sample text below, you may wish")
call append(line('$'),
\"to try a different range, either by setting global txtfmt option")
call append(line('$'),
\"g:txtfmtTokrange to a different value, or by including a different definition")
call append(line('$'),
\"in a txtfmt modeline string passed to the MakeTestPage command. Either way, you")
call append(line('$'),
\"will need to invoke MakeTestPage again to see whether the changed settings are")
call append(line('$'),
\"better.")
call append(line('$'), "")
call append(line('$'),
\" :help txtfmt-choosing-token-range")
call append(line('$'), '')
call append(line('$'), tok_fb)
call append(line('$'),
\'--color/format table--')
call append(line('$'), tok_fmt_end)
" Determine line on which to start the fmt/clr table
let iLine = line('.')
" Before looping over bgc, fgc and fmt, determine the length of the list
" of format specs (i.e., the number of characters, including start fmt
" specs, from the hyphen to the end of the line).
" Assumption: Each format token will take up a single character width. (If
" conceal patch is in effect, it will be a literal space.)
" Note: We don't include either the 'no format' token at the end of the
" line or the following space (used for table framing) in the count, as
" these characters are beyond the edge of the table proper, and we want
" them to extend beyond the end of the underscores.
let post_hyphen_width = 1 " hyphen representing 'no fmt'
let iFmt = 1 " start just after 'no format' token
while iFmt < b:txtfmt_num_formats
" Accumulate width of space and fmt spec
let post_hyphen_width = post_hyphen_width + 1 + strlen(b:ubisrc_fmt{iFmt})
let iFmt = iFmt + 1
endwhile
" Define width of lines up to the hyphen, *NOT* including potentially
" multibyte token chars that appear at the beginning of the line. A fixed
" number of columns will be reserved for such tokens.
" Note: This width is chosen with the string 'no bg color' in mind
let pre_hyphen_width = 16
" Generate a string of underscores that spans the table (but not the
" framing whitespace/tokens at left and right edges)
let underscores = s:Repeat('_', pre_hyphen_width + post_hyphen_width)
" Put the text into the buffer
" Outer loop is over background colors
" Note: piBgc in the loop below is a 1-based index into
" b:txtfmt_cfg_bgcolor{}, the array of active color indices. This array
" stores the actual color index corresponding to the piBgc'th active
" color; i.e., it stores the indices that are used for the b:txtfmt_bgc{}
" array. Both arrays are 1-based. Index 0 represents the default (no)
" color token in b:txtfmt_bgc{}.
" Note: Even if bgc is disabled, we'll iterate once for default background
let piBgc = 0
while piBgc <= (b:txtfmt_cfg_bgcolor ? b:txtfmt_cfg_numbgcolors : 0)
" Get the actual color index via one level of indirection
let iBgc = piBgc == 0 ? 0 : b:txtfmt_cfg_bgcolor{piBgc}
" Don't output the bg color title if bgcolor is disabled
if b:txtfmt_cfg_bgcolor
" Put a line consisting entirely of underscores before the bg
" color title line
" Note: If this is not the default bg color, a bg token and
" possibly a default fg token will need to be prepended.
if iBgc == 0
" Default bg color
let s = ' '
else
" Non-default bg color
let s = nr2char(b:txtfmt_bgc_first_tok + iBgc).cncl_ws
if b:txtfmt_cfg_numfgcolors
" We're currently inside a fg clr region, but bg color
" title line should be default fg color, so end the fg
" color
let s = s.nr2char(b:txtfmt_clr_first_tok).cncl_ws
else
" No fg clr region to end, but need space for alignment
let s = s.' '
endif
endif
" Now append the underscores and a 2-space end of line pad
let s = s . underscores . ' '
call append(line('$'), s)
" Insert a description of the current bg color
if iBgc == 0
let s = " no bg color"
else
let s = " Bg color ".iBgc
endif
" Append spaces such that background coloration is as wide as it is on
" subsequent lines.
" Note: The hardcoded 4 is for the beginning and end of line framing spaces
" Note: s cannot contain multibyte chars at this point, so the
" strlen() is safe.
let s = s . s:Repeat(' ', pre_hyphen_width + post_hyphen_width + 4 - strlen(s))
call append(line('$'), s)
" Put a line consisting entirely of underscores after the bg color
" title line
call append(line('$'), ' ' . underscores . ' ')
endif
" Note: See notes on piBgc and iBgc above for description of piClr and
" iClr.
let piClr = 0
while piClr <= b:txtfmt_cfg_numfgcolors
" Get the actual color index via one level of indirection
let iClr = piClr == 0 ? 0 : b:txtfmt_cfg_fgcolor{piClr}
" Build the string for this line, taking care to ensure there is a
" margin of 2 space widths
" Note: Need to keep beginning of line spaces/tokens separate
" until after the strlen(), since strlen counts characters rather
" than bytes.
if iClr == 0
let ldr = ' '
let s = "no fg color"
else
" Insert the non-default fg clr token, preceded by a space in
" the column dedicated to bg clr tokens
let ldr = ' '.nr2char(b:txtfmt_clr_first_tok + iClr).cncl_ws
let s = 'Fg color '.iClr
endif
" Pad with spaces till hyphen
let s = ldr . s . s:Repeat(' ', pre_hyphen_width - strlen(s))
" Loop over format attributes
let iFmt = 0
while iFmt < b:txtfmt_num_formats
if iFmt == 0
let s = s.'-'
else
" Conceal patch entails special handling to prevent the
" space between the specifiers from being underlined or
" undercurled.
" Case 1: 'conceal'
" <SPC> <fmt-tok> <fmt-spec> <no-fmt-tok>
" Case 2: 'noconceal'
" <fmt-tok> <fmt-spec>
" Note: For the 'noconceal' case *only*, a single
" <no-fmt-tok> goes outside loop.
let s = s . cncl_ws
\. nr2char(b:txtfmt_fmt_first_tok + iFmt)
\. b:ubisrc_fmt{iFmt}
\. (b:txtfmt_cfg_conceal ? nr2char(b:txtfmt_fmt_first_tok) : '')
endif
let iFmt = iFmt + 1
endwhile
" If necessary, add default fmt token to prevent formatting from
" spilling onto next line, and add space(s) for margin
" Case 1: 'conceal'
" <SPC> <SPC>
" Case 2: 'noconceal'
" <no-fmt-tok> <SPC>
let s = s . (b:txtfmt_cfg_conceal ? ' ' : nr2char(b:txtfmt_fmt_first_tok)) . ' '
call append(line('$'), s)
let piClr = piClr + 1
endwhile
let piBgc = piBgc + 1
endwhile
" Return to default background and foreground colors (as applicable)
" TODO: If 'conceal', then this has been done already.
call append(line('$'),
\(b:txtfmt_cfg_bgcolor && b:txtfmt_cfg_numbgcolors > 0 ? nr2char(b:txtfmt_bgc_first_tok) : '')
\.(b:txtfmt_cfg_numfgcolors > 0 ? nr2char(b:txtfmt_clr_first_tok) : ''))
call append(line('$'), tok_fb)
call append(line('$'),
\"=============================================================================")
call append(line('$'),
\"*** Escaping txtfmt tokens ***")
$center
call append(line('$'), tok_fui)
call append(line('$'),
\'Configuration:'.tok_fb.cncl_ws."escape".tok_fmt_end.cncl_ws."= ".b:txtfmt_cfg_escape)
call append(line('$'), "")
call append(line('$'),
\" :help txtfmt-escape")
" Now display text specific to the option setting
if b:txtfmt_cfg_escape != 'none'
call append(line('$'), tok_fb)
call append(line('$'),
\'--Escaping tokens outside a region--'.tok_fmt_end)
call append(line('$'),
\"The following shows that all tokens (other than the \"no fmt\" / \"no clr\" tokens)")
call append(line('$'),
\"may be escaped to prevent them from beginning a region:")
" Escaped fg color tokens
call append(line('$'), '')
call append(line('$'), tok_fb.cncl_ws
\.'*'.(b:txtfmt_cfg_bgcolor ? 'fg ' : '').'color tokens*'.tok_fmt_end)
" Loop over all clr tokens, inserting an escaped version of each.
let s = ''
let piClr = 1
while piClr <= b:txtfmt_cfg_numfgcolors
let iClr = b:txtfmt_cfg_fgcolor{piClr}
let tok = nr2char(b:txtfmt_clr_first_tok + iClr)
let s = s.cncl_ws.(b:txtfmt_cfg_escape == 'self' ? tok : '\').tok
let piClr = piClr + 1
endwhile
if s == ''
" Indicate that no fg colors are active
let s = ' --N/A--'
endif
call append(line('$'), s)
" Escaped bg color tokens
if b:txtfmt_cfg_bgcolor
call append(line('$'), tok_fb.cncl_ws
\.'*bg color tokens*'.tok_fmt_end)
" Loop over all bgc tokens, inserting an escaped version of each.
let s = ''
let piBgc = 1
while piBgc <= b:txtfmt_cfg_numbgcolors
let iBgc = b:txtfmt_cfg_bgcolor{piBgc}
let tok = nr2char(b:txtfmt_bgc_first_tok + iBgc)
let s = s.cncl_ws.(b:txtfmt_cfg_escape == 'self' ? tok : '\').tok
let piBgc = piBgc + 1
endwhile
if s == ''
" Indicate that no bg colors are active
let s = ' --N/A--'
endif
call append(line('$'), s)
endif
" Escaped format tokens
call append(line('$'), tok_fb.cncl_ws
\.'*format tokens*'.tok_fmt_end)
" Loop over all fmt tokens, inserting an escaped version of each.
let s = ''
let iFmt = 1
while iFmt < b:txtfmt_num_formats
let tok = nr2char(b:txtfmt_fmt_first_tok + iFmt)
let s = s.cncl_ws.(b:txtfmt_cfg_escape == 'self' ? tok : '\').tok
let iFmt = iFmt + 1
endwhile
call append(line('$'), s)
call append(line('$'), tok_fb)
call append(line('$'),
\'--Escaping tokens inside a region--'.tok_fui)
call append(line('$'), '')
call append(line('$'),
\"Here's a little swatch of \"underline, italic\" text. On the line below are some")
call append(line('$'),
\"escaped tokens, which, in their unescaped form, would alter the region's")
call append(line('$'),
\"formatting:")
call append(line('$'),
\(b:txtfmt_cfg_escape == 'self' ? tok_fb : '\').tok_fb
\.' (escaped bold token), '
\.(b:txtfmt_cfg_escape == 'self' ? tok_fmt_end : '\').tok_fmt_end
\.' (escaped "no fmt" token)')
call append(line('$'),
\"As you can see, the escaping characters are concealed, and the formatting is")
call append(line('$'),
\"unaffected by the escaped tokens.")
call append(line('$'),
\"Note: After you have viewed the rest of this page, you may wish to experiment")
call append(line('$'),
\"by removing the escape tokens to see how the formatting is affected.")
else
" Inform user that escaping is not configured
call append(line('$'), '')
call append(line('$'),
\"Escaping of txtfmt tokens is currently disabled.")
endif
call append(line('$'), tok_fb)
call append(line('$'),
\"=============================================================================")
call append(line('$'),
\"*** Nesting txtfmt regions ***")
$center
call append(line('$'), tok_fui)
call append(line('$'),
\'Configuration:'.tok_fb.cncl_ws.(b:txtfmt_cfg_nested ? "nested" : "nonested").tok_fmt_end)
call append(line('$'), "")
call append(line('$'),
\" :help txtfmt-nesting")
" Now display text specific to the option setting
if b:txtfmt_cfg_nested
call append(line('$'), '')
call append(line('$'),
\"/* Here's a sample comment (italicized), intended to illustrate the nesting of")
call append(line('$'),
\" * txtfmt regions within non-txtfmt regions.")
call append(line('$'),
\" *")
call append(line('$'),
\" * The following txtfmt token -->".tok_fu."<-- begins a nested \"underline\" region, which")
call append(line('$'),
\" * ends with the following \"no fmt\" token. -->".tok_fmt_end."<--")
call append(line('$'),
\" * As you can see, the comment resumes automatically after the nested region")
call append(line('$'),
\" * ends. */")
call append(line('$'), "")
call append(line('$'),
\"Non-txtfmt regions may be divided into two categories: those with the")
call append(line('$'),
\"'keepend' attribute, and those without it. To demonstrate the effect of the")
call append(line('$'),
\"'keepend' attribute on nested txtfmt regions, I have defined two additional")
call append(line('$'),
\"regions, enclosed by curly braces and square brackets respectively. The curly")
call append(line('$'),
\"brace region does not have the 'keepend' attribute; the square bracket region")
call append(line('$'),
\"does. Both regions are highlighted in bold.")
call append(line('$'),
\"{ Once again, here's a".tok_fu.cncl_ws."nested \"underline\" txtfmt region, followed by a curly")
call append(line('$'),
\"brace. }")
call append(line('$'),
\"As you can see, the nested txtfmt region was *not* terminated by the")
call append(line('$'),
\"closing curly brace. In fact, the curly brace region was extended by the")
call append(line('$'),
\"txtfmt region. Notice how the following txtfmt \"no fmt\" token -->".tok_fmt_end."<--")
call append(line('$'),
\"permits the resumption of the curly brace region}, which is finally ended by")
call append(line('$'),
\"the unobscured closing curly brace.")
call append(line('$'),
\"[ Notice, by contrast, how both the".tok_fu.cncl_ws."nested \"underline\" txtfmt region and the")
call append(line('$'),
\"square bracket region itself are terminated by the following square bracket ]")
call append(line('$'),
\"because the square bracket region was defined with the 'keepend' attribute.")
" Define comment, curly brace, and square brace regions...
syn region Tf_example_comment start=+/\*+ end=+\*/+ keepend
hi Tf_example_comment cterm=italic gui=italic
syn region Tf_example_curly start=+{+ end=+}+
hi Tf_example_curly cterm=bold gui=bold
syn region Tf_example_square start=+\[+ end=+\]+ keepend
hi Tf_example_square cterm=bold gui=bold
else
" Inform user that nesting is not configured
call append(line('$'), "")
call append(line('$'),
\"Nesting of txtfmt regions is currently disabled.")
endif
endfu
endif " if !exists('*s:MakeTestPage')
" >>>
" Public-interface commands <<<
" TODO - Add this command to undo list - Should it redefine (com!)?
com! -nargs=? MakeTestPage call s:MakeTestPage(<f-args>)
" >>>
" vim: sw=4 ts=4 foldmethod=marker foldmarker=<<<,>>> :