" 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 " 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("") if l:filetype =~ '\%(^\|\.\)txtfmt\%(\.\|$\)' let b:txtfmt_filetype = l:filetype endif endfu fu! s:Txtfmt_save_syntax() let l:syntax = expand("") 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}=|[sSlL] " {escape|esc}={self|bslash|none} " {sync}={||fromstart|none} " {bgcolor|bg}= " {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 :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: " ,, " 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: " ,[,,...,] " := " []: " := " :::...: " *** Parse table *** " st next_st can_end? tok " 0 1 n " 1 2 n , " 2 3 n " 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: " -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] " -specifies whether clrstr will be the rhs of a " "ctermfg=" ('c'), or "guifg=" ('g') " -pattern used to match against &term option. It is a regex pattern " which will be applied as ^$ " -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: " ,, 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}."\" 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=[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={||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: , '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: " .\{-}txtfmt:: " 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 = "\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 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: numcolors will be 0 and corresponding array will be empty if " there are no 1's in the 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__first_tok: 1st (default) token " txtfmt__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 = '\%(\%(^\|[^\\]\)\%(\\\\\)*\\\)\@>> " 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 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("\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, "\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('$'), \"\start token: ".b:txtfmt_cfg_starttok_display) call append(line('$'), \"\background colors: ".(b:txtfmt_cfg_bgcolor \? "enabled (".b:txtfmt_cfg_numbgcolors." active)" \: "disabled")) call append(line('$'), \"\".(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' " " Case 2: 'noconceal' " " Note: For the 'noconceal' case *only*, a single " 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' " " Case 2: 'noconceal' " 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() " >>> " vim: sw=4 ts=4 foldmethod=marker foldmarker=<<<,>>> :