" EnhancedCommentify.vim
" Maintainer:	Meikel Brandmeyer <Brandels_Mikesh@web.de>
" Version:	2.3
" Last Change:	Wednesday, February 20th, 2008

" License:
" Copyright (c) 2008 Meikel Brandmeyer, Frankfurt am Main
" All rights reserved.
" 
" Redistribution and use in source and binary form are permitted provided
" that the following conditions are met:
" 
" 1. Redistribition of source code must retain the above copyright
"    notice, this list of conditions and the following disclaimer.
" 
" 2. Redistributions in binary form must reproduce the above copyright
"    notice, this list of conditions and the following disclaimer in the
"    documentation and/or other materials provided with the distribution.
" 
" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS "AS IS" AND
" ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
" SUCH DAMAGE.

" Description: 
" This is a (well... more or less) simple script to comment lines in a program.
" Currently supported languages are C, C++, PHP, the vim scripting
" language, python, HTML, Perl, LISP, Tex, Shell, CAOS and others.

" Bugfixes:
"   2.3
"   Fixed type 'apacha' -> 'apache' (thanks to Brian Neu)
"   Fixed nested comment escapes strings. (thanks to Samuel Ferencik)
"   Fixed broken keyboard mappings when wcm was set to <Tab>.
"					(thanks to xpatriotx)
"   2.2
"   Fixed problem with UseSyntax (thanks to Pieter Naaijkens) 
"   Fixed typo in ParseCommentsOp (commstr -> commStr). 
"   Fixed support for ocaml (thanks to Zhang Le)
"   2.1
"   Fixed problems with alignement when a line contains tabs 
"   Fixed (resp. cleaned up) issues with overrideEL (thanks to Steve Hall) 
"   Fixed problems with javascript detection (thanks to Brian Neu) 
"   Changed Buffer init to BufWinEnter in order to use the modelines. 
"   2.0
"   Fixed invalid expression '\'' -> "'" (thanks to Zak Beck)
"   Setting AltOpen/AltClose to '' (ie. disabling it) would
"   insert '/*' resp. '*/' for character in a line (thanks to Ben Kibbey)
"   1.8
"   Backslashes in comment symbols should not be escaped.
"   typo (commensSymbol -> commentSymbol) (thanks to Steve Butts) 
"   typo (== -> =) 
"   Fixed hardwired '|+'-'+|' pair. 
"   1.7
"   Lines were not correctly decommentified, when there was whitespace
"   at the beginning of the line.    (thanks to Xiangjiang Ma) 
"   Fixed error detecting '*sh' filetypes. 
"   1.3
"   hlsearch was set unconditionally (thanks to Mary Ellen Foster)
"   made function silent	     (thanks to Mare Ellen Foster)

" Changelog:
"   2.3
"   Added support for viki/deplate (thanks to Thomas Link)
"   Added support for xslt/xsd/mail (thanks to Stefano Zacchiroli)
"   Added callback-functionality to enable extensions without the
"   need of modification directly in the script.
"   2.2
"   Added possibility to override the modes, in which keybindings are
"   defined.
"   Keybindings may be defined local to every buffer now.
"   If a filetype is unknown, one can turn off the keybindings now. 
"   2.1
"   Removed any cursor movement. The script should now be free of
"   side-effects.
"   The script now uses &commentstring to determine the right
"   comment strings. Fallback is still the ugly if-thingy.
"   Script can now interpret &comments in order to add a middle
"   string in blocks.
"   Added EnhancedCommentifySet for use by other scripts. (Necessary?) 
"   Added MultiPartBlocks for languages with multipart-comments.
"   Added parsing for comments option if using MultiPartBlocks.
"   2.0
"   IMPORTANT: EnhancedCommentify is now licensed under BSD license
"              for distribution with Cream! However this shouldn't
"              change anything... 
"   useBlockIndent does no longer depend on respectIndent. 
"   Added code to cope with 'C' in '&cpo'. (thanks to Luc Hermitte
"   for pointing this out!)
"   Added EnhCommentifyIdentFrontOnly option.
"   All options are now handled on a per buffer basis. So options
"   can be overriden for different buffers. 
"   1.9
"   Filetype is now recognized via regular expressions.
"   All known filetypes are (more or less) supported.
"   Decomments multipart-block comments.
"   Added RespectIndent, AlignRight and synID-guessing.
"   Switched to buffer variables.
"   1.8
"   Added Ada support. (thanks to Preben Randhol) 
"   Added Latte support.
"   Added blocksupport and possibility to specify action (comment or
"   decomment). It's also possible to guess the action line by line or
"   using the first line of a block.
"   Thanks to Xiangjiang Ma and John Orr for the rich feedback on these
"   issues.
"   Decomments /*foo();*/, when PrettyComments is set.
"   Added 'vhdl' and 'verilog'. (thanks to Steve Butts) 
"   1.7
"   Added different options to control behaviour of the plugin. 
"   Changed default Keybindings to proper plugin settings.
"   1.6
"   Now supports 'm4', 'config', 'automake'
"   'vb', 'aspvbs', 'plsql' (thanks to Zak Beck)
"   1.5
"   Now supports 'java', 'xml', 'jproperties'. (thanks to Scott Stirling)
"   1.4
"   Lines containing only whitespace are now considered empty.
"   Added Tcl support.
"   Multipart comments are now escaped with configurable alternative
"   strings. Prevents nesting errors (eg. /**/*/ in C)
"   1.3
"   Doesn't break lines like
"	foo(); /* bar */
"   when doing commentify.

" Install Details:
" Simply drop this file into your $HOME/.vim/plugin directory.

if exists("DidEnhancedCommentify")
    finish
endif
let DidEnhancedCommentify = 1

let s:savedCpo = &cpo
set cpo-=C

" Note: These must be defined here, since they are used during
"       initialisation.
"
" InitBooleanVariable(confVar, scriptVar, defaultVal)
"	confVar		-- name of the configuration variable
"	scriptVar	-- name of the variable to set
"	defaultVal	-- default value
"
" Tests on existence of configuration variable and sets scriptVar
" according to its contents.
"
function s:InitBooleanVariable(confVar, scriptVar, defaultVal)
    let regex = a:defaultVal ? 'no*' : 'ye*s*'

    if exists(a:confVar) && {a:confVar} =~? regex
	let {a:scriptVar} = !a:defaultVal
    else
	let {a:scriptVar} = a:defaultVal
    endif
endfunction

"
" InitStringVariable(confVar, scriptVar, defaultVal)
"	confVar		-- name of the configuration variable
"	scriptVar	-- name of the variable to set
"	defaultVal	-- default value
"
" Tests on existence of configuration variable and sets scriptVar
" to its contents.
"
function s:InitStringVariable(confVar, scriptVar, defaultVal)
    if exists(a:confVar)
	execute "let ". a:scriptVar ." = ". a:confVar
    else
	let {a:scriptVar} = a:defaultVal
    endif
endfunction

