" @Author:      Tom Link (mailto:micathom AT gmail com?subject=[vim])
" @Website:     http://www.vim.org/account/profile.php?user_id=4037
" @License:     GPL (see http://www.gnu.org/licenses/gpl.txt)
" @Created:     2007-09-17.
" @Last Change: 2015-08-11.
" @Revision:    1760

" call tlog#Log('Load: '. expand('<sfile>')) " vimtlib-sfile

if !exists("g:tcomment#blank_lines")
    " If 1, comment blank lines too.
    " If 2, also comment blank lines within indented code blocks 
    " (requires mixedindent -- see |tcomment#Comment()|).
    let g:tcomment#blank_lines = 2    "{{{2
endif

if !exists('g:tcomment#rstrip_on_uncomment')
    " If 1, remove right-hand whitespace on uncomment from empty lines.
    " If 2, remove right-hand whitespace on uncomment from all lines.
    let g:tcomment#rstrip_on_uncomment = 1   "{{{2
endif

if !exists("g:tcommentModeExtra")
    " Modifies how commenting works.
    "   >  ... Move the cursor to the end of the comment
    "   >> ... Like above but move the cursor to the next line
    "   #  ... Move the cursor to the position of the commented text 
    "          (NOTE: this only works when creating empty comments using 
    "          |:TCommentInline| from normal or insert mode and should 
    "          not be set here as a global option.)
    let g:tcommentModeExtra = ''   "{{{2
endif

if !exists("g:tcommentOpModeExtra")
    " Modifies how the operator works.
    " See |g:tcommentModeExtra| for a list of possible values.
    let g:tcommentOpModeExtra = ''   "{{{2
endif

if !exists('g:tcommentOptions')
    " Other key-value options used by |tcomment#Comment()|.
    "
    " Example: If you want to put the opening comment marker always in 
    " the first column regardless of the block's indentation, put this 
    " into your |vimrc| file: >
    "   let g:tcommentOptions = {'col': 1}
    let g:tcommentOptions = {}   "{{{2
endif

if !exists('g:tcomment#options_comments')
    " Additional args for |tcomment#Comment()| when using the 'comments' 
    " option.
    let g:tcomment#options_comments = {'whitespace': get(g:tcommentOptions, 'whitespace', 'both')}   "{{{2
endif

if !exists('g:tcomment#options_commentstring')
    " Additional args for |tcomment#Comment()| when using the 
    " 'commentstring' option.
    let g:tcomment#options_commentstring = {'whitespace': get(g:tcommentOptions, 'whitespace', 'both')}   "{{{2
endif

if !exists('g:tcomment#ignore_char_type')
    " |text-objects| for use with |tcomment#Operator| can have different 
    " types: line, block, char etc. Text objects like aB, it, at etc. 
    " have type char but this may not work reliably. By default, 
    " tcomment handles those text objects most often as if they were of 
    " type line. Set this variable to 0 in order to change this 
    " behaviour. Be prepared that the result may not always match your 
    " intentions.
    let g:tcomment#ignore_char_type = 1   "{{{2
endif

if !exists("g:tcommentGuessFileType")
    " Guess the file type based on syntax names always or for some fileformat only
    " If non-zero, try to guess filetypes.
    " tcomment also checks g:tcommentGuessFileType_{&filetype} for 
    " filetype specific values.
    "
    " Values:
    "   0        ... don't guess
    "   1        ... guess
    "   FILETYPE ... assume this filetype
    let g:tcommentGuessFileType = 0   "{{{2
endif
if !exists("g:tcommentGuessFileType_dsl")
    " For dsl documents, assume filetype = xml.
    let g:tcommentGuessFileType_dsl = 'xml'   "{{{2
endif
if !exists("g:tcommentGuessFileType_php")
    " In php documents, the php part is usually marked as phpRegion. We 
    " thus assume that the buffers default comment style isn't php but 
    " html.
    let g:tcommentGuessFileType_php = 'html'   "{{{2
endif
if !exists("g:tcommentGuessFileType_blade")
    " See |g:tcommentGuessFileType_php|.
    let g:tcommentGuessFileType_blade = 'html'   "{{{2
endif
if !exists("g:tcommentGuessFileType_html")
    let g:tcommentGuessFileType_html = 1   "{{{2
endif
if !exists("g:tcommentGuessFileType_tskeleton")
    let g:tcommentGuessFileType_tskeleton = 1   "{{{2
endif
if !exists("g:tcommentGuessFileType_vim")
    let g:tcommentGuessFileType_vim = 1   "{{{2
endif
if !exists("g:tcommentGuessFileType_django")
    let g:tcommentGuessFileType_django = 1   "{{{2
endif
if !exists("g:tcommentGuessFileType_eruby")
    let g:tcommentGuessFileType_eruby = 1   "{{{2
endif
if !exists("g:tcommentGuessFileType_jinja")
    let g:tcommentGuessFileType_jinja = 'html'   "{{{2
endif
if !exists("g:tcommentGuessFileType_smarty")
    let g:tcommentGuessFileType_smarty = 1   "{{{2
endif
if !exists("g:tcommentGuessFileType_rnoweb")
    let g:tcommentGuessFileType_rnoweb = 'r'   "{{{2
endif

if !exists("g:tcommentIgnoreTypes_php")
    " In php files, some syntax regions are wrongly highlighted as sql 
    " markup. We thus ignore sql syntax when guessing the filetype in 
    " php files.
    let g:tcommentIgnoreTypes_php = 'sql'   "{{{2
endif