"
" InitScriptVariables(nameSpace)
"	nameSpace	-- may be "g" for global or "b" for local
"
" Initialises the script variables.
"
function s:InitScriptVariables(nameSpace)
    let ns = a:nameSpace	" just for abbreviation
    let lns = (ns == "g") ? "s" : "b" " 'local namespace'

    " Comment escape strings...
    call s:InitStringVariable(ns .":EnhCommentifyAltOpen", lns .":ECaltOpen",
		\ s:ECaltOpen)
    call s:InitStringVariable(ns .":EnhCommentifyAltClose", lns .":ECaltClose",
		\ s:ECaltClose)

    call s:InitBooleanVariable(ns .":EnhCommentifyIgnoreWS", lns .":ECignoreWS",
		\ s:ECignoreWS)

    " Adding a space between comment strings and code...
    if exists(ns .":EnhCommentifyPretty")
	if {ns}:EnhCommentifyPretty =~? 'ye*s*'
	    let {lns}:ECprettyComments = ' '
	    let {lns}:ECprettyUnComments = ' \='
	else
	    let {lns}:ECprettyComments = ''
	    let {lns}:ECprettyUnComments = ''
	endif
    else
	let {lns}:ECprettyComments = s:ECprettyComments
	let {lns}:ECprettyUnComments = s:ECprettyUnComments
    endif

    " Identification string settings...
    call s:InitStringVariable(ns .":EnhCommentifyIdentString",
		\ lns .":ECidentFront", s:ECidentFront)
    let {lns}:ECidentBack =
		\ (exists(ns .":EnhCommentifyIdentFrontOnly")
		\	    && {ns}:EnhCommentifyIdentFrontOnly =~? 'ye*s*')
		\ ? ''
		\ : {lns}:ECidentFront

    " Wether to use syntax items...
    call s:InitBooleanVariable(ns .":EnhCommentifyUseSyntax",
		\ lns .":ECuseSyntax", s:ECuseSyntax)

    " Should the script respect line indentation, when inserting strings?
    call s:InitBooleanVariable(ns .":EnhCommentifyRespectIndent",
		\ lns .":ECrespectIndent", s:ECrespectIndent)

    " Keybindings...
    call s:InitBooleanVariable(ns .":EnhCommentifyUseAltKeys",
		\ lns .":ECuseAltKeys", s:ECuseAltKeys)
    call s:InitBooleanVariable(ns .":EnhCommentifyBindPerBuffer",
		\ lns .":ECbindPerBuffer", s:ECbindPerBuffer)
    call s:InitBooleanVariable(ns .":EnhCommentifyBindInNormal",
		\ lns .":ECbindInNormal", s:ECbindInNormal)
    call s:InitBooleanVariable(ns .":EnhCommentifyBindInInsert",
		\ lns .":ECbindInInsert", s:ECbindInInsert)
    call s:InitBooleanVariable(ns .":EnhCommentifyBindInVisual",
		\ lns .":ECbindInVisual", s:ECbindInVisual)
    call s:InitBooleanVariable(ns .":EnhCommentifyUserBindings",
		\ lns .":ECuserBindings", s:ECuserBindings)
    call s:InitBooleanVariable(ns .":EnhCommentifyTraditionalMode",
		\ lns .":ECtraditionalMode", s:ECtraditionalMode)
    call s:InitBooleanVariable(ns .":EnhCommentifyFirstLineMode",
		\ lns .":ECfirstLineMode", s:ECfirstLineMode)
    call s:InitBooleanVariable(ns .":EnhCommentifyUserMode",
		\ lns .":ECuserMode", s:ECuserMode)
    call s:InitBooleanVariable(ns .":EnhCommentifyBindUnknown",
		\ lns .":ECbindUnknown", s:ECbindUnknown)

    " Block stuff...
    call s:InitBooleanVariable(ns .":EnhCommentifyAlignRight",
		\ lns .":ECalignRight", s:ECalignRight)
    call s:InitBooleanVariable(ns .":EnhCommentifyUseBlockIndent",
		\ lns .":ECuseBlockIndent", s:ECuseBlockIndent)
    call s:InitBooleanVariable(ns .":EnhCommentifyMultiPartBlocks",
		\ lns .":ECuseMPBlock", s:ECuseMPBlock)
    call s:InitBooleanVariable(ns .":EnhCommentifyCommentsOp",
		\ lns .":ECuseCommentsOp", s:ECuseCommentsOp)

    let {lns}:ECsaveWhite = ({lns}:ECrespectIndent
		\ || {lns}:ECignoreWS || {lns}:ECuseBlockIndent)
		\	? '\(\s*\)'
		\	: ''

    if !{lns}:ECrespectIndent
	let {lns}:ECuseBlockIndent = 0
    endif

    if {lns}:ECrespectIndent
	let {lns}:ECrespectWhite = '\1'
	let {lns}:ECignoreWhite = ''
    elseif {lns}:ECignoreWS
	let {lns}:ECrespectWhite = ''
	let {lns}:ECignoreWhite = '\1'
    else
	let {lns}:ECrespectWhite = ''
	let {lns}:ECignoreWhite = ''
    endif

    " Using comments option, doesn't make sense without useMPBlock
    "if lns == 'b' && b:ECuseCommentsOp
    "	    let b:ECuseMPBlock = 1
    "endif
endfunction

"
" EnhancedCommentifySet(option, value, ...)
"	option	-- which option
"	value	-- value which will be asigned to the option
"
" The purpose of this function is mainly to act as an interface to the
" outer world. It hides the internally used variables.
"
function EnhancedCommentifySet(option, value)
    if a:option == 'AltOpen'
	let oldval = b:ECaltOpen
	let b:ECaltOpen = a:value
    elseif a:option == 'AltClose'
	let oldval = b:ECaltClose
	let b:ECaltClose = a:value
    elseif a:option == 'IdentString'
	let oldval = b:ECidentFront
	let b:ECidentFront = a:value
    elseif a:option == 'IdentFrontOnly'
	let oldval = (b:ECidentBack == '') ? 'Yes' : 'No'
	let b:ECidentBack = (a:value =~? 'ye*s*') ? '' : b:ECidentFront
    elseif a:option == 'RespectIndent'
	let oldval = b:ECrespectIndent
	let b:ECrespectIndent = (a:value =~? 'ye*s*') ? 1 : 0
    elseif a:option == 'IgnoreWS'
	let oldval = b:ECignoreWS
	let b:ECignoreWS = (a:value =~? 'ye*s*') ? 1 : 0
    elseif a:option == 'Pretty'
	let oldval = (b:ECprettyComments == ' ') ? 'Yes' : 'No'
	if a:value =~? 'ye*s*'
	    let b:ECprettyComments = ' '
	    let b:ECprettyUnComments = ' \='
	else
	    let b:ECprettyComments = ''
	    let b:ECprettyUnComments = ''
	endif
    elseif a:option == 'MultiPartBlocks'
	let oldval = b:ECuseMPBlock
	let b:ECuseMPBlock = (a:value =~? 'ye*s*') ? 1 : 0
    elseif a:option == 'CommentsOp'
	let oldval = b:ECuseCommentsOp
	let b:ECuseCommentsOp = (a:value =~? 'ye*s*') ? 1 : 0
    elseif a:option == 'UseBlockIndent'
	let oldval = b:ECuseBlockIndent
	let b:ECuseBlockIndent = (a:value =~? 'ye*s*') ? 1 : 0
    elseif a:option == 'AlignRight'
	let oldval = b:ECalignRight
	let b:ECalignRight = (a:value =~? 'ye*s*') ? 1 : 0
    elseif a:option == 'UseSyntax'
	let oldval = b:ECuseSyntax
	let b:ECuseSyntax = (a:value =~? 'ye*s*') ? 1 : 0
    else
	if (has("dialog_gui") && has("gui_running"))
	    call confirm("EnhancedCommentifySet: Unknwon option '"
			\ . option . "'")
	else
	    echohl ErrorMsg
	    echo "EnhancedCommentifySet: Unknown option '". option ."'"
	    echohl None
	endif
    endif

    if oldval == 1
	let oldval = 'Yes'
    elseif oldval == 0
	let oldval = 'No'
    endif

    return oldval