if !exists('g:tcomment#syntax_substitute')
    " :read: let g:tcomment#syntax_substitute = {...}   "{{{2
    " Perform replacements on the syntax name.
    let g:tcomment#syntax_substitute = {
                \ '\C^javaScript\ze\(\u\|$\)': {'sub': 'javascript'},
                \ '\C^js\ze\(\u\|$\)': {'sub': 'javascript'}
                \ }
endif

if !exists('g:tcomment#filetype_map')
    " Keys must match the full |filetype|. Regexps must be |magic|. No 
    " regexp modifiers (like |\V|) are allowed.
    " let g:tcomment#filetype_map = {...}   "{{{2
    let g:tcomment#filetype_map = {
                \ 'rails-views': 'html',
                \ 'mkd': 'html',
                \ }
endif

if !exists('g:tcommentSyntaxMap')
    " tcomment guesses filetypes based on the name of the current syntax 
    " region. This works well if the syntax names match 
    " /filetypeSomeName/. Other syntax names have to be explicitly 
    " mapped onto the corresponding filetype.
    " :read: let g:tcommentSyntaxMap = {...}   "{{{2
    let g:tcommentSyntaxMap = {
            \ 'erubyExpression':   'ruby',
            \ 'vimMzSchemeRegion': 'scheme',
            \ 'vimPerlRegion':     'perl',
            \ 'vimPythonRegion':   'python',
            \ 'vimRubyRegion':     'ruby',
            \ 'vimTclRegion':      'tcl',
            \ 'Delimiter': {
            \     'filetype': {
            \         'php': 'php',
            \     },
            \ },
            \ 'phpRegionDelimiter': {
            \     'prevnonblank': [
            \         {'match': '<?php', 'filetype': 'php'},
            \         {'match': '?>', 'filetype': 'html'},
            \     ],
            \     'nextnonblank': [
            \         {'match': '?>', 'filetype': 'php'},
            \         {'match': '<?php', 'filetype': 'html'},
            \     ],
            \ },
            \ }
endif

if !exists('g:tcomment#replacements_c')
    " Replacements for c filetype.
    " :read: let g:tcomment#replacements_c = {...}   "{{{2
    let g:tcomment#replacements_c = {
                \     '/*': '#<{(|',
                \     '*/': '|)}>#',
                \ }
endif

if !exists("g:tcommentLineC_fmt")
    " Generic c-like block comments.
    let g:tcommentLineC_fmt = {
                \ 'commentstring_rx': '\%%(// %s\|/* %s */\)',
                \ 'commentstring': '/* %s */',
                \ 'rxbeg': '\*\+',
                \ 'rxend': '',
                \ 'rxmid': '',
                \ 'replacements': g:tcomment#replacements_c
                \ }
endif


function! tcomment#GetLineC(...)
    let cmt = deepcopy(g:tcommentLineC_fmt)
    if a:0 >= 1
        let cmt.commentstring = a:1
    endif
    return cmt
endf


if !exists("g:tcommentInlineC")
    " Generic c-like comments.
    " :read: let g:tcommentInlineC = {...}   "{{{2
    let g:tcommentInlineC = tcomment#GetLineC()
endif


if !exists("g:tcommentBlockC")
    let g:tcommentBlockC = {
                \ 'commentstring': '/*%s */',
                \ 'middle': ' * ',
                \ 'rxbeg': '\*\+',
                \ 'rxend': '',
                \ 'rxmid': '',
                \ 'replacements': g:tcomment#replacements_c
                \ }
endif
if !exists("g:tcommentBlockC2")
    " Generic c-like block comments (alternative markup).
    " :read: let g:tcommentBlockC2 = {...}   "{{{2
    let g:tcommentBlockC2 = {
                \ 'commentstring': '/**%s */',
                \ 'middle': ' * ',
                \ 'rxbeg': '\*\+',
                \ 'rxend': '\*\+',
                \ 'rxmid': '\*\+',
                \ 'replacements': g:tcomment#replacements_c
                \ }
endif

if !exists('g:tcomment#replacements_xml')
    " Replacements for xml filetype.
    " :read: let g:tcomment#replacements_xml = {...}   "{{{2
    let g:tcomment#replacements_xml = {
                \     '-': '&#45;',
                \     '&': '&#38;',
                \ }
endif

if !exists("g:tcommentBlockXML")
    " Generic xml-like block comments.
    " :read: let g:tcommentBlockXML = {...}   "{{{2
    let g:tcommentBlockXML = {
                \ 'commentstring': "<!--%s-->\n  ",
                \ 'replacements': g:tcomment#replacements_xml
                \ }
endif
if !exists("g:tcommentInlineXML")
    " Generic xml-like comments.
    " :read: let g:tcommentInlineXML = {...}   "{{{2
    let g:tcommentInlineXML = {
                \ 'commentstring': "<!-- %s -->",
                \ 'replacements': g:tcomment#replacements_xml
                \ }
endif

if !exists('g:tcomment#ignore_comment_def')
    " A list of names or filetypes, which should be ignored by 
    " |tcomment#DefineType()| -- no custom comment definition will be 
    " stored for these names.
    "
    " This variable should be set before loading autoload/tcomment.vim.
    let g:tcomment#ignore_comment_def = []   "{{{2
endif

if !exists('g:tcomment#must_escape_expression_backslash')
    " Users of vim earlier than 7.3 might have to set this variable to 
    " true. Set this variable to 0, if you see unexpected "\r" char 
    " sequences in comments.
    "
    " The reommended value was `!(v:version > 702 || (v:version == 702 && has('patch407')))`.
    " It is now assumed though, that no unpatched versions of vim are in 
    " use.
    "
    " References:
    " Patch 7.2.407  when using :s with an expression backslashes are dropped
    " https://github.com/tomtom/tcomment_vim/issues/102
    let g:tcomment#must_escape_expression_backslash = 0   "{{{2
endif


let s:types_dirty = 1

let s:definitions = {}

" If you don't explicitly define a comment style, |:TComment| will use 
" 'commentstring' instead. We override the default values here in order 
" to have a blank after the comment marker. Block comments work only if 
" we explicitly define the markup.
"
" NAME usually is a 'filetype'. You can use special suffixes to define 
" special comment types. E.g. the name "FILETYPE_block" is used for 
" block comments for 'filetype'. The name "FILETYPE_inline" is used for 
" inline comments. If no specialized comment definition exists, the 
" normal one with name "FILETYPE" is used.
"
" The comment definition can be either a string or a dictionary.
"
" If it is a string:
" The format for block comments is similar to 'commentstrings' with the 
" exception that the format strings for blocks can contain a second line 
" that defines how "middle lines" (see :h format-comments) should be 
" displayed.
"
" Example: If the string is "--%s--\n-- ", lines will be commented as 
" "--%s--" but the middle lines in block comments will be commented as 
" "--%s".
"
" If it is a dictionary:
" See the help on the args argument of |tcomment#Comment| (see item 1, 
" args is a list of key=value pairs) to find out which fields can be 
" used.
" :display: tcomment#DefineType(name, commentdef, ?cdef={}, ?anyway=0)
function! tcomment#DefineType(name, commentdef, ...)
    let basename = matchstr(a:name, '^[^_]\+')
    let use = a:0 >= 2 ? a:2 : len(filter(copy(g:tcomment#ignore_comment_def), 'v:val == basename')) == 0
    " TLogVAR a:name, use
    if use
        if type(a:commentdef) == 4
            let cdef = copy(a:commentdef)
        else
            let cdef = a:0 >= 1 ? a:1 : {}
            let cdef.commentstring = a:commentdef
        endif
        let s:definitions[a:name] = cdef
    endif
    let s:types_dirty = 1
endf

" Return the comment definition for NAME.
"                                                       *b:tcomment_def_{NAME}*
" Return b:tcomment_def_{NAME} if the variable exists. Otherwise return 
" the comment definition as set with |tcomment#DefineType|.
function! tcomment#GetCommentDef(name, ...)
    if exists('b:tcomment_def_'. a:name)
        return b:tcomment_def_{a:name}
    else
        return get(s:definitions, a:name, a:0 >= 1 ? a:1 : '')
    endif
endf

" :nodoc:
" Return 1 if a comment type is defined.
function! tcomment#TypeExists(name, ...)
    let comment_mode = a:0 >= 1 ? a:1 : ''
    let name = a:name
    if comment_mode =~? 'b'
        let name .= '_block'
    elseif comment_mode =~? 'i'
        let name .= '_inline'
    endif
    return has_key(s:definitions, name) ? name : ''
endf

call tcomment#DefineType('aap',              '# %s'             )
call tcomment#DefineType('ada',              '-- %s'            )
call tcomment#DefineType('autohotkey',       '; %s'             )
call tcomment#DefineType('apache',           '# %s'             )
call tcomment#DefineType('applescript',      '(* %s *)'         )
call tcomment#DefineType('applescript_block',"(*%s*)\n"         )
call tcomment#DefineType('applescript_inline','(* %s *)'        )
call tcomment#DefineType('asciidoc',         '// %s'            )
call tcomment#DefineType('asm',              '; %s'             )
call tcomment#DefineType('asterisk',         '; %s'             )
call tcomment#DefineType('blade',            '{{-- %s --}}'     )
call tcomment#DefineType('blade_block',      '{{-- %s --}}'     )
call tcomment#DefineType('blade_inline',     '{{-- %s --}}'     )
call tcomment#DefineType('c',                tcomment#GetLineC())
call tcomment#DefineType('c_block',          g:tcommentBlockC   )
call tcomment#DefineType('c_inline',         g:tcommentInlineC  )
call tcomment#DefineType('catalog',          '-- %s --'         )
call tcomment#DefineType('catalog_block',    "--%s--\n  "       )
call tcomment#DefineType('cfg',              '# %s'             )
call tcomment#DefineType('chromemanifest',   '# %s'             )
call tcomment#DefineType('clojure',          {'commentstring': '; %s', 'count': 2})
call tcomment#DefineType('clojure_inline',   '; %s'             )
call tcomment#DefineType('clojurescript',    ';; %s'            )
call tcomment#DefineType('clojurescript_inline', '; %s'         )
call tcomment#DefineType('cmake',            '# %s'             )
call tcomment#DefineType('coffee',           '# %s'             )
call tcomment#DefineType('conf',             '# %s'             )
call tcomment#DefineType('context',          '%% %s'            )
call tcomment#DefineType('conkyrc',          '# %s'             )
call tcomment#DefineType('cpp',              tcomment#GetLineC('// %s'))
call tcomment#DefineType('cpp_block',        g:tcommentBlockC   )
call tcomment#DefineType('cpp_inline',       g:tcommentInlineC  )
call tcomment#DefineType('cram',             {'col': 1, 'commentstring': '# %s' })
call tcomment#DefineType('crontab',          '# %s'             )
call tcomment#DefineType('cs',               '// %s'            )
call tcomment#DefineType('cs_block',         g:tcommentBlockC   )
call tcomment#DefineType('cs_inline',        g:tcommentInlineC  )
call tcomment#DefineType('css',              '/* %s */'         )
call tcomment#DefineType('css_block',        g:tcommentBlockC   )
call tcomment#DefineType('css_inline',       g:tcommentInlineC  )
call tcomment#DefineType('debcontrol',       '# %s'             )
call tcomment#DefineType('debsources',       '# %s'             )
call tcomment#DefineType('desktop',          '# %s'             )
call tcomment#DefineType('dnsmasq',          '# %s'             )
call tcomment#DefineType('docbk',            g:tcommentInlineXML)
call tcomment#DefineType('docbk_block',      g:tcommentBlockXML )
call tcomment#DefineType('docbk_inline',     g:tcommentInlineXML)
call tcomment#DefineType('dosbatch',         'rem %s'           )
call tcomment#DefineType('dosini',           '; %s'             )
call tcomment#DefineType('dsl',              '; %s'             )
call tcomment#DefineType('dustjs',           '{! %s !}'         )
call tcomment#DefineType('dylan',            '// %s'            )
call tcomment#DefineType('eiffel',           '-- %s'            )
call tcomment#DefineType('elixir',           '# %s'             )
call tcomment#DefineType('erlang',           '%%%% %s'          )
call tcomment#DefineType('eruby',            '<%%# %s'          )
call tcomment#DefineType('esmtprc',          '# %s'             )
call tcomment#DefineType('expect',           '# %s'             )
call tcomment#DefineType('fish',             '# %s'             )
call tcomment#DefineType('form',             {'commentstring': '* %s', 'col': 1})
call tcomment#DefineType('fstab',            '# %s'             )
call tcomment#DefineType('gitconfig',        '# %s'             )
call tcomment#DefineType('gitcommit',        '# %s'             )
call tcomment#DefineType('gitignore',        '# %s'             )
call tcomment#DefineType('gnuplot',          '# %s'             )
call tcomment#DefineType('go',               '// %s'            )
call tcomment#DefineType('go_block',         g:tcommentBlockC   )
call tcomment#DefineType('go_inline',        g:tcommentInlineC  )
call tcomment#DefineType('groovy',           tcomment#GetLineC('// %s'))
call tcomment#DefineType('groovy_block',     g:tcommentBlockC   )
call tcomment#DefineType('groovy_doc_block', g:tcommentBlockC2  )
call tcomment#DefineType('groovy_inline',    g:tcommentInlineC  )
call tcomment#DefineType('gtkrc',            '# %s'             )
call tcomment#DefineType('haml',             '-# %s'            )
call tcomment#DefineType('haskell',          '-- %s'            )
call tcomment#DefineType('haskell_block',    "{-%s-}\n   "      )
call tcomment#DefineType('haskell_inline',   '{- %s -}'         )
call tcomment#DefineType('html',             g:tcommentInlineXML)
call tcomment#DefineType('html_block',       g:tcommentBlockXML )
call tcomment#DefineType('html_inline',      g:tcommentInlineXML)
call tcomment#DefineType('htmldjango',       '{# %s #}'     )
call tcomment#DefineType('htmldjango_block', "{%% comment %%}%s{%% endcomment %%}\n ")
call tcomment#DefineType('htmljinja',       '{# %s #}'     )
call tcomment#DefineType('htmljinja_block', "{%% comment %%}%s{%% endcomment %%}\n ")
call tcomment#DefineType('hy',               '; %s'             )
call tcomment#DefineType('ini',              '; %s'             ) " php ini (/etc/php5/...)
call tcomment#DefineType('io',               '// %s'            )
call tcomment#DefineType('jade',             '// %s'            )
call tcomment#DefineType('jasmine',          '# %s'             )
call tcomment#DefineType('java',             tcomment#GetLineC('// %s'))
call tcomment#DefineType('java_block',       g:tcommentBlockC   )
call tcomment#DefineType('java_doc_block',   g:tcommentBlockC2  )
call tcomment#DefineType('java_inline',      g:tcommentInlineC  )
call tcomment#DefineType('javascript',       tcomment#GetLineC('// %s'))
call tcomment#DefineType('javascript_block', g:tcommentBlockC   )
call tcomment#DefineType('javascript_inline', g:tcommentInlineC )
call tcomment#DefineType('jsx',             '{/* %s */}')
call tcomment#DefineType('jsx_block',       '{/* %s */}')
call tcomment#DefineType('jsx_inline',      '{/* %s */}')
call tcomment#DefineType('jinja',           '{# %s #}'     )
call tcomment#DefineType('jinja_block',     "{%% comment %%}%s{%% endcomment %%}\n ")
call tcomment#DefineType('jproperties',      '# %s'             )
call tcomment#DefineType('lilypond',         '%% %s'            )
call tcomment#DefineType('lisp',             '; %s'             )
call tcomment#DefineType('liquid',           g:tcommentInlineXML)
call tcomment#DefineType('liquid_block',     g:tcommentBlockXML )
call tcomment#DefineType('liquid_inline',    g:tcommentInlineXML)
call tcomment#DefineType('lua',              '-- %s'            )
call tcomment#DefineType('lua_block',        "--[[%s--]]\n"     )
call tcomment#DefineType('lua_inline',       '--[[%s --]]'      )
call tcomment#DefineType('lynx',             '# %s'             )
call tcomment#DefineType('m4',               'dnl %s'           )
call tcomment#DefineType('mail',             '> %s'             )
call tcomment#DefineType('make',             '# %s'             )
call tcomment#DefineType('matlab',           '%% %s'            )
call tcomment#DefineType('monkey',           ''' %s'            )
call tcomment#DefineType('msidl',            '// %s'            )
call tcomment#DefineType('msidl_block',      g:tcommentBlockC   )
call tcomment#DefineType('nginx',            '# %s'             )
call tcomment#DefineType('nroff',            '.\\" %s'          )
call tcomment#DefineType('noweb',            '%% %s'            )
call tcomment#DefineType('nsis',             '# %s'             )
call tcomment#DefineType('objc',             '/* %s */'         )
call tcomment#DefineType('objc_block',       g:tcommentBlockC   )
call tcomment#DefineType('objc_inline',      g:tcommentInlineC  )
call tcomment#DefineType('objcpp',           '// %s'            )
call tcomment#DefineType('ocaml',            '(* %s *)'         )
call tcomment#DefineType('ocaml_block',      "(*%s*)\n   "      )
call tcomment#DefineType('ocaml_inline',     '(* %s *)'         )
call tcomment#DefineType('pac',              '// %s'            )
call tcomment#DefineType('pascal',           '(* %s *)'         )
call tcomment#DefineType('pascal_block',     "(*%s*)\n   "      )
call tcomment#DefineType('pascal_inline',    '(* %s *)'         )
call tcomment#DefineType('perl',             '# %s'             )
call tcomment#DefineType('perl_block',       "=cut%s=cut"       )
call tcomment#DefineType('pfmain',           '# %s'             )
call tcomment#DefineType('php',              {'commentstring_rx': '\%%(//\|#\) %s', 'commentstring': '// %s'})
call tcomment#DefineType('php_2_block',      g:tcommentBlockC2  )
call tcomment#DefineType('php_block',        g:tcommentBlockC   )
call tcomment#DefineType('php_inline',       g:tcommentInlineC  )
call tcomment#DefineType('po',               '# %s'             )
call tcomment#DefineType('prolog',           '%% %s'            )
call tcomment#DefineType('puppet',           '# %s'             )
call tcomment#DefineType('purescript',       '-- %s'            )
call tcomment#DefineType('purescript_block', "{-%s-}\n   "      )
call tcomment#DefineType('purescript_inline','{- %s -}'         )
call tcomment#DefineType('python',           '# %s'             )
call tcomment#DefineType('qml',              '// %s'            )
call tcomment#DefineType('r',                '# %s'             )
call tcomment#DefineType('racket',           '; %s'             )
call tcomment#DefineType('racket_block',     '#|%s|#'           )
call tcomment#DefineType('rc',               '// %s'            )
call tcomment#DefineType('readline',         '# %s'             )
call tcomment#DefineType('remind',           {'commentstring_rx': '\[;#] %s', 'commentstring': '# %s'})
call tcomment#DefineType('resolv',           '# %s'             )
call tcomment#DefineType('robot', {'col': 1, 'commentstring': '# %s'})
call tcomment#DefineType('robots',           '# %s'             )
call tcomment#DefineType('rust',             tcomment#GetLineC('// %s'))
call tcomment#DefineType('rust_block',       g:tcommentBlockC   )
call tcomment#DefineType('ruby',             '# %s'             )
call tcomment#DefineType('ruby_3',           '### %s'           )
call tcomment#DefineType('ruby_block',       "=begin rdoc%s=end")
call tcomment#DefineType('ruby_nodoc_block', "=begin%s=end"     )
call tcomment#DefineType('samba',            '# %s'             )
call tcomment#DefineType('sbs',              "' %s"             )
call tcomment#DefineType('scala',            '// %s'            )
call tcomment#DefineType('scala_block',      g:tcommentBlockC   )
call tcomment#DefineType('scala_inline',     g:tcommentInlineC  )
call tcomment#DefineType('scheme',           '; %s'             )
call tcomment#DefineType('scheme_block',     '#|%s|#'           )
call tcomment#DefineType('scss',             '// %s'            )
call tcomment#DefineType('scss_block',       g:tcommentBlockC   )
call tcomment#DefineType('scss_inline',      g:tcommentInlineC  )
call tcomment#DefineType('sed',              '# %s'             )
call tcomment#DefineType('sgml',             g:tcommentInlineXML)
call tcomment#DefineType('sgml_block',       g:tcommentBlockXML )
call tcomment#DefineType('sgml_inline',      g:tcommentInlineXML)
call tcomment#DefineType('sh',               '# %s'             )
call tcomment#DefineType('slim',             '/%s'              )
call tcomment#DefineType('sls',              '# %s'             )
call tcomment#DefineType('smarty',           '{* %s *}'         )
call tcomment#DefineType('spec',             '# %s'             )
call tcomment#DefineType('sps',              '* %s.'            )
call tcomment#DefineType('sps_block',        "* %s."            )
call tcomment#DefineType('spss',             '* %s.'            )
call tcomment#DefineType('spss_block',       "* %s."            )
call tcomment#DefineType('sql',              '-- %s'            )
call tcomment#DefineType('squid',            '# %s'             )
call tcomment#DefineType('sshconfig',        '# %s'             )
call tcomment#DefineType('sshdconfig',       '# %s'             )
call tcomment#DefineType('st',               '" %s "'           )
call tcomment#DefineType('tcl',              '# %s'             )
call tcomment#DefineType('tex',              '%% %s'            )
call tcomment#DefineType('toml',             '# %s'             )
call tcomment#DefineType('tpl',              '<!-- %s -->'      )
call tcomment#DefineType('tup',              '# %s'             )
call tcomment#DefineType('typoscript',       '# %s'             )
call tcomment#DefineType('upstart',          '# %s'             )
call tcomment#DefineType('vader',            {'col': 1, 'commentstring': '" %s' })
call tcomment#DefineType('vhdl',             '-- %s'            )
call tcomment#DefineType('verilog',          '// %s'            )
call tcomment#DefineType('verilog_inline',   g:tcommentInlineC  )
call tcomment#DefineType('verilog_block',    g:tcommentBlockC   )
call tcomment#DefineType('verilog_systemverilog',       '// %s' )
call tcomment#DefineType('verilog_systemverilog_block', g:tcommentBlockC)
call tcomment#DefineType('verilog_systemverilog_inline', g:tcommentInlineC)
call tcomment#DefineType('viki',             '%% %s'            )
call tcomment#DefineType('viki_3',           '%%%%%% %s'        )
call tcomment#DefineType('viki_inline',      '{cmt: %s}'        )
call tcomment#DefineType('vim',              '" %s'             )
call tcomment#DefineType('vim_3',            '""" %s'           )
call tcomment#DefineType('vimwiki',          '%%%% %s'          )
call tcomment#DefineType('websec',           '# %s'             )
call tcomment#DefineType('x86conf',          '# %s'             )
call tcomment#DefineType('xml',              g:tcommentInlineXML)
call tcomment#DefineType('xml_block',        g:tcommentBlockXML )
call tcomment#DefineType('xml_inline',       g:tcommentInlineXML)
call tcomment#DefineType('xs',               g:tcommentInlineC  )
call tcomment#DefineType('xs_block',         g:tcommentBlockC   )
call tcomment#DefineType('xslt',             g:tcommentInlineXML)
call tcomment#DefineType('xslt_block',       g:tcommentBlockXML )
call tcomment#DefineType('xslt_inline',      g:tcommentInlineXML)
call tcomment#DefineType('yaml',             '# %s'             )

" :doc:
" A dictionary of NAME => COMMENT DEFINITION (see |tcomment#DefineType|) 
" that can be set in vimrc to override tcomment's default comment 
" styles.
" :read: let g:tcomment_types = {} "{{{2
if exists('g:tcomment_types')
    for [s:name, s:def] in items(g:tcomment_types)
        call tcomment#DefineType(s:name, s:def)
    endfor
    unlet! s:name s:def
endif


function! s:DefaultValue(option)
    exec 'let '. a:option .' = &'. a:option
    exec 'set '. a:option .'&'
    exec 'let default = &'. a:option
    exec 'let &'. a:option .' = '. a:option
    return default
endf


function! s:Count(string, rx)
    return len(split(a:string, a:rx, 1)) - 1
endf


function! s:Printf1(fmt, expr)
    let n = s:Count(a:fmt, '%\@<!\%(%%\)*%s')
    let exprs = repeat([a:expr], n)
    " TLogVAR a:fmt, a:expr, exprs
    let rv = call(function('printf'), [a:fmt] + exprs)
    " TLogVAR rv
    return rv
endf


let s:default_comments       = s:DefaultValue('comments')
let s:default_comment_string = s:DefaultValue('commentstring')
let s:null_comment_string    = '%s'

" tcomment#Comment(line1, line2, ?comment_mode, ?comment_anyway, ?args...)
" args... are either:
"   1. a list of key=value pairs where known keys are (see also 
"      |g:tcommentOptions|):
"         as=STRING        ... Use a specific comment definition
"         count=N          ... Repeat the comment string N times
"         col=N            ... Start the comment at column N (in block 
"                              mode; must be smaller than |indent()|)
"         mode=STRING      ... See the notes below on the "comment_mode" argument
"         mode_extra=STRING ... Add to comment_mode
"         begin=STRING     ... Comment prefix
"         end=STRING       ... Comment postfix
"         middle=STRING    ... Middle line comments in block mode
"         rxbeg=N          ... Regexp to find the substring of "begin" 
"                              that should be multiplied by "count"
"         rxend=N          ... The above for "end"
"         rxmid=N          ... The above for "middle"
"         mixedindent=BOOL ... If true, allow use of mixed 
"                              characters for indentation
"         commentstring_rx ... A regexp format string that matches 
"                              commented lines (no new groups may be 
"                              introduced, the |regexp| is |\V|; % have 
"                              to be doubled); "commentstring", "begin" 
"                              and optionally "end" must be defined or 
"                              deducible.
"         whitespace       ... Define whether commented text is 
"                              surrounded with whitespace; if
"                              both ... surround with whitespace (default)
"                              left ... keep whitespace on the left
"                              right... keep whitespace on the right
"                              no   ... don't use whitespace
"         strip_whitespace ... Strip trailing whitespace: if 1 
"                              (default), strip from empty lines only, 
"                              if 2, always strip whitespace; if 0, 
"                              don't strip any whitespace
"   2. 1-2 values for: ?commentPrefix, ?commentPostfix
"   3. a dictionary (internal use only)
"
" comment_mode (see also ¦g:tcommentModeExtra¦):
"   G ... guess the value of comment_mode
"   B ... block (use extra lines for the comment markers)
"   L ... lines
"   i ... maybe inline, guess
"   I ... inline
"   R ... right (comment the line right of the cursor)
"   v ... visual
"   o ... operator
"   C ... force comment
"   K ... comment only uncommented lines
"   U ... force uncomment (if U and C are present, U wins)
" By default, each line in range will be commented by adding the comment 
" prefix and postfix.
function! tcomment#Comment(beg, end, ...)
    let comment_mode0  = s:AddModeExtra((a:0 >= 1 ? a:1 : 'G'), g:tcommentModeExtra, a:beg, a:end)
    let comment_mode   = comment_mode0
    let comment_anyway = a:0 >= 2 ? (a:2 == '!') : 0
    " TLogVAR a:beg, a:end, comment_mode, comment_anyway, a:000
    " save the cursor position
    if exists('w:tcomment_pos')
        let s:current_pos = copy(w:tcomment_pos)
    else
        let s:current_pos = getpos('.')
    endif
    " echom "DBG current_pos=" string(s:current_pos)
    let cursor_pos = getpos("'>")
    " TLogVAR cursor_pos
    let s:cursor_pos = []
    if comment_mode =~# 'i'
        let blnum = line("'<")
        if blnum == line("'>")
            if virtcol('.') <= indent(blnum)
                let i_mode = 'G'
            else
                let i_mode = 'I'
            endif
        else
            let i_mode = 'G'
        endif
        let comment_mode = substitute(comment_mode, '\Ci', i_mode, 'g')
        " TLogVAR 1, comment_mode
    endif
    let [lbeg, cbeg, lend, cend] = s:GetStartEnd(a:beg, a:end, comment_mode)
    " TLogVAR lbeg, cbeg, lend, cend, virtcol('$')
    if comment_mode ==? 'I' && comment_mode0 =~# 'i' && lbeg == lend && cend >= virtcol('$') - 1
        let comment_mode = substitute(comment_mode, '\CI', cbeg <= 1 ? 'G' : 'R', 'g')
        " TLogVAR comment_mode
    endif
    let mode_extra = s:GetTempOption('mode_extra', '')
    " TLogVAR mode_extra
    if !empty(mode_extra)
        let comment_mode = s:AddModeExtra(comment_mode, mode_extra, lbeg, lend)
        " TLogVAR "mode_extra", comment_mode
        unlet s:temp_options.mode_extra
    endif
    " get the correct commentstring
    let cdef = copy(g:tcommentOptions)
    " TLogVAR 1, cdef
    if exists('b:tcommentOptions')
        let cdef = extend(cdef, copy(b:tcommentOptions))
        " TLogVAR 2, cdef
    endif
    if a:0 >= 3 && type(a:3) == 4
        call extend(cdef, a:3)
        " TLogVAR 3, cdef
    else
        let cdef0 = s:GetCommentDefinition(lbeg, lend, comment_mode)
        " TLogVAR 4.1, cdef, cdef0
        call extend(cdef, cdef0)
        " TLogVAR 4.2, cdef
        let ax = 3
        if a:0 >= 3 && a:3 != '' && stridx(a:3, '=') == -1
            let ax = 4
            let cdef.begin = a:3
            if a:0 >= 4 && a:4 != '' && stridx(a:4, '=') == -1
                let ax = 5
                let cdef.end = a:4
            endif
        endif
        " TLogVAR ax, a:0, a:000
        if a:0 >= ax
            " let cdef = extend(cdef, s:ParseArgs(lbeg, lend, comment_mode, a:000[ax - 1 : -1]))
            let cdef = s:ExtendCDef(lbeg, lend, comment_mode, cdef, s:ParseArgs(lbeg, lend, comment_mode, a:000[ax - 1 : -1]))
            " TLogVAR 5, cdef
        endif
        if !empty(get(cdef, 'begin', '')) || !empty(get(cdef, 'end', ''))
            let cdef.commentstring = s:EncodeCommentPart(get(cdef, 'begin', ''))
                        \ . '%s'
                        \ . s:EncodeCommentPart(get(cdef, 'end', ''))
        endif
        let comment_mode = cdef.mode
        " TLogVAR 2, comment_mode
    endif
    if empty(comment_mode)
        echohl WarningMsg
        echo "TComment: Comment mode is not supported for the current filetype"
        echohl NONE
        return
    endif
    if exists('s:temp_options')
        let cdef = s:ExtendCDef(lbeg, lend, comment_mode, cdef, s:temp_options)
        " TLogVAR 6, cdef
        " echom "DBG s:temp_options" string(s:temp_options)
        unlet s:temp_options
    endif
    " TLogVAR 7, cdef
    if has_key(cdef, 'whitespace')
        call s:SetWhitespaceMode(cdef)
    endif
    if !empty(filter(['count', 'cbeg', 'cend', 'cmid'], 'has_key(cdef, v:val)'))
        call s:RepeatCommentstring(cdef)
    endif
    " echom "DBG" string(a:000)
    let cms0 = s:BlockGetCommentRx(cdef)
    " TLogVAR cms0
    "" make whitespace optional; this conflicts with comments that require some 
    "" whitespace
    let cmt_check = substitute(cms0, '\([	 ]\)', '\1\\?', 'g')
    "" turn commentstring into a search pattern
    " TLogVAR cmt_check
    let cmt_check = s:Printf1(cmt_check, '\(\_.\{-}\)')
    " TLogVAR cdef, cmt_check
    let s:cdef = cdef
    " set comment_mode
    " TLogVAR comment_mode
    let [lbeg, lend, uncomment] = s:CommentDef(lbeg, lend, cmt_check, comment_mode, cbeg, cend)
    " TLogVAR lbeg, lend, cbeg, cend, uncomment, comment_mode, comment_anyway
    if uncomment
        if comment_mode =~# 'C' || comment_anyway
            let comment_do = 'c'
        else
            let comment_do = 'u'
        endif
    else
        if comment_mode =~# 'U'
            let comment_do = 'u'
        elseif comment_mode =~# 'K'
            let comment_do = 'k'
        else
            let comment_do = 'c'
        endif
    endif
    " TLogVAR comment_anyway, comment_mode, mode_extra, comment_do
    " " echom "DBG" string(s:cdef)
    if comment_do ==# 'c'
        let cbeg = get(s:cdef, 'col', cbeg)
    endif
    " TLogVAR cbeg
    " go
    " TLogVAR comment_mode
    if comment_mode =~# 'B'
        " We want a comment block
        call s:CommentBlock(lbeg, lend, cbeg, cend, comment_mode, comment_do, cmt_check, s:cdef)
    else
        " We want commented lines
        " final search pattern for uncommenting
        let cmt_check   = '\V\^\(\s\{-}\)'. cmt_check .'\$'
        " let cmt_check   = escape(cmt_check, '"/\')
        " final pattern for commenting
        let cmt_replace = s:GetCommentReplace(s:cdef, cms0)
        " TLogVAR cmt_replace
        " TLogVAR comment_mode, lbeg, cbeg, lend, cend
        let s:processline_lnum = lbeg
        let end_rx = s:EndPosRx(comment_mode, lend, cend)
        let postfix_rx = end_rx == '\$' ? '' : '\.\*\$'
        let prefix_rx = '\^\.\{-}' . s:StartPosRx(comment_mode, lbeg, cbeg)
        let comment_rx = '\V'
                    \ . '\('. prefix_rx . '\)'
                    \ .'\('
                    \ .'\(\_.\{-}\)'
                    \ . end_rx
                    \ .'\)'
                    \ .'\(' . postfix_rx . '\)'
        " TLogVAR comment_rx, prefix_rx, end_rx, postfix_rx
        " let @x = comment_rx " DBG
        for lnum in range(lbeg, lend)
            let line0 = getline(lnum)
            " TLogVAR line0
            let lmatch = matchlist(line0, comment_rx)
            " TLogVAR lmatch
            if empty(lmatch) && g:tcomment#blank_lines >= 2
                let lline0 = s:Strdisplaywidth(line0)
                " TLogVAR lline0, cbeg
                if lline0 < cbeg
                    let line0 = line0 . repeat(' ', cbeg - lline0)
                    let lmatch = [line0, line0, '', '', '']
                    " TLogVAR 'padded', line0, lmatch
                endif
            endif
            if !empty(lmatch)
                let [part1, ok] = s:ProcessLine(comment_do, lmatch[2], cmt_check, cmt_replace)
                " TLogVAR part1, ok
                if ok
                    let line1 = lmatch[1] . part1 . lmatch[4]
                    if comment_do ==# 'u' && g:tcomment#rstrip_on_uncomment > 0
                        if g:tcomment#rstrip_on_uncomment == 2 || line1 !~ '\S'
                            let line1 = substitute(line1, '\s\+$', '', '')
                        endif
                    endif
                    " TLogVAR line1
                    call setline(lnum, line1)
                endif
            endif
        endfor
    endif
    " reposition cursor
    " TLogVAR 3, comment_mode
    " echom "DBG s:cursor_pos" string(s:cursor_pos)
    if !empty(s:cursor_pos)
        let cursor_pos = s:cursor_pos
    endif
    if comment_mode =~ '>'
        call setpos('.', cursor_pos)
        if comment_mode !~ 'i' && comment_mode =~ '>>'
            norm! l^
        endif
    elseif comment_mode =~ '#'
        call setpos('.', cursor_pos)
        if exists('w:tcomment_pos')
            let w:tcomment_pos = cursor_pos
        endif
    else
        call setpos('.', s:current_pos)
    endif
    unlet! s:cursor_pos s:current_pos s:cdef
endf


if v:version >= 703
    function! s:Strdisplaywidth(...) "{{{3
        return call('strdisplaywidth', a:000)
    endf
else
    function! s:Strdisplaywidth(string, ...) "{{{3
        " NOTE: Col argument is ignored
        return strlen(substitute(a:string, ".", "x", "g"))
    endf
endif


function! tcomment#MaybeReuseOptions(name) "{{{3
    if exists('s:options_cache') && get(s:options_cache, 'name', '') == a:name
        if exists('s:temp_options')
            let s:temp_options = extend(deepcopy(s:options_cache.options), s:temp_options)
            let s:options_cache = {'name': a:name, 'options': s:temp_options}
        else
            let s:temp_options = deepcopy(s:options_cache.options)
        endif
    elseif exists('s:temp_options')
        let s:options_cache = {'name': a:name, 'options': s:temp_options}
    endif
endf


function! s:GetTempOption(name, default) "{{{3
    if exists('s:temp_options') && has_key(s:temp_options, a:name)
        return s:temp_options[a:name]
    else
        return a:default
    endif
endf


function! tcomment#ResetOption() "{{{3
    unlet! s:temp_options s:options_cache
endf


function! tcomment#SetOption(name, arg) "{{{3
    " TLogVAR a:name, a:arg
    if !exists('s:temp_options')
        let s:temp_options = {}
    endif
    " if index(['count', 'as'], a:name) != -1
        if empty(a:arg)
            if has_key(s:temp_options, a:name)
                call remove(s:temp_options, a:name)
            endif
        else
            let s:temp_options[a:name] = a:arg
        endif
    " endif
endf


function! s:GetStartEnd(beg, end, comment_mode) "{{{3
    " TLogVAR a:beg, a:end, a:comment_mode
    if type(a:beg) == 3
        let [lbeg, cbeg] = a:beg
        let [lend, cend] = a:end
    else
        let lbeg = a:beg
        let lend = a:end
        let comment_mode = a:comment_mode
        " TLogVAR comment_mode
        if comment_mode =~# 'R'
            let cbeg = virtcol('.')
            let cend = virtcol('$')
            let comment_mode = substitute(comment_mode, '\CR', 'G', 'g')
            " TLogVAR 'R', cbeg, cend, comment_mode
        elseif comment_mode =~# 'I'
            let cbeg = virtcol("'<")
            if cbeg == 0
                let cbeg = virtcol('.')
            endif
            let cend = virtcol("'>")
            if cend < virtcol('$') && (comment_mode =~# 'o' || &selection == 'inclusive')
                let cend += 1
                " TLogVAR cend, virtcol('$')
            endif
            " TLogVAR 'I', cbeg, cend, comment_mode
        else
            let cbeg = -1
            let cend = 0
            for lnum in range(a:beg, a:end)
                if getline(lnum) =~ '\S'
                    let indentwidth = indent(lnum)
                    " TLogVAR cbeg, lnum, indentwidth, getline(lnum)
                    if cbeg == -1 || indentwidth < cbeg
                        let cbeg = indentwidth
                    endif
                endif
            endfor
            if cbeg == -1
                let cbeg = 0
            endif
        endif
    endif
    " TLogVAR lbeg, cbeg, lend, cend
    if lend < lbeg || (lend == lbeg && cend > 0 && cend < cbeg)
        return [lend, cend, lbeg, cbeg]
    else
        return [lbeg, cbeg, lend, cend]
    endif
endf


function! s:SetWhitespaceMode(cdef) "{{{3
    let mode = a:cdef.whitespace
    let cms = s:BlockGetCommentString(a:cdef)
    let mid = s:BlockGetMiddleString(a:cdef)
    let cms0 = cms
    let mid0 = mid
    " TLogVAR mode, cms, mid
    if mode =~ '^\(n\%[o]\|l\%[eft]\|r\%[ight]\)$'
        " Remove whitespace on the left
        if mode =~ '^n\%[o]$' || mode =~ '^r\%[ight]$'
            let cms = substitute(cms, '\s\+\ze%\@<!%s', '', 'g')
            let mid = substitute(mid, '\s\+\ze%\@<!%s', '', 'g')
        endif
        " Remove whitespace on the right
        if mode =~ '^n\%[o]$' || mode =~ '^l\%[eft]$'
            let cms = substitute(cms, '%\@<!%s\zs\s\+', '', 'g')
            let mid = substitute(mid, '%\@<!%s\zs\s\+', '', 'g')
        endif
        if mode =~ '^l\%[eft]$'
            if mid !~ '%s'
                let mid .= ' '
            endif
        endif
    elseif mode =~ '^\(b\%[oth]\)$'
        let cms = substitute(cms, '\S\zs\ze%\@<!%s', ' ', 'g')
        let cms = substitute(cms, '%\@<!%s\zs\ze\S', ' ', 'g')
        if mid =~ '%s'
            let mid = substitute(mid, '\S\zs\ze%\@<!%s', ' ', 'g')
            let mid = substitute(mid, '%\@<!%s\zs\ze\S', ' ', 'g')
        else
            let mid .= ' '
        endif
    endif
    if cms != cms0
        " TLogVAR cms
        let a:cdef.commentstring = cms
    endif
    if mid != mid0
        " TLogVAR mid
        let a:cdef.middle = mid
    endif
    " TLogVAR a:cdef
    return a:cdef
endf


function! s:RepeatCommentstring(cdef) "{{{3
    " TLogVAR a:cdef
    let cms = s:BlockGetCommentString(a:cdef)
    let mid = s:BlockGetMiddleString(a:cdef)
    let cms_fbeg = match(cms, '\s*%\@<!%s')
    let cms_fend = matchend(cms, '%\@<!%s\s*')
    let rxbeg = get(a:cdef, 'rxbeg', '^.*$')
    let rxend = get(a:cdef, 'rxend', '^.*$')
    let rpbeg = repeat('&', get(a:cdef, 'cbeg', get(a:cdef, 'count', 1)))
    let rpend = repeat('&', get(a:cdef, 'cend', get(a:cdef, 'count', 1)))
    let cmsbeg = cms[0 : cms_fbeg - 1]
    if !empty(rxbeg)
        let cmsbeg = substitute(cmsbeg, rxbeg, rpbeg, '')
    endif
    let cmsend = cms[cms_fend : -1]
    if !empty(rxend)
        let cmsend = substitute(cmsend, rxend, rpend, '')
    endif
    let a:cdef.commentstring = cmsbeg . cms[cms_fbeg : cms_fend - 1] . cmsend
    " TLogVAR cms, a:cdef.commentstring
    if !empty(mid)
        let rxmid = get(a:cdef, 'rxmid', '^.*$')
        if empty(rxmid)
            let a:cdef.middle = mid
        else
            let rpmid = repeat('&', get(a:cdef, 'cmid', get(a:cdef, 'count', 1)))
            let a:cdef.middle = substitute(mid, rxmid, rpmid, '')
            " TLogVAR mid, a:cdef.middle
        endif
    endif
    return a:cdef
endf


function! s:ParseArgs(beg, end, comment_mode, arglist) "{{{3
    let args = {}
    for arg in a:arglist
        let key = matchstr(arg, '^[^=]\+')
        let value = matchstr(arg, '=\zs.*$')
        if !empty(key)
            let args[key] = value
        endif
    endfor
    return s:ExtendCDef(a:beg, a:end, a:comment_mode, {}, args)
endf


function! s:ExtendCDef(beg, end, comment_mode, cdef, args)
    for [key, value] in items(a:args)
        " TLogVAR key, value
        if key == 'as'
            call extend(a:cdef, s:GetCommentDefinitionForType(a:beg, a:end, a:comment_mode, value))
        elseif key == 'mode'
            " let a:cdef[key] = a:comment_mode . value
            let a:cdef.mode = s:AddModeExtra(a:comment_mode, value, a:beg, a:end)
        elseif key == 'mode_extra'
            if has_key(a:cdef, 'mode')
                let mode = s:AddModeExtra(a:comment_mode, a:cdef.mode, a:beg, a:end)
                " TLogVAR 'mode', mode
            else
                let mode = a:comment_mode
                " TLogVAR 'mode == comment_mode', mode
            endif
            let a:cdef.mode = s:AddModeExtra(mode, value, a:beg, a:end)
        elseif key == 'count'
            let a:cdef[key] = str2nr(value)
        else
            let a:cdef[key] = value
        endif
        " TLogVAR get(a:cdef, 'comment_mode', '')
    endfor
    return a:cdef
endf


function! tcomment#Operator(type, ...) "{{{3
    let type = a:type
    let comment_mode = a:0 >= 1 ? a:1 : ''
    let bang = a:0 >= 2 ? a:2 : ''
    " TLogVAR type, comment_mode, bang
    if !exists('w:tcomment_pos')
        let w:tcomment_pos = getpos(".")
    endif
    let sel_save = &selection
    set selection=inclusive
    let reg_save = @@
    try
        if type == 'line'
            silent exe "normal! '[V']"
            let comment_mode1 = 'G'
        elseif type == 'block'
            silent exe "normal! `[\<C-V>`]"
            let comment_mode1 = 'I'
        elseif type == 'char'
            silent exe "normal! `[v`]"
            let comment_mode1 = 'I'
        else
            silent exe "normal! `[v`]"
            let comment_mode1 = 'i'
        endif
        if empty(comment_mode)
            let comment_mode = comment_mode1
        endif
        let lbeg = line("'[")
        let lend = line("']")
        let cend = virtcol("']")
        if type == 'char'
            if lbeg == lend && cend >= virtcol('$') - 1
                let comment_mode = 'R'
            elseif g:tcomment#ignore_char_type && lbeg != lend
                silent exe "normal! '[V']"
                let cend = virtcol("']")
                let comment_mode = 'G'
                let type = 'line'
            endif
        endif
        let cbeg = virtcol("'[")
        " TLogVAR comment_mode, comment_mode1, lbeg, lend, cbeg, cend, virtcol('$')
        " TLogVAR comment_mode
        " echom "DBG tcomment#Operator" lbeg virtcol("'[") virtcol("'<") lend virtcol("']") virtcol("'>")
        norm! 
        let comment_mode = s:AddModeExtra(comment_mode, g:tcommentOpModeExtra, lbeg, lend)
        " TLogVAR comment_mode, type
        "  if type =~ 'line\|block' || g:tcomment#ignore_char_type
        " if comment_mode =~# '[R]'
        "     call tcomment#Comment([lbeg, cbeg], [lend, cend], comment_mode.'o', bang)
        " elseif type =~ 'line\|block' || g:tcomment#ignore_char_type
        if type =~ 'line\|block'
            call tcomment#Comment(lbeg, lend, comment_mode.'o', bang)
        else
            call tcomment#Comment([lbeg, cbeg], [lend, cend], comment_mode.'o', bang)
        endif
    finally
        let &selection = sel_save
        let @@ = reg_save
        " TLogVAR g:tcommentOpModeExtra
        if g:tcommentOpModeExtra !~ '[#>]'
            if exists('w:tcomment_pos')
                " TLogVAR w:tcomment_pos
                if w:tcomment_pos != getpos('.')
                    call setpos('.', w:tcomment_pos)
                endif
                unlet! w:tcomment_pos
            else
                echohl WarningMsg
                echom "TComment: w:tcomment_pos wasn't set. Please report this to the plugin author"
                echohl NONE
            endif
        endif
    endtry
endf


function! tcomment#OperatorLine(type) "{{{3
    " TLogVAR a:type
    call tcomment#Operator('line', 'L')
endf


function! tcomment#OperatorAnyway(type) "{{{3
    " TLogVAR a:type
    call tcomment#Operator(a:type, '', '!')
endf


function! tcomment#OperatorLineAnyway(type) "{{{3
    " TLogVAR a:type
    call tcomment#Operator('line', 'L', '!')
endf


" :display: tcomment#CommentAs(beg, end, comment_anyway, filetype, ?args...)
" Where args is either:
"   1. A count NUMBER
"   2. An args list (see the notes on the "args" argument of 
"      |tcomment#Comment()|)
" comment text as if it were of a specific filetype
function! tcomment#CommentAs(beg, end, comment_anyway, filetype, ...)
    if a:filetype =~ '_block$'
        let comment_mode = 'B'
        let ft = substitute(a:filetype, '_block$', '', '')
    elseif a:filetype =~ '_inline$'
        let comment_mode = 'I'
        let ft = substitute(a:filetype, '_inline$', '', '')
    else 
        let comment_mode = 'G'
        let ft = a:filetype
    endif
    if a:0 >= 1
        if type(a:1) == 0
            let cdef = {'count': a:0 >= 1 ? a:1 : 1}
        else
            let cdef = s:ParseArgs(a:beg, a:end, comment_mode, a:000)
        endif
    else
        let cdef = {}
    endif
    " echom "DBG" string(cdef)
    call extend(cdef, s:GetCommentDefinitionForType(a:beg, a:end, comment_mode, ft))
    keepjumps call tcomment#Comment(a:beg, a:end, comment_mode, a:comment_anyway, cdef)
endf


" collect all known comment types
" :nodoc:
function! tcomment#CollectFileTypes()
    if s:types_dirty
        let s:types = keys(s:definitions)
        let s:types_rx = '\V\^\('. join(s:types, '\|') .'\)\(\u\.\*\)\?\$'
        let s:types_dirty = 0
    endif
endf

call tcomment#CollectFileTypes()


" return a list of filetypes for which a tcomment_{&ft} is defined
" :nodoc:
function! tcomment#Complete(ArgLead, CmdLine, CursorPos) "{{{3
    call tcomment#CollectFileTypes()
    let completions = copy(s:types)
    let filetype = s:Filetype()
    if index(completions, filetype) != -1
        " TLogVAR filetype
        call insert(completions, filetype)
    endif
    if !empty(a:ArgLead)
        call filter(completions, 'v:val =~ ''\V\^''.a:ArgLead')
    endif
    let completions += tcomment#CompleteArgs(a:ArgLead, a:CmdLine, a:CursorPos)
    return completions
endf


let s:first_completion = 0

" :nodoc:
function! tcomment#CompleteArgs(ArgLead, CmdLine, CursorPos) "{{{3
    if v:version < 703 && !s:first_completion
        redraw
        let s:first_completion = 1
    endif
    let completions = ['as=', 'col=', 'count=', 'mode=', 'begin=', 'end=', 'rxbeg=', 'rxend=', 'rxmid=']
    if !empty(a:ArgLead)
        if a:ArgLead =~ '^as='
            call tcomment#CollectFileTypes()
            let completions += map(copy(s:types), '"as=". v:val')
        endif
        call filter(completions, 'v:val =~ ''\V\^''.a:ArgLead')
    endif
    return completions
endf


function! s:EncodeCommentPart(string)
    return substitute(a:string, '%', '%%', 'g')
endf


function! s:GetCommentDefinitionForType(beg, end, comment_mode, filetype) "{{{3
    let cdef = s:GetCommentDefinition(a:beg, a:end, a:comment_mode, a:filetype)
    " TLogVAR cdef
    let cms  = cdef.commentstring
    let comment_mode = cdef.mode
    let pre  = substitute(cms, '%\@<!%s.*$', '', '')
    let pre  = substitute(pre, '%%', '%', 'g')
    let post = substitute(cms, '^.\{-}%\@<!%s', '', '')
    let post = substitute(post, '%%', '%', 'g')
    let cdef.begin = pre
    let cdef.end   = post
    return cdef
endf


" s:GetCommentDefinition(beg, end, comment_mode, ?filetype="")
function! s:GetCommentDefinition(beg, end, comment_mode, ...)
    let ft = a:0 >= 1 ? a:1 : ''
    " TLogVAR a:comment_mode, ft
    if ft != ''
        let cdef = s:GuessCustomCommentString(ft, a:comment_mode)
    else
        let cdef = {'mode': a:comment_mode}
    endif
    " TLogVAR cdef
    let cms = get(cdef, 'commentstring', '')
    if empty(cms)
        let filetype = s:Filetype(ft)
        if exists('b:commentstring')
            let cms = b:commentstring
            " TLogVAR 1, cms
            return s:GuessCustomCommentString(filetype, a:comment_mode, cms)
        elseif exists('b:commentStart') && b:commentStart != ''
            let cms = s:EncodeCommentPart(b:commentStart) .' %s'
            " TLogVAR 2, cms
            if exists('b:commentEnd') && b:commentEnd != ''
                let cms = cms .' '. s:EncodeCommentPart(b:commentEnd)
            endif
            return s:GuessCustomCommentString(filetype, a:comment_mode, cms)
        else
            let [use_guess_ft, altFiletype] = s:AltFiletype(ft)
            " TLogVAR use_guess_ft, altFiletype
            if use_guess_ft
                return s:GuessFileType(a:beg, a:end, a:comment_mode, filetype, altFiletype)
            else
                let guess_cdef = s:GuessVimOptionsCommentString(a:comment_mode)
                " TLogVAR guess_cdef
                return s:GuessCustomCommentString(filetype, a:comment_mode, guess_cdef.commentstring, guess_cdef)
            endif
        endif
        let cdef.commentstring = cms
    endif
    return cdef
endf


function! s:StartPosRx(comment_mode, line, col)
    " TLogVAR a:comment_mode, a:line, a:col
    " if a:comment_mode =~# 'I'
    "     return s:StartLineRx(a:line) . s:StartColRx(a:comment_mode, a:col)
    " else
        let rv = s:StartColRx(a:comment_mode, a:col)
    " endif
    " TLogVAR rv
    return rv
endf


function! s:EndPosRx(comment_mode, lnum, col)
    " TLogVAR a:comment_mode, a:lnum, a:col
    " if a:comment_mode =~# 'I'
    "     return s:EndLineRx(a:lnum) . s:EndColRx(a:col)
    " else
        return s:EndColRx(a:comment_mode, a:lnum, a:col)
    " endif
endf


function! s:StartLineRx(pos)
    return '\%'. a:pos .'l'
endf


function! s:EndLineRx(pos)
    return '\%'. a:pos .'l'
endf


function! s:StartColRx(comment_mode, col, ...)
    let mixedindent = a:0 >= 1 ? a:1 : get(s:cdef, 'mixedindent', 1)
    " TLogVAR a:comment_mode, a:col, mixedindent
    if a:comment_mode =~# '[IR]'
        let col = mixedindent ? a:col - 1 : a:col
        let c0 = 1
    else
        let col = a:col
        let c0 = 2
    endif
    " TLogVAR col, c0, mixedindent
    if col < c0
        return '\^'
    elseif mixedindent
        return '\%>'. col .'v'
    else
        return '\%'. col .'v'
    endif
endf


function! s:EndColRx(comment_mode, lnum, pos)
    " TLogVAR a:comment_mode, a:lnum, a:pos
    let line = getline(a:lnum)
    let cend = s:Strdisplaywidth(line)
    " TLogVAR cend
    if a:pos == 0 || a:pos >= cend
        return '\$'
    else
        if a:comment_mode =~? 'i' && a:comment_mode =~# 'o'
            let mod = '>'
        else
            let mod = ''
        endif
        " TLogVAR &selection, mod
        return '\%'. mod . a:pos .'v'
    endif
endf


function! s:CommentDef(beg, end, checkRx, comment_mode, cbeg, cend)
    " TLogVAR a:beg, a:end, a:checkRx, a:comment_mode, a:cbeg, a:cend
    let beg = a:beg
    let end = a:end
    if a:comment_mode =~# 'U'
        let uncomment = 1
    elseif a:comment_mode =~# '[CK]'
        let uncomment = 0
    else
        if get(s:cdef, 'mixedindent', 1)
            let mdrx = '\V'. s:StartColRx(a:comment_mode, a:cbeg) .'\s\*'
            let mdrx .= s:StartColRx(a:comment_mode, a:cbeg + 1, 0) .'\s\*'
        else
            let mdrx = '\V'. s:StartColRx(a:comment_mode, a:cbeg) .'\s\*'
        endif
        let mdrx .= a:checkRx .'\s\*'. s:EndColRx(a:comment_mode, a:end, 0)
        " let mdrx = '\V'. s:StartPosRx(a:comment_mode, beg, a:cbeg) .'\s\*'. a:checkRx .'\s\*'. s:EndPosRx(a:comment_mode, end, 0)
        " TLogVAR mdrx
        let line = getline(beg)
        if a:cbeg != 0 && a:cend != 0
            let line = strpart(line, 0, a:cend - 1)
        endif
        let uncomment = (line =~ mdrx)
        " TLogVAR 1, uncomment, line
        let n  = beg + 1
        if a:comment_mode =~# 'G'
            if uncomment
                while n <= end
                    if getline(n) =~ '\S'
                        if !(getline(n) =~ mdrx)
                            let uncomment = 0
                            " TLogVAR 2, uncomment
                            break
                        endif
                    endif
                    let n = n + 1
                endwh
            endif
        elseif a:comment_mode =~# 'B'
            let t = @t
            try
                silent exec 'norm! '. beg.'G1|v'.end.'G$"ty'
                if &selection == 'inclusive' && @t =~ '\n$' && len(@t) > 1
                    let @t = @t[0 : -2]
                endif
                " TLogVAR @t, mdrx
                let uncomment = (@t =~ mdrx)
                " TLogVAR 3, uncomment
                if !uncomment && a:comment_mode =~ 'o'
                    let mdrx1 = substitute(mdrx, '\\$$', '\\n\\$', '')
                    " TLogVAR mdrx1
                    if @t =~ mdrx1
                        let uncomment = 1
                        " TLogVAR 4, uncomment
                    endif
                endif
            finally
                let @t = t
            endtry
        endif
    endif
    " TLogVAR 5, beg, end, uncomment
    return [beg, end, uncomment]
endf


function! s:ProcessLine(comment_do, match, checkRx, replace)
    " TLogVAR a:comment_do, a:match, a:checkRx, a:replace
    try
        if !(g:tcomment#blank_lines > 0 || a:match =~ '\S')
            return a:match
        endif
        if a:comment_do ==# 'k'
            if a:match =~ a:checkRx
                return ['', 0]
            endif
        endif
        if a:comment_do ==# 'u'
            let m = matchlist(a:match, a:checkRx)
            if !empty(m)
                for irx in range(2, s:Count(a:checkRx, '\\\@<!\\('))
                    if !empty(m[irx])
                        break
                    endif
                endfor
                " TLogVAR irx
            else
                let irx = 2
            endif
            let rv = substitute(a:match, a:checkRx, '\1\'. irx, '')
            let rv = s:UnreplaceInLine(rv)
        else
            let ml = len(a:match)
            let rv = s:ReplaceInLine(a:match)
            let rv = s:Printf1(a:replace, rv)
            let strip_whitespace = get(s:cdef, 'strip_whitespace', 1)
            if strip_whitespace == 2 || (strip_whitespace == 1 && ml == 0)
                let rv = substitute(rv, '\s\+$', '', '')
            endif
        endif
        " TLogVAR rv
        " echom "DBG s:cdef.mode=" string(s:cdef.mode) "s:cursor_pos=" string(s:cursor_pos)
        if s:cdef.mode =~ '>'
            let s:cursor_pos = getpos('.')
            let s:cursor_pos[2] += len(rv)
        elseif s:cdef.mode =~ '#'
            if empty(s:cursor_pos) || s:current_pos[1] == s:processline_lnum
                let prefix = matchstr(a:replace, '^.*%\@<!\ze%s')
                let prefix = substitute(prefix, '%\(.\)', '\1', 'g')
                let prefix_len = s:Strdisplaywidth(prefix)
                " TLogVAR a:replace, prefix_len
                if prefix_len != -1
                    let s:cursor_pos = copy(s:current_pos)
                    if a:comment_do ==# 'u'
                        let s:cursor_pos[2] -= prefix_len
                        if s:cursor_pos[2] < 1
                            let s:cursor_pos[2] = 1
                        endif
                    else
                        let s:cursor_pos[2] += prefix_len
                    endif
                    " echom "DBG s:current_pos=" string(s:current_pos) "s:cursor_pos=" string(s:cursor_pos)
                endif
            endif
        endif
        " TLogVAR rv
        if g:tcomment#must_escape_expression_backslash
            let rv = escape(rv, "\\r")
        else
            let rv = escape(rv, "\r")
        endif
        " TLogVAR rv
        " let rv = substitute(rv, '\n', '\\\n', 'g')
        " TLogVAR rv
        return [rv, 1]
    finally
        let s:processline_lnum += 1
    endtry
endf


function! s:ReplaceInLine(text) "{{{3
    if has_key(s:cdef, 'replacements')
        let replacements = s:cdef.replacements
        return s:DoReplacements(a:text, keys(replacements), values(replacements))
    else
        return a:text
    endif
endf


function! s:UnreplaceInLine(text) "{{{3
    if has_key(s:cdef, 'replacements')
        let replacements = s:cdef.replacements
        return s:DoReplacements(a:text, values(replacements), keys(replacements))
    else
        return a:text
    endif
endf


function! s:DoReplacements(text, tokens, replacements) "{{{3
    if empty(a:tokens)
        return a:text
    else
        let rx = '\V\('. join(map(a:tokens, 'escape(v:val, ''\'')'), '\|') .'\)'
        let texts = split(a:text, rx .'\zs', 1)
        let texts = map(texts, 's:InlineReplacement(v:val, rx, a:tokens, a:replacements)')
        let text = join(texts, '')
        return text
    endif
endf


function! s:InlineReplacement(text, rx, tokens, replacements) "{{{3
    " TLogVAR a:text, a:rx, a:replacements
    let matches = split(a:text, '\ze'. a:rx .'\$', 1)
    if len(matches) == 1
        return a:text
    else
        let match = matches[-1]
        let idx = index(a:tokens, match)
        " TLogVAR matches, match, idx
        if idx != -1
            let matches[-1] = a:replacements[idx]
            " TLogVAR matches
            return join(matches, '')
        else
            throw 'TComment: Internal error: cannot find '. string(match) .' in '. string(a:tokens)
        endif
    endif
endf


function! s:CommentBlock(beg, end, cbeg, cend, comment_mode, comment_do, checkRx, cdef)
    " TLogVAR a:beg, a:end, a:cbeg, a:cend, a:comment_do, a:checkRx, a:cdef
    let indentStr = repeat(' ', a:cbeg)
    let t = @t
    let sel_save = &selection
    set selection=exclusive
    try
        silent exec 'norm! '. a:beg.'G1|v'.a:end.'G$"td'
        " TLogVAR @t
        let ms = s:BlockGetMiddleString(a:cdef)
        let mx = escape(ms, '\')
        let cs = s:BlockGetCommentString(a:cdef)
        let prefix = substitute(matchstr(cs, '^.*%\@<!\ze%s'), '%\(.\)', '\1', 'g')
        let postfix = substitute(matchstr(cs, '%\@<!%s\zs.*$'), '%\(.\)', '\1', 'g')
        " TLogVAR ms, mx, cs, prefix, postfix
        if a:comment_do == 'u'
            let @t = substitute(@t, '\V\^\s\*'. a:checkRx .'\$', '\1', '')
            let tt = []
            " TODO: Correctly handle foreign comments with inconsistent 
            " whitespace around mx markers
            let rx = '\V'. s:StartColRx(a:comment_mode, a:cbeg) . '\zs'. mx
            " TLogVAR mx1, rx
            for line in split(@t, '\n')
                let line1 = substitute(line, rx, '', 'g')
                call add(tt, line1)
            endfor
            let @t = join(tt, "\n")
            " TLogVAR @t
            let @t = substitute(@t, '^\n', '', '')
            let @t = substitute(@t, '\n\s*$', '', '')
            if a:comment_mode =~ '#'
                let s:cursor_pos = copy(s:current_pos)
                let prefix_lines = len(substitute(prefix, "[^\n]", '', 'g')) + 1
                let postfix_lines = len(substitute(postfix, "[^\n]", '', 'g')) + 1
                " TODO: more precise solution (when cursor is placed on 
                " postfix or prefix
                if s:cursor_pos[1] > a:beg
                    let s:cursor_pos[1] -= prefix_lines
                    if s:cursor_pos[1] > a:end - postfix_lines
                        let s:cursor_pos[1] -= postfix_lines
                    endif
                    if s:cursor_pos[1] < 1
                        let s:cursor_pos[1] = 1
                    endif
                endif
                let prefix_len = s:Strdisplaywidth(mx)
                let s:cursor_pos[2] -= prefix_len
                if s:cursor_pos[2] < 1
                    let s:cursor_pos[2] = 1
                endif
            endif
        else
            let cs = indentStr . substitute(cs, '%\@<!%s', '%s'. indentStr, '')
            " TLogVAR cs, ms
            if ms != ''
                let lines = []
                let lnum = 0
                let indentlen = a:cbeg
                let rx = '^.\{-}\%>'. indentlen .'v\zs'
                " TLogVAR indentStr, indentlen, rx, @t, empty(@t)
                if @t =~ '^\n\?$'
                    let lines = [indentStr . ms]
                    let cs .= "\n"
                    " TLogVAR 1, lines
                else
                    for line in split(@t, '\n')
                        " TLogVAR 1, line
                        if lnum == 0
                            let line = substitute(line, rx, ms, '')
                        else
                            let line = substitute(line, rx, mx, '')
                        endif
                        " TLogVAR 2, line
                        call add(lines, line)
                        let lnum += 1
                    endfor
                    " TLogVAR 2, lines
                endif
                let @t = join(lines, "\n")
                " TLogVAR 3, @t
            endif
            let @t = s:Printf1(cs, "\n". @t ."\n")
            " TLogVAR 4, cs, @t, a:comment_mode
            if a:comment_mode =~ '#'
                let s:cursor_pos = copy(s:current_pos)
                let s:cursor_pos[1] += len(substitute(prefix, "[^\n]", '', 'g')) + 1
                let prefix_len = s:Strdisplaywidth(mx)
                let s:cursor_pos[2] += prefix_len
                " echom "DBG s:current_pos=" string(s:current_pos) "s:cursor_pos=" string(s:cursor_pos)
            endif
        endif
        silent norm! "tP
    finally
        let &selection = sel_save
        let @t = t
    endtry
endf


function! s:Filetype(...) "{{{3
    let ft = a:0 >= 1 && !empty(a:1) ? a:1 : &filetype
    let pos = a:0 >= 2 ? a:2 : 0
    " TLogVAR ft, pos
    let fts = split(ft, '^\@!\.')
    " TLogVAR fts
    " let ft = substitute(ft, '\..*$', '', '')
    let rv = get(fts, pos, ft)
    " TLogVAR fts, rv
    if !exists('s:filetype_map_rx')
        let fts_rx = '^'. join(map(keys(g:tcomment#filetype_map), 'escape(v:val, ''\'')'), '\|') .'$'
    endif
    " TLogVAR fts_rx
    if rv =~ fts_rx
        for [ft_rx, ftrv] in items(g:tcomment#filetype_map)
            " TLogVAR ft_rx, ftrv
            if rv =~ ft_rx
                let rv = substitute(rv, ft_rx, ftrv, '')
                " TLogVAR rv
                break
            endif
        endfor
    endif
    return rv
endf


function! s:AltFiletype(filetype) "{{{3
    let filetype = empty(a:filetype) ? &filetype : a:filetype
    " TLogVAR a:filetype, filetype
    if g:tcommentGuessFileType || (exists('g:tcommentGuessFileType_'. filetype) 
                \ && g:tcommentGuessFileType_{filetype} =~ '[^0]')
        if g:tcommentGuessFileType_{filetype} == 1
            if filetype =~ '^.\{-}\..\+$'
                let altFiletype = s:Filetype(filetype, 1)
            else
                let altFiletype = ''
            endif
        else
            let altFiletype = g:tcommentGuessFileType_{filetype}
        endif
        " TLogVAR 1, altFiletype
        return [1, altFiletype]
    elseif filetype =~ '^.\{-}\..\+$'
        let altFiletype = s:Filetype(filetype, 1)
        " TLogVAR 2, altFiletype
        return [1, altFiletype]
    else
        " TLogVAR 3, ''
        return [0, '']
    endif
endf


" A function that makes the s:GuessFileType() function usable for other 
" library developers.
"
" The argument is a dictionary with the following keys:
"
"   beg ................ (default = line("."))
"   end ................ (default = line("."))
"   comment_mode ........ (default = "G")
"   filetype ........... (default = &filetype)
"   fallbackFiletype ... (default = "")
"
" This function return a dictionary that contains information about how 
" to make comments. The information about the filetype of the text 
" between lines "beg" and "end" is in the "filetype" key of the return 
" value. It returns the first discernible filetype it encounters.
" :display: tcomment#GuessFileType(?options={})
function! tcomment#GuessCommentType(...) "{{{3
    let options = a:0 >= 1 ? a:1 : {}
    let beg = get(options, 'beg', line('.'))
    let end = get(options, 'end', line('.'))
    let comment_mode = get(options, 'comment_mode', '')
    let filetype = get(options, 'filetype', &filetype)
    let fallbackFiletype = get(options, 'filetype', '')
    return s:GuessFileType(beg, end, comment_mode, filetype, fallbackFiletype)
endf


" inspired by Meikel Brandmeyer's EnhancedCommentify.vim
" this requires that a syntax names are prefixed by the filetype name 
" s:GuessFileType(beg, end, comment_mode, filetype, ?fallbackFiletype)
function! s:GuessFileType(beg, end, comment_mode, filetype, ...)
    " TLogVAR a:beg, a:end, a:comment_mode, a:filetype, a:000
    let cdef0 = s:GuessCustomCommentString(a:filetype, a:comment_mode)
    if a:0 >= 1 && a:1 != ''
        let cdef = s:GuessCustomCommentString(a:1, a:comment_mode)
        " TLogVAR 0, cdef
        let cdef = extend(cdef, cdef0, 'keep')
        " TLogVAR 1, cdef
        if empty(get(cdef, 'commentstring', ''))
            let guess_cdef = s:GuessVimOptionsCommentString(a:comment_mode)
            call extend(cdef, guess_cdef)
        endif
        " TLogVAR 2, cdef
    else
        let cdef = cdef0
        " TLogVAR 3, cdef
        if !has_key(cdef, 'commentstring')
            let cdef = s:GuessVimOptionsCommentString(a:comment_mode)
        endif
        " TLogVAR 4, cdef
    endif
    let beg = a:beg
    let end = nextnonblank(a:end)
    if end == 0
        let end = a:end
        let beg = prevnonblank(a:beg)
        if beg == 0
            let beg = a:beg
        endif
    endif
    let n  = beg
    " TLogVAR n, beg, end
    while n <= end
        let text = getline(n)
        let indentstring = matchstr(text, '^\s*')
        let m = strwidth(indentstring)
        " let m  = indent(n) + 1
        let le = strwidth(text)
        " TLogVAR n, m, le
        while m <= le
            let syntax_name = s:GetSyntaxName(n, m)
            " TLogVAR syntax_name, n, m
            unlet! ftype_map
            let ftype_map = get(g:tcommentSyntaxMap, syntax_name, '')
            " TLogVAR ftype_map
            if !empty(ftype_map) && type(ftype_map) == 4
                if n < a:beg
                    let key = 'prevnonblank'
                elseif n > a:end
                    let key = 'nextnonblank'
                else
                    let key = ''
                endif
                if empty(key) || !has_key(ftype_map, key)
                    let ftypeftype = get(ftype_map, 'filetype', {})
                    " TLogVAR ftype_map, ftypeftype
                    unlet! ftype_map
                    let ftype_map = get(ftypeftype, a:filetype, '')
                else
                    let mapft = ''
                    for mapdef in ftype_map[key]
                        if strpart(text, m - 1) =~ '^'. mapdef.match
                            let mapft = mapdef.filetype
                            break
                        endif
                    endfor
                    unlet! ftype_map
                    if empty(mapft)
                        let ftype_map = ''
                    else
                        let ftype_map = mapft
                    endif
                endif
            endif
            if !empty(ftype_map)
                " TLogVAR ftype_map
                return s:GuessCustomCommentString(ftype_map, a:comment_mode, cdef.commentstring)
            elseif syntax_name =~ s:types_rx
                let ft = substitute(syntax_name, s:types_rx, '\1', '')
                " TLogVAR ft
                if exists('g:tcommentIgnoreTypes_'. a:filetype) && g:tcommentIgnoreTypes_{a:filetype} =~ '\<'.ft.'\>'
                    let m += 1
                else
                    return s:GuessCustomCommentString(ft, a:comment_mode, cdef.commentstring)
                endif
            elseif syntax_name == '' || syntax_name == 'None' || syntax_name =~ '^\u\+$' || syntax_name =~ '^\u\U*$'
                let m += 1
            else
                break
            endif
        endwh
        let n += 1
    endwh
    " TLogVAR cdef
    return cdef
endf


function! s:GetSyntaxName(lnum, col) "{{{3
    let syntax_name = synIDattr(synID(a:lnum, a:col, 1), 'name')
    if !empty(g:tcomment#syntax_substitute)
        for [rx, subdef] in items(g:tcomment#syntax_substitute)
            if !has_key(subdef, 'if') || eval(subdef.if)
                let syntax_name = substitute(syntax_name, rx, subdef.sub, 'g')
            endif
        endfor
    endif
    " TLogVAR syntax_name
    return syntax_name
endf


function! s:AddModeExtra(comment_mode, extra, beg, end) "{{{3
    " TLogVAR a:comment_mode, a:extra
    if a:beg == a:end
        let extra = substitute(a:extra, '\C[B]', '', 'g')
    else
        let extra = substitute(a:extra, '\C[IR]', '', 'g')
    endif
    let comment_mode = a:comment_mode
    if extra =~# 'B'
        let comment_mode = substitute(comment_mode, '\c[gir]', '', 'g')
    endif
    if extra =~# '[IR]'
        let comment_mode = substitute(comment_mode, '\c[gb]', '', 'g')
    endif
    if extra =~# '[BLIRK]' && comment_mode =~# 'G'
        let comment_mode = substitute(comment_mode, '\c[G]', '', 'g')
    endif
    let rv = comment_mode . extra
    " TLogVAR a:comment_mode, a:extra, comment_mode, extra, rv
    return rv
endf


function! s:GuessCommentMode(comment_mode, supported_comment_modes) "{{{3
    " TLogVAR a:comment_mode, a:supported_comment_modes
    let special = substitute(a:comment_mode, '\c[^ukc]', '', 'g')
    let cmode = tolower(a:comment_mode)
    let ccmodes = split(tolower(a:supported_comment_modes), '\zs')
    let ccmodes = filter(ccmodes, 'stridx(cmode, v:val) != -1')
    let guess = substitute(a:comment_mode, '\w\+', 'G', 'g')
    " TLogVAR ccmodes, guess
    if a:comment_mode =~# '[BR]'
        let rv = !empty(ccmodes) ? a:comment_mode : guess
    elseif a:comment_mode =~# '[I]'
        let rv = !empty(ccmodes) ? a:comment_mode : ''
    else
        let rv = guess
    endif
    return s:AddModeExtra(rv, special, 0, 1)
endf


function! s:GuessVimOptionsCommentString(comment_mode)
    " TLogVAR a:comment_mode
    let valid_cms = (match(&commentstring, '%\@<!\(%%\)*%s') != -1)
    let ccmodes = 'r'
    if &commentstring =~ '\S\s*%s\s*\S'
        let ccmodes .= 'bi'
    endif
    let guess_comment_mode = s:GuessCommentMode(a:comment_mode, ccmodes)
    " TLogVAR guess_comment_mode
    if &commentstring != s:default_comment_string && valid_cms
        " The &commentstring appears to have been set and to be valid
        let cdef = copy(g:tcomment#options_commentstring)
        let cdef.mode = guess_comment_mode
        let cdef.commentstring = &commentstring
        return cdef
    endif
    if &comments != s:default_comments
        " the commentstring is the default one, so we assume that it wasn't 
        " explicitly set; we then try to reconstruct &cms from &comments
        let cdef = s:ConstructFromCommentsOption(a:comment_mode)
        " TLogVAR comments_cdef
        if !empty(cdef)
            call extend(cdef, g:tcomment#options_comments)
            return cdef
        endif
    endif
    if valid_cms
        " Before &commentstring appeared not to be set. As we don't know 
        " better we return it anyway if it is valid
        let cdef = copy(g:tcomment#options_commentstring)
        let cdef.mode = guess_comment_mode
        let cdef.commentstring = &commentstring
        return cdef
    else
        " &commentstring is invalid. So we return the identity string.
        let cdef = {}
        let cdef.mode = guess_comment_mode
        let cdef.commentstring = s:null_comment_string
        return cdef
    endif
endf


function! s:ConstructFromCommentsOption(comment_mode)
    " TLogVAR a:comment_mode
    let cdef = {}
    let comments = s:ExtractCommentsPart()
    " TLogVAR comments
    if a:comment_mode =~? '[bi]' && !empty(comments.s.string)
        let cdef.mode = a:comment_mode
        let cdef.commentstring = comments.s.string .' %s '. comments.e.string
        if a:comment_mode =~? '[b]' && !empty(comments.m.string)
            let mshift = str2nr(matchstr(comments.s.flags, '\(^\|[^-]\)\zs\d\+'))
            let mindent = repeat(' ', mshift)
            let cdef.middle = mindent . comments.m.string
        endif
        " TLogVAR cdef
        return cdef
    endif
    let ccmodes = 'r'
    if !empty(comments.e.string)
        let ccmodes .= 'bi'
    endif
    let comment_mode = s:GuessCommentMode(a:comment_mode, ccmodes)
    if !empty(comments.line.string)
        let cdef.mode = comment_mode
        let cdef.commentstring = comments.line.string .' %s'
    elseif !empty(comments.s.string)
        let cdef.mode = comment_mode
        let cdef.commentstring = comments.s.string .' %s '. comments.e.string
    endif
    return cdef
endf


function! s:ExtractCommentsPart()
    let comments = {
                \ 'line': {'string': '', 'flags': ''},
                \ 's':    {'string': '', 'flags': ''},
                \ 'm':    {'string': '', 'flags': ''},
                \ 'e':    {'string': '', 'flags': ''},
                \ }
    let rx = '\C\([nbfsmelrOx0-9-]*\):\(\%(\\,\|[^,]\)*\)'
    let comparts = split(&l:comments, rx .'\zs,')
    for part in comparts
        let ml = matchlist(part, '^'. rx .'$')
        let flags = ml[1]
        let string = substitute(ml[2], '\\,', ',', 'g')
        if flags !~# 'O'
            let flag = matchstr(flags, '^[sme]')
            if empty(flag)
                let flag = 'line'
            endif
            let string = substitute(string, '%', '%%', 'g')
            let comments[flag] = {'string': string, 'flags': flags}
        endif
    endfor
    return comments
endf


" s:GuessCustomCommentString(ft, comment_mode, ?default="", ?default_cdef={})
function! s:GuessCustomCommentString(ft, comment_mode, ...)
    " TLogVAR a:ft, a:comment_mode, a:000
    let comment_mode   = a:comment_mode
    let custom_comment = tcomment#TypeExists(a:ft)
    let custom_comment_mode = tcomment#TypeExists(a:ft, comment_mode)
    let supported_comment_mode = !empty(custom_comment_mode) ? comment_mode : ''
    " TLogVAR custom_comment, custom_comment_mode
    let default = a:0 >= 1 ? a:1 : ''
    let default_cdef = a:0 >= 2 ? a:2 : {}
    let default_supports_comment_mode = get(default_cdef, 'comment_mode', custom_comment_mode)
    " TLogVAR default, default_supports_comment_mode
    if comment_mode =~# '[ILB]' && !empty(custom_comment_mode)
        let def = tcomment#GetCommentDef(custom_comment_mode)
        " TLogVAR 1, def
    elseif !empty(custom_comment)
        let def = tcomment#GetCommentDef(custom_comment)
        let comment_mode = s:GuessCommentMode(comment_mode, supported_comment_mode)
        " TLogVAR 3, def, comment_mode
    elseif !empty(default)
        if empty(default_cdef)
            let def = {'commentstring': default}
        else
            let def = default_cdef
        endif
        let comment_mode = s:GuessCommentMode(comment_mode, default_supports_comment_mode)
        " TLogVAR 4, def, comment_mode
    else
        let def = {}
        let comment_mode = s:GuessCommentMode(comment_mode, '')
        " TLogVAR 5, def, comment_mode
    endif
    let cdef = copy(def)
    if !has_key(cdef, 'mode')
        let cdef.mode = comment_mode
    endif
    let cdef.filetype = a:ft
    " TLogVAR cdef
    return cdef
endf


function! s:GetCommentReplace(cdef, cms0)
    if has_key(a:cdef, 'commentstring_rx')
        let rs = s:BlockGetCommentString(a:cdef)
    else
        let rs = a:cms0
    endif
    return rs
    " return escape(rs, '"/')
endf


function! s:BlockGetCommentRx(cdef)
    if has_key(a:cdef, 'commentstring_rx')
        return a:cdef.commentstring_rx
    else
        let cms0 = s:BlockGetCommentString(a:cdef)
        let cms0 = escape(cms0, '\')
        return cms0
    endif
endf


function! s:BlockGetCommentString(cdef)
    if has_key(a:cdef, 'middle')
        return a:cdef.commentstring
    else
        return matchstr(a:cdef.commentstring, '^.\{-}\ze\(\n\|$\)')
    endif
endf


function! s:BlockGetMiddleString(cdef)
    if has_key(a:cdef, 'middle')
        return a:cdef.middle
    else
        return matchstr(a:cdef.commentstring, '\n\zs.*')
    endif
endf


function! tcomment#TextObjectInlineComment() "{{{3
    let cdef = tcomment#GuessCommentType({'comment_mode': 'I'})
    let cms  = escape(cdef.commentstring, '\')
    let pos  = getpos('.')
    let lnum = pos[1]
    let col  = pos[2]
    let cmtf = '\V'. printf(cms, '\.\{-}\%'. lnum .'l\%'. col .'c\.\{-}')
    " TLogVAR cmtf, search(cmtf,'cwn')
    if search(cmtf, 'cw') > 0
        let pos0 = getpos('.')
        if search(cmtf, 'cwe') > 0
            let pos1 = getpos('.')
            exec 'norm!'
                        \ pos0[1].'gg'.pos0[2].'|v'.
                        \ pos1[1].'gg'.pos1[2].'|'.
                        \ (&sel == 'exclusive' ? 'l' : '')
        endif
    endif
endf


" vi: ft=vim:tw=72:ts=4:fo=w2croql