endfunction

" Initial settings.
"
" Setting the default options resp. taking user preferences.
if !exists("g:EnhCommentifyUserMode")
	    \ && !exists("g:EnhCommentifyFirstLineMode")
	    \ && !exists("g:EnhCommentifyTraditionalMode")
	    \ && !exists("g:EnhCommentifyUserBindings")
    let g:EnhCommentifyTraditionalMode = 'Yes'
endif

" These will be the default settings for the script:
let s:ECaltOpen = "|+"
let s:ECaltClose = "+|"
let s:ECignoreWS = 1
let s:ECprettyComments = ''
let s:ECprettyUnComments = ''
let s:ECidentFront = ''
let s:ECuseSyntax = 0
let s:ECrespectIndent = 0
let s:ECalignRight = 0
let s:ECuseBlockIndent = 0
let s:ECuseMPBlock = 0
let s:ECuseCommentsOp = 0
let s:ECuseAltKeys = 0
let s:ECbindPerBuffer = 0
let s:ECbindInNormal = 1
let s:ECbindInInsert = 1
let s:ECbindInVisual = 1
let s:ECuserBindings = 0
let s:ECtraditionalMode = 0
let s:ECfirstLineMode = 0
let s:ECuserMode = 1
let s:ECbindUnknown = 1

" Now initialise the global defaults with the preferences set
" by the user in his .vimrc. Settings local to a buffer will be
" done later on, when the script is first called in a buffer.
"
call s:InitScriptVariables("g")

" Globally used variables with some initialisation.
" FIXME: explain what they are good for
" 
let s:Action = 'guess'
let s:firstOfBlock = 1
let s:blockAction = 'comment'
let s:blockIndentRegex = ''
let s:blockIndent = 0
let s:inBlock = 0
let s:tabConvert = ''
let s:overrideEmptyLines = 0
let s:emptyLines = 'no'
let s:maxLen = 0

function EnhancedCommentifyInitBuffer()
    if !exists("b:ECdidBufferInit")
	call s:InitScriptVariables("b")

	if !exists("b:EnhCommentifyFallbackTest")
	    let b:EnhCommentifyFallbackTest = 0
	endif

	call s:GetFileTypeSettings(&ft)
	call s:CheckPossibleEmbedding(&ft)

	"
	" If the filetype is not supported and the user wants us to, we do not
	" add keybindings.
	"
	if s:ECbindPerBuffer
	    if b:ECcommentOpen != "" || b:ECbindUnknown
		call s:SetKeybindings("l")
	    endif
	endif

	let b:ECdidBufferInit = 1
	let b:ECsyntax = &ft
    endif
endfunction

autocmd BufWinEnter,BufNewFile  *	call EnhancedCommentifyInitBuffer()

"
" EnhancedCommentify(emptyLines, action, ...)
"	overrideEL	-- commentify empty lines
"			   may be 'yes', 'no' or '' for guessing
"	action		-- action which should be executed:
"			    * guess:
"			      toggle commetification (old behaviour)
"			    * comment:
"			      comment lines
"			    * decomment:
"			      decomment lines
"			    * first:
"			      use first line of block to determine action 
"	a:1, a:2	-- first and last line of block, which should be
"			   processed. 
"
" Commentifies the current line.
"
function EnhancedCommentify(overrideEL, action, ...)
    if a:overrideEL != ''
	let s:overrideEmptyLines = 1
    endif

    " Now do the buffer initialisation. Every buffer will get
    " it's pendant to a global variable (eg. s:ECalignRight -> b:ECalignRight).
    " The local variable is actually used, whereas the global variable
    " holds the defaults from the user's .vimrc. In this way the settings
    " can be overriden for single buffers.
    " 
    " NOTE: Buffer init is done by autocommands now.
    "

    let b:ECemptyLines = a:overrideEL

    " The language is not supported.
    if b:ECcommentOpen == ''
	if (has("dialog_gui") && has("gui_running"))
	    call confirm("This filetype is currently _not_ supported!\n"
			\ ."Please consider contacting the author in order"
			\ ." to add this filetype.", "", 1, "Error")
	else
	    echohl ErrorMsg
	    echo "This filetype is currently _not_ supported!"
	    echo "Please consider contacting the author in order to add"
	    echo "this filetype in future releases!"
	    echohl None
	endif
	return
    endif

    let lnum = line(".")

    " Now some initialisations...
    let s:Action = a:action

    " FIXME: Is there really _no_ function to simplify this???
    " (Maybe something like 'let foo = 8x" "'?) 
    if s:tabConvert == '' && strlen(s:tabConvert) != &tabstop
	let s:tabConvert = ''
	let i = 0
	while i < &tabstop
	    let s:tabConvert = s:tabConvert .' '
	    let i = i + 1
	endwhile
    endif

    if a:0 == 2
	let s:startBlock = a:1
	let s:i = a:1
	let s:endBlock = a:2

	let s:inBlock = 1
    else
	let s:startBlock = lnum
	let s:i = lnum
	let s:endBlock = lnum

	let s:inBlock = 0
    endif

    if b:ECuseSyntax && b:ECpossibleEmbedding
	let column = indent(s:startBlock) + 1
	if !&expandtab
		let rem = column % &tabstop
		let column = ((column - rem) / &tabstop) + rem
	endif
	call s:CheckSyntax(s:startBlock, column)
    endif

    " Get the indent of the less indented line of the block.
    if s:inBlock && (b:ECuseBlockIndent || b:ECalignRight)
	call s:DoBlockComputations(s:startBlock, s:endBlock)
    endif

    while s:i <= s:endBlock
	let lineString = getline(s:i)
	let lineString = s:TabsToSpaces(lineString) 

	" If we should comment "empty" lines, we have to add
	" the correct indent, if we use blockIndent.
	if b:ECemptyLines =~? 'ye*s*'
			\ && b:ECuseBlockIndent
			\ && lineString =~ "^\s*$"
	    let i = 0
	    while i < s:blockIndent
		let lineString = " " . lineString
		let i = i + 1
	    endwhile
	endif

	" Don't comment empty lines.
	if lineString !~ "^\s*$"
		    \ || b:ECemptyLines =~? 'ye*s*'
	    if b:ECcommentClose != ''
		let lineString = s:CommentifyMultiPart(lineString,
			    \ b:ECcommentOpen,
			    \ b:ECcommentClose,
			    \ b:ECcommentMiddle)
	    else
	    	let lineString = s:CommentifySinglePart(lineString,
			    \ b:ECcommentOpen)
	    endif
	endif

	" Revert the above: If the line is "empty" and we
	" used blockIndent, we remove the spaces.
	" FIXME: Why does "^\s*$" not work? 
	if b:ECemptyLines =~? 'ye*s*'
		    \ && b:ECuseBlockIndent
		    \ && lineString =~ "^" . s:blockIndentRegex ."\s*$"
	    let lineString =
			\ substitute(lineString, s:blockIndentRegex,
			\     '', '')
	endif

	let lineString = s:SpacesToTabs(lineString)
	call setline(s:i, lineString)

	let s:i = s:i + 1
	let s:firstOfBlock = 0
    endwhile

    let s:firstOfBlock = 1
endfunction

"
" DoBlockComputations(start, end)
"	    start	-- number of first line
"	    end		-- number of last line
"
" This function does some computations which are necessary for useBlockIndent
" and alignRight. ie. find smallest indent and longest line.
"
function s:DoBlockComputations(start, end)
    let i = a:start
    let len = 0
    let amount = 100000	    " this should be enough ...

    while i <= a:end
	if b:ECuseBlockIndent && getline(i) !~ '^\s*$'
	    let cur = indent(i)
	    if cur < amount
		let amount = cur
	    endif
	endif

	if b:ECalignRight
	    let cur = s:GetLineLen(s:TabsToSpaces(getline(i)),
			\ s:GetLineLen(b:ECcommentOpen, 0)
			\ + strlen(b:ECprettyComments))
	    if b:ECuseMPBlock
		let cur = cur + s:GetLineLen(b:ECcommentOpen, 0)
			    \ + strlen(b:ECprettyComments)
	    endif

	    if len < cur
		let len = cur
	    endif
	endif

	let i = i + 1
    endwhile

    if b:ECuseBlockIndent
	if amount > 0
	    let regex = '\( \{'. amount .'}\)'
	else
	    let regex = ''
	endif
	let s:blockIndentRegex = regex
	let s:blockIndent = amount
    endif

    if b:ECalignRight
	let s:maxLen = len
    endif
endfunction

"
" CheckSyntax(line, column)
"	line	    -- line of line
"	column	    -- column of line
" Check what syntax is active during call of main function. First hit
" wins. If the filetype changes during the block, we ignore that.
" Adjust the filetype if necessary.
"
function s:CheckSyntax(line, column)
    let ft = ""
    let synFiletype = synIDattr(synID(a:line, a:column, 1), "name")

    " FIXME: This feature currently relies on a certain format
    " of the names of syntax items: the filetype must be prepended
    " in lowwer case letters, followed by at least one upper case
    " letter.
    if match(synFiletype, '\l\+\u') == 0
	let ft = substitute(synFiletype, '^\(\l\+\)\u.*$', '\1', "")
    endif

    if ft == ""
	execute "let specialCase = ". b:EnhCommentifyFallbackTest

	if specialCase
	    let ft = b:EnhCommentifyFallbackValue
	else
	    " Fallback: If nothing holds, use normal filetype!
	    let ft = &ft
	endif
    endif

    " Nothing changed!
    if ft == b:ECsyntax
	return
    endif

    let b:ECsyntax = ft
    call s:GetFileTypeSettings(ft)
endfunction

"
" GetFileTypeSettings(ft)
"	ft	    -- filetype
"
" This functions sets some buffer-variables, which control the comment
" strings and 'empty lines'-handling.
"
function s:GetFileTypeSettings(ft)
    let fileType = a:ft

    " If we find nothing appropriate this is the default.
    let b:ECcommentOpen = ''
    let b:ECcommentClose = ''

    if exists("g:EnhCommentifyCallbackExists")
	call EnhCommentifyCallback(fileType)

	" Check whether the callback did yield a result.
	" If so we use it. The user nows, what he's doing.
	if b:ECcommentOpen != ''
	    return
	endif
    endif

    " I learned about the commentstring option. Let's use it.
    " For now we ignore it, if it is "/*%s*/". This is the
    " default. We cannot check wether this is default or C or
    " something other like CSS, etc. We have to wait, until the
    " filetypes adopt this option.
    if &commentstring != "/*%s*/" && !b:ECuseSyntax
	let b:ECcommentOpen =
		    \ substitute(&commentstring, '%s.*', "", "")
	let b:ECcommentClose =
		    \ substitute(&commentstring, '.*%s', "", "")
    " Multipart comments:
    elseif fileType =~ '^\(c\|b\|css\|csc\|cupl\|indent\|jam\|lex\|lifelines\|'.
		\ 'lite\|nqc\|phtml\|progress\|rexx\|rpl\|sas\|sdl\|sl\|'.
		\ 'strace\|xpm\|yacc\)$'
	let b:ECcommentOpen = '/*'
	let b:ECcommentClose = '*/'
    elseif fileType =~ '^\(html\|xhtml\|xml\|xslt\|xsd\|dtd\|sgmllnx\)$'
	let b:ECcommentOpen = '<!--'
	let b:ECcommentClose = '-->'
    elseif fileType =~ '^\(sgml\|smil\)$'
	let b:ECcommentOpen = '<!'
	let b:ECcommentClose = '>'
    elseif fileType == 'atlas'
	let b:ECcommentOpen = 'C'
	let b:ECcommentClose = '$'
    elseif fileType =~ '^\(catalog\|sgmldecl\)$'
	let b:ECcommentOpen = '--'
	let b:ECcommentClose = '--'
    elseif fileType == 'dtml'
	let b:ECcommentOpen = '<dtml-comment>'
	let b:ECcommentClose = '</dtml-comment>'
    elseif fileType == 'htmlos'
	let b:ECcommentOpen = '#'
	let b:ECcommentClose = '/#'
    elseif fileType =~ '^\(jgraph\|lotos\|mma\|modula2\|modula3\|pascal\|'.
		\ 'ocaml\|sml\)$'
	let b:ECcommentOpen = '(*'
	let b:ECcommentClose = '*)'
    elseif fileType == 'jsp'
	let b:ECcommentOpen = '<%--'
	let b:ECcommentClose = '--%>'
    elseif fileType == 'model'
	let b:ECcommentOpen = '$'
	let b:ECcommentClose = '$'
    elseif fileType == 'st'
	let b:ECcommentOpen = '"'
	let b:ECcommentClose = '"'
    elseif fileType =~ '^\(tssgm\|tssop\)$'
	let b:ECcommentOpen = 'comment = "'
	let b:ECcommentClose = '"'
    " Singlepart comments:
    elseif fileType =~ '^\(ox\|cpp\|php\|java\|verilog\|acedb\|ch\|clean\|'.
		\ 'clipper\|cs\|dot\|dylan\|hercules\|idl\|ishd\|javascript\|'.
		\ 'kscript\|mel\|named\|openroad\|pccts\|pfmain\|pike\|'.
		\ 'pilrc\|plm\|pov\|rc\|scilab\|specman\|tads\|tsalt\|uc\|'.
		\ 'xkb\)$'
	let b:ECcommentOpen = '//'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(vim\|abel\)$'
	let b:ECcommentOpen = '"'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(lisp\|scheme\|scsh\|amiga\|asm\|asm68k\|bindzone\|'.
		\ 'def\|dns\|dosini\|dracula\|dsl\|idlang\|iss\|jess\|kix\|'.
		\ 'masm\|monk\|nasm\|ncf\|omnimark\|pic\|povini\|rebol\|'.
		\ 'registry\|samba\|skill\|smith\|tags\|tasm\|tf\|winbatch\|'.
		\ 'wvdial\|z8a\)$'
	let b:ECcommentOpen = ';'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(python\|perl\|[^w]*sh$\|tcl\|jproperties\|make\|'.
		\ 'robots\|apache\|apachestyle\|awk\|bc\|cfg\|cl\|conf\|'.
		\ 'crontab\|diff\|ecd\|elmfilt\|eterm\|expect\|exports\|'.
		\ 'fgl\|fvwm\|gdb\|gnuplot\|gtkrc\|hb\|hog\|ia64\|icon\|'.
		\ 'inittab\|lftp\|lilo\|lout\|lss\|lynx\|maple\|mush\|'.
		\ 'muttrc\|nsis\|ora\|pcap\|pine\|po\|procmail\|'.
		\ 'psf\|ptcap\|r\|radiance\|ratpoison\|readline\remind\|'.
		\ 'ruby\|screen\|sed\|sm\|snnsnet\|snnspat\|snnsres\|spec\|'.
		\ 'squid\|terminfo\|tidy\|tli\|tsscl\|vgrindefs\|vrml\|'.
		\ 'wget\|wml\|xf86conf\|xmath\)$'
	let b:ECcommentOpen = '#'
	let b:ECcommentClose = ''
    elseif fileType == 'webmacro'
	let b:ECcommentOpen = '##'
	let b:ECcommentClose = ''
    elseif fileType == 'ppwiz'
	let b:ECcommentOpen = ';;'
	let b:ECcommentClose = ''
    elseif fileType == 'latte'
	let b:ECcommentOpen = '\\;'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(tex\|abc\|erlang\|ist\|lprolog\|matlab\|mf\|'.
		\ 'postscr\|ppd\|prolog\|simula\|slang\|slrnrc\|slrnsc\|'.
		\ 'texmf\|viki\|virata\)$'
	let b:ECcommentOpen = '%'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(caos\|cterm\|form\|foxpro\|sicad\|snobol4\)$'
	let b:ECcommentOpen = '*'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(m4\|config\|automake\)$'
	let b:ECcommentOpen = 'dnl '
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(vb\|aspvbs\|ave\|basic\|elf\|lscript\)$'
	let b:ECcommentOpen = "'"
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(plsql\|vhdl\|ahdl\|ada\|asn\|csp\|eiffel\|gdmo\|'.
		\ 'haskell\|lace\|lua\|mib\|sather\|sql\|sqlforms\|sqlj\|'.
		\ 'stp\)$'
	let b:ECcommentOpen = '--'
	let b:ECcommentClose = ''
    elseif fileType == 'abaqus'
	let b:ECcommentOpen = '**'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(aml\|natural\|vsejcl\)$'
	let b:ECcommentOpen = '/*'
	let b:ECcommentClose = ''
    elseif fileType == 'ampl'
	let b:ECcommentOpen = '\\#'
	let b:ECcommentClose = ''
    elseif fileType == 'bdf'
	let b:ECcommentOpen = 'COMMENT '
	let b:ECcommentClose = ''
    elseif fileType == 'btm'
	let b:ECcommentOpen = '::'
	let b:ECcommentClose = ''
    elseif fileType == 'dcl'
	let b:ECcommentOpen = '$!'
	let b:ECcommentClose = ''
    elseif fileType == 'dosbatch'
	let b:ECcommentOpen = 'rem '
	let b:ECcommentClose = ''
    elseif fileType == 'focexec'
	let b:ECcommentOpen = '-*'
	let b:ECcommentClose = ''
    elseif fileType == 'forth'
	let b:ECcommentOpen = '\\ '
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(fortran\|inform\|sqr\|uil\|xdefaults\|'.
		\ 'xmodmap\|xpm2\)$'
	let b:ECcommentOpen = '!'
	let b:ECcommentClose = ''
    elseif fileType == 'gp'
	let b:ECcommentOpen = '\\\\'
	let b:ECcommentClose = ''
    elseif fileType =~ '^\(master\|nastran\|sinda\|spice\|tak\|trasys\)$'
	let b:ECcommentOpen = '$'
	let b:ECcommentClose = ''
    elseif fileType == 'nroff' || fileType == 'groff'
	let b:ECcommentOpen = ".\\\\\""
	let b:ECcommentClose = ''
    elseif fileType == 'opl'
	let b:ECcommentOpen = 'REM '
	let b:ECcommentClose = ''
    elseif fileType == 'texinfo'
	let b:ECcommentOpen = '@c '
	let b:ECcommentClose = ''
    elseif fileType == 'mail'
	let b:ECcommentOpen = '>'
	let b:ECcommentClose = ''
    endif

    if b:ECuseCommentsOp
	let b:ECcommentMiddle =
		    \ s:ParseCommentsOp(b:ECcommentOpen, b:ECcommentClose)
	if b:ECcommentMiddle == ''
	    let b:ECuseCommentsOp = 0
	endif
    else
	let b:ECcommentMiddle = ''
    endif

    if !s:overrideEmptyLines
	call s:CommentEmptyLines(fileType)
    endif
endfunction

"
" ParseCommentsOp(commentOpen, commentClose)
"	commentOpen -- comment-open string
"	commentClose-- comment-close string
"
" Try to extract the middle comment string from &comments. First hit wins.
" If nothing is found '' is returned.
"
function s:ParseCommentsOp(commentOpen, commentClose)
    let commStr = &comments
    let offset = 0
    let commentMiddle = ''

    while commStr != ''
	"
	" First decompose &omments into consecutive s-, m- and e-parts.
	"
	let s = stridx(commStr, 's')
	if s == -1
	    return ''
	endif

	let commStr = strpart(commStr, s)
	let comma = stridx(commStr, ',')
	if comma == -1
	    return ''
	endif
	let sPart = strpart(commStr, 0, comma)

	let commStr = strpart(commStr, comma)
	let m = stridx(commStr, 'm')
	if m == -1
	    return ''
	endif

	let commStr = strpart(commStr, m)
	let comma = stridx(commStr, ',')
	if comma == -1
	    return ''
	endif
	let mPart = strpart(commStr, 0, comma)

	let commStr = strpart(commStr, comma)
	let e = stridx(commStr, 'e')
	if e == -1
	    return ''
	endif

	let commStr = strpart(commStr, e)
	let comma = stridx(commStr, ',')
	if comma == -1
	    let comma = strlen(commStr)
	endif
	let ePart = strpart(commStr, 0, comma)

	let commStr = strpart(commStr, comma)

	"
	" Now check wether this is what we want:
	" Are the comment string the same?
	"
	let sColon = stridx(sPart, ':')
	let eColon = stridx(ePart, ':')
	if sColon == -1 || eColon == -1
	    return ''
	endif
	if strpart(sPart, sColon + 1) != a:commentOpen
		    \ || strpart(ePart, eColon + 1) != a:commentClose
	    continue
	endif

	let mColon = stridx(mPart, ':')
	if mColon == -1
	    return ''
	endif
	let commentMiddle = strpart(mPart, mColon + 1)

	"
	" Check for any alignement.
	"
	let i = 1
	while sPart[i] != ':'
	    if sPart[i] == 'r'
		let offset = strlen(a:commentOpen) - strlen(commentMiddle)
		break
	    elseif sPart[i] == 'l'
		let offset = 0
		break
	    elseif s:isDigit(sPart[i])
		let j = 1
		while s:isDigit(sPart[i + j])
		    let j = j + 1
		endwhile
		let offset = 1 * strpart(sPart, i, j)
		break
	    endif
	    let i = i + 1
	endwhile

	if offset == 0
	    let i = 1
	    while ePart[i] != ':'
		if ePart[i] == 'r'
		    let offset = strlen(a:commentClose) - strlen(commentMiddle)
		    break
		elseif ePart[i] == 'l'
		    let offset = 0
		    break
		elseif s:isDigit(ePart[i])
		    let j = 1
		    while s:isDigit(ePart[i + j])
			let j = j + 1
		    endwhile
		    let offset = 1 * strpart(ePart, i, j)
		    break
		endif

		let i = i + 1
	    endwhile
	endif

	while offset > 0
	    let commentMiddle = " " . commentMiddle
	    let offset = offset - 1
	endwhile

	break
    endwhile

    return commentMiddle
endfunction

"
" isDigit(char)
"
" Nomen est Omen.
"
function s:isDigit(char)
    let r = 0

    let charVal = char2nr(a:char)

    if charVal >= 48 && charVal <= 57
	let r = 1
    endif

    return r
endfunction

"
" CommentEmptyLines(ft)
"	ft	    -- filetype of current buffer
"
" Decides, if empty lines should be commentified or not. Add the filetype,
" you want to change, to the apropriate if-clause.
"
function s:CommentEmptyLines(ft)
    " FIXME: Quick hack (tm)!
    if 0
	" Add special filetypes here.
    elseif b:ECcommentClose == ''
	let b:ECemptyLines = 'yes'
    else
	let b:ECemptyLines = 'no'
    endif
endfunction

"
" CheckPossibleEmbedding(ft)
"	ft	-- the filetype of current buffer
"
" Check wether it makes sense to allow checking for the synIDs.
" Eg. C will never have embedded code...
"
function s:CheckPossibleEmbedding(ft)
    if a:ft =~ '^\(php\|vim\|latte\|html\)$'
	let b:ECpossibleEmbedding = 1
    else
	" Since getting the synID is slow, we set the default to 'no'!
	" There are also some 'broken' languages like the filetype for
	" autoconf's configure.in's ('config'). 
	let b:ECpossibleEmbedding = 0
    endif
endfunction

"
" CommentifyMultiPart(lineString, commentStart, commentEnd, action)
"	lineString	-- line to commentify
"	commentStart	-- comment-start string, eg '/*'
"	commentEnd	-- comment-end string, eg. '*/'
"	commentMiddle	-- comment-middle string, eg. ' *'	
"
" This function commentifies code of languages, which have multipart
" comment strings, eg. '/*' - '*/' of C.
"
function s:CommentifyMultiPart(lineString, commentStart,
	    \ commentEnd, commentMiddle)
    if s:Action == 'guess' || s:Action == 'first' || b:ECuseMPBlock
	let todo = s:DecideWhatToDo(a:lineString, a:commentStart, a:commentEnd)
    else
	let todo = s:Action
    endif

    if todo == 'decomment'
	return s:UnCommentify(a:lineString, a:commentStart,
		    \ a:commentEnd, a:commentMiddle)
    else
	return s:Commentify(a:lineString, a:commentStart,
		    \ a:commentEnd, a:commentMiddle)
    endif
endfunction

"
" CommentifySinglePart(lineString, commentSymbol)
"	lineString	-- line to commentify
"	commentSymbol	-- comment string, eg '#'
"
" This function is used for all languages, whose comment strings
" consist only of one string at the beginning of a line.
"
function s:CommentifySinglePart(lineString, commentSymbol)
    if s:Action == 'guess' || s:Action == 'first'
	let todo = s:DecideWhatToDo(a:lineString, a:commentSymbol)
    else
	let todo = s:Action
    endif

    if todo == 'decomment'
	return s:UnCommentify(a:lineString, a:commentSymbol)
    else
	return s:Commentify(a:lineString, a:commentSymbol)
    endif
endfunction

"
" Escape(lineString, commentStart, commentEnd)
"
" Escape already present symbols.
"
function s:Escape(lineString, commentStart, commentEnd)
    let line = a:lineString

    if b:ECaltOpen != ''
	let line = substitute(line, s:EscapeString(a:commentStart),
		    \ b:ECaltOpen, "g")
    endif
    if b:ECaltClose != ''
	let line = substitute(line, s:EscapeString(a:commentEnd),
		    \ b:ECaltClose, "g")
    endif

    return line
endfunction

"
" UnEscape(lineString, commentStart, commentEnd)
"
" Unescape already present escape symbols.
"
function s:UnEscape(lineString, commentStart, commentEnd)
    let line = a:lineString

    " We translate only the first and the last occurrence
    " of this resp. escape string. Commenting a line several
    " times and decommenting it again breaks things.
    if b:ECaltOpen != ''
	let line = substitute(line, s:EscapeString(b:ECaltOpen),
		    \ a:commentStart, "")
    endif
    if b:ECaltClose != ''
	let esAltClose = s:EscapeString(b:ECaltClose)
	let line = substitute(line, esAltClose
		    \ . "\\(.*" . esAltClose . "\\)\\@!",
		    \ a:commentEnd, "")
    endif

    return line
endfunction

"
" Commentify(lineString, commentSymbol, [commentEnd])
"       lineString	-- the line in work
"	commentSymbol	-- string to insert at the beginning of the line
"	commentEnd	-- string to insert at the end of the line
"			   may be omitted
"
" This function inserts the start- (and if given the end-) string of the
" comment in the current line.
"
function s:Commentify(lineString, commentSymbol, ...)
    let line = a:lineString
    let j = 0

    " If a end string is present, insert it too.
    if a:0 > 0
	" First we have to escape any comment already contained in the line,
	" since (at least for C) comments are not allowed to nest.
	let line = s:Escape(line, a:commentSymbol, a:1)

	if b:ECuseCommentsOp && b:ECuseMPBlock
		    \ && a:0 > 1
		    \ && s:i > s:startBlock
	    let line = substitute(line, s:LookFor('commentmiddle'),
			\ s:SubstituteWith('commentmiddle', a:2), "")
	endif

	if !b:ECuseMPBlock || (b:ECuseMPBlock && s:i == s:endBlock)
	    " Align the closing part to the right.
	    if b:ECalignRight && s:inBlock
		let len = s:GetLineLen(line, strlen(a:commentSymbol)
			    \ + strlen(b:ECprettyComments))
		while j < s:maxLen - len
		    let line = line .' '
		    let j = j + 1
		endwhile
	    endif

	    let line = substitute(line, s:LookFor('commentend'),
			\ s:SubstituteWith('commentend', a:1), "")
	endif
    endif

    " insert the comment symbol
    if !b:ECuseMPBlock || a:0 == 0 || (b:ECuseMPBlock && s:i == s:startBlock) 
	let line = substitute(line, s:LookFor('commentstart'),
		    \ s:SubstituteWith('commentstart', a:commentSymbol), "")
    endif

    return line
endfunction

"
" UnCommentify(lineString, commentSymbol, [commentEnd])
"	lineString	-- the line in work
"	commentSymbol	-- string to remove at the beginning of the line
"	commentEnd	-- string to remove at the end of the line
"			   may be omitted
"
" This function removes the start- (and if given the end-) string of the
" comment in the current line.
"
function s:UnCommentify(lineString, commentSymbol, ...)
    let line = a:lineString

    " remove the first comment symbol found on a line
    if a:0 == 0 || !b:ECuseMPBlock || (b:ECuseMPBlock && s:i == s:startBlock) 
	let line = substitute(line, s:LookFor('decommentstart',
		    \	a:commentSymbol),
		    \ s:SubstituteWith('decommentstart'), "")
    endif

    " If a end string is present, we have to remove it, too.
    if a:0 > 0
	" First, we remove the trailing comment symbol.
	if !b:ECuseMPBlock || (b:ECuseMPBlock && s:i == s:endBlock) 
	    let line = substitute(line, s:LookFor('decommentend', a:1),
			\ s:SubstituteWith('decommentend'), "")

	    " Remove any trailing whitespace, if we used alignRight.
	    if b:ECalignRight
		let line = substitute(line, ' *$', '', "")
	    endif
	endif

	" Maybe we added a middle string. Remove it here.
	if b:ECuseCommentsOp && b:ECuseMPBlock
		    \ && a:0 > 1
		    \ && s:i > s:startBlock
	    let line = substitute(line, s:LookFor('decommentmiddle', a:2),
			\ s:SubstituteWith('decommentmiddle'), "")
	endif

	" Remove escaped inner comments.
	let line = s:UnEscape(line, a:commentSymbol, a:1)
    endif

    return line
endfunction

"
" GetLineLen(line, offset)
"	line	    -- line of which length should be computed
"	offset	    -- maybe a shift of the line to the right
"
" Expands '\t' to it's tabstop value.
"
function s:GetLineLen(line, offset)
    let len = a:offset
    let i = 0

    while a:line[i] != ""
	if a:line[i] == "\t"
	    let len = (((len / &tabstop) + 1) * &tabstop)
	else
	    let len = len + 1
	endif
	let i = i + 1
    endwhile

    return len
endfunction

"
" EscapeString(string)
"	string	    -- string to process
"
" Escapes characters in 'string', which have some function in
" regular expressions, with a '\'.
"
" Returns the escaped string.
"
function s:EscapeString(string)
    return escape(a:string, "*{}[]$^-")
endfunction

"
" LookFor(what, ...)
"	what	    -- what type of regular expression
"			* checkstart:
"			* checkend:
"			  check for comment at start/end of line
"			* commentstart:
"			* commentend:
"			  insert comment strings
"			* decommentstart:
"			* decommentend:
"			  remove comment strings
"	a:1	    -- comment string
"
function s:LookFor(what, ...)
    if b:ECuseBlockIndent && s:inBlock
	let handleWhitespace = s:blockIndentRegex
    else
	let handleWhitespace = b:ECsaveWhite
    endif

    if a:what == 'checkstart'
	let regex = '^'. b:ECsaveWhite . s:EscapeString(a:1)
		    \ . s:EscapeString(b:ECidentFront)
    elseif a:what == 'checkend'
	let regex = s:EscapeString(b:ECidentBack)
		    \ . s:EscapeString(a:1) . b:ECsaveWhite . '$'
    elseif a:what == 'commentstart'
	let regex = '^'. handleWhitespace
    elseif a:what == 'commentmiddle'
	let regex = '^'. handleWhitespace
    elseif a:what == 'commentend'
	let regex = '$'
    elseif a:what == 'decommentstart'
	let regex = '^'. b:ECsaveWhite . s:EscapeString(a:1)
    		    \ . s:EscapeString(b:ECidentFront) . b:ECprettyUnComments
    elseif a:what == 'decommentmiddle'
	let regex = '^'. b:ECsaveWhite . s:EscapeString(a:1)
		    \ . s:EscapeString(b:ECidentFront) . b:ECprettyUnComments
    elseif a:what == 'decommentend'
	let regex = b:ECprettyUnComments . s:EscapeString(b:ECidentBack)
		    \ . s:EscapeString(a:1) . b:ECsaveWhite .'$'
    endif

    return regex
endfunction

"
" SubstituteWith(what, ...)
"	what	    -- what type of regular expression
"			* commentstart:
"			* commentend:
"			  insert comment strings
"			* decommentstart:
"			* decommentend:
"			  remove comment strings
"	a:1	    -- comment string
"
function s:SubstituteWith(what, ...)
    if a:what == 'commentstart'
		\ || a:what == 'commentmiddle'
		\ || a:what == 'commentend'
	let commentSymbol = a:1
    else
	let commentSymbol = ''
    endif

    if b:ECuseBlockIndent && s:inBlock
	let handleWhitespace = '\1' . commentSymbol
    else
	let handleWhitespace = b:ECrespectWhite . commentSymbol
		    \ . b:ECignoreWhite
    endif

    if a:what == 'commentstart'
	let regex = handleWhitespace . b:ECidentFront
		    \ . b:ECprettyComments
    elseif a:what == 'commentmiddle'
	let regex = handleWhitespace . b:ECidentFront
		    \ . b:ECprettyComments
    elseif a:what == 'commentend'
	let regex = b:ECprettyComments . b:ECidentBack . a:1
    elseif a:what == 'decommentstart'
		\ || a:what == 'decommentmiddle'
		\ || a:what == 'decommentend'
	let regex = handleWhitespace
    endif

    return regex
endfunction

"
"
" DecideWhatToDo(lineString, commentStart, ...)
"	lineString	-- first line of block
"	commentStart	-- comment start symbol
"	a:1		-- comment end symbol
"
function s:DecideWhatToDo(lineString, commentStart, ...)
    " If we checked already, we return our previous result.
    if !s:firstOfBlock
		\ && (s:Action == 'first'
		\	|| (b:ECuseMPBlock && s:inBlock && a:0))
	return s:blockAction
    endif

    let s:blockAction = 'comment'

    if s:inBlock && a:0 && b:ECuseMPBlock
	let first = getline(s:startBlock)
	let last = getline(s:endBlock)

	if first =~ s:LookFor('checkstart', a:commentStart)
		\ && first !~ s:LookFor('checkend', a:1)
		\ && last !~ s:LookFor('checkstart', a:commentStart)
		\ && last =~ s:LookFor('checkend', a:1)
	    let s:blockAction = 'decomment'
	endif

	return s:blockAction
    endif

    if a:lineString =~ s:LookFor('checkstart', a:commentStart)
	let s:blockAction = 'decomment'
    endif

    if a:0
	if a:lineString !~ s:LookFor('checkend', a:1)
	    let s:blockAction = 'comment'
	endif
    endif

    let s:firstOfBlock = 0
    return s:blockAction
endfunction

"
" TabsToSpaces(str)
"	str	    -- string to convert
"
" Convert leading tabs of given string to spaces.
"
function s:TabsToSpaces(str)
    let string = a:str

    " FIXME: Can we use something like retab? I don't think so,
    " because retab changes every whitespace in the line, but we
    " wan't to modify only the leading spaces. Is this a problem?
    while string =~ '^\( *\)\t'
	let string = substitute(string, '^\( *\)\t', '\1'. s:tabConvert, "")
    endwhile

    return string
endfunction

"
" SpacesToTabs(str)
"	str	    -- string to convert
"
" Convert leading spaces of given string to tabs.
"
function s:SpacesToTabs(str)
    let string = a:str

    if !&expandtab
	while string =~ '^\(\t*\)'. s:tabConvert
	    let string = substitute(string, '^\(\t*\)'. s:tabConvert,
			\ '\1\t', "")
	endwhile
    endif

    return string
endfunction

"
" EnhCommentifyFallback4Embedded(test, fallback)
"	test	    -- test for the special case
"	fallback    -- filetype instead of normal fallback
"
" This function is global. It should be called from filetype
" plugins like php, where the normal fallback behaviour may
" not work. One may use 'synFiletype' to reference the guessed
" filetype via synID.
"
function EnhCommentifyFallback4Embedded(test, fallback)
    let b:EnhCommentifyFallbackTest = a:test
    let b:EnhCommentifyFallbackValue = a:fallback
endfunction

"
" Keyboard mappings.
"
noremap <Plug>Comment
	    \ :call EnhancedCommentify('', 'comment')<CR>
noremap <Plug>DeComment
	    \ :call EnhancedCommentify('', 'decomment')<CR>
noremap <Plug>Traditional
	    \ :call EnhancedCommentify('', 'guess')<CR>
noremap <Plug>FirstLine
	    \ :call EnhancedCommentify('', 'first')<CR>

noremap <Plug>VisualComment
	    \ <Esc>:call EnhancedCommentify('', 'comment',
	    \   line("'<"), line("'>"))<CR>
noremap <Plug>VisualDeComment
	    \ <Esc>:call EnhancedCommentify('', 'decomment',
	    \   line("'<"), line("'>"))<CR>
noremap <Plug>VisualTraditional
	    \ <Esc>:call EnhancedCommentify('', 'guess',
	    \   line("'<"), line("'>"))<CR>
noremap <Plug>VisualFirstLine
	    \ <Esc>:call EnhancedCommentify('', 'first',
	    \   line("'<"), line("'>"))<CR>
"
" Finally set keybindings.
"
" SetKeybindings(where)
"	where	    -- "l" for local to the buffer, "g" for global
"
function s:SetKeybindings(where)
    if a:where == "l"
	let where = "<buffer>"
	let ns = "b"
    else
	let where = ""
	let ns = "s"
    endif

    execute "let userBindings = ". ns .":ECuserBindings"
    execute "let useAltKeys = ". ns .":ECuseAltKeys"
    execute "let traditionalMode = ". ns .":ECtraditionalMode"
    execute "let firstLineMode = ". ns .":ECfirstLineMode"
    execute "let bindInNormal = ". ns .":ECbindInNormal"
    execute "let bindInInsert = ". ns .":ECbindInInsert"
    execute "let bindInVisual = ". ns .":ECbindInVisual"

    if userBindings
	"
	" *** Put your personal bindings here! ***
	"
    else
	if useAltKeys
	    let s:c = '<M-c>'
	    let s:x = '<M-x>'
	    let s:C = '<M-v>'
	    let s:X = '<M-y>'
	else
	    let s:c = '<Leader>c'
	    let s:x = '<Leader>x'
	    let s:C = '<Leader>C'
	    let s:X = '<Leader>X'
	endif

	if traditionalMode
	    let s:Method = 'Traditional'
	elseif firstLineMode
	    let s:Method = 'FirstLine'
	else
	    let s:Method = 'Comment'

	    " Decomment must be defined here. Everything else is mapped below.
	    if bindInNormal 
		execute 'nmap '. where .' <silent> <unique> '. s:C
			    \ .' <Plug>DeCommentj'
		execute 'nmap '. where .' <silent> <unique> '. s:X
			    \ .' <Plug>DeComment'
	    endif

	    if bindInInsert
		execute 'imap '. where .' <silent> <unique> '. s:C
			    \ .' <Esc><Plug>DeCommentji'
		execute 'imap '. where .' <silent> <unique> '. s:X
			    \ .' <Esc><Plug>DeCommenti'
	    endif

	    if bindInVisual
		execute 'vmap '. where .' <silent> <unique> '. s:C
			    \ .' <Plug>VisualDeCommentj'
		execute 'vmap '. where .' <silent> <unique> '. s:X
			    \ .' <Plug>VisualDeComment'
	    endif
	endif

	if bindInNormal
	    execute 'nmap '. where .' <silent> <unique> '. s:c
			\ .' <Plug>'. s:Method .'j'
	    execute 'nmap '. where .' <silent> <unique> '. s:x
			\ .' <Plug>'. s:Method
	endif

	if bindInInsert
	    execute 'imap '. where .' <silent> <unique> '. s:c
			\ .' <Esc><Plug>'. s:Method .'ji'
	    execute 'imap '. where .' <silent> <unique> '. s:x
			\ .' <Esc><Plug>'. s:Method
	endif

	if bindInVisual
	    execute 'vmap <silent> <unique> '. s:c
			\ .' <Plug>Visual'. s:Method .'j'
	    execute 'vmap '. where .' <silent> <unique> '. s:x
			\ .' <Plug>Visual'. s:Method
	endif
    endif
endfunction

if !s:ECbindPerBuffer
    call s:SetKeybindings("g")
endif

let &cpo = s:savedCpo

" vim: set sts=4 sw=4 ts=8 :
