From da709cd9e90c3ab644bb92700f0ed40d965b80d3 Mon Sep 17 00:00:00 2001 From: Anton Bobov Date: Thu, 13 Oct 2011 11:17:19 +0600 Subject: Initial commit. --- files/.vim/plugin/NERD_commenter.vim | 3349 ++++++++++++++++++++++++ files/.vim/plugin/NERD_tree.vim | 3559 ++++++++++++++++++++++++++ files/.vim/plugin/SyntaxFolds.vim | 323 +++ files/.vim/plugin/filebrowser.vim | 251 ++ files/.vim/plugin/imaps.vim | 831 ++++++ files/.vim/plugin/libList.vim | 249 ++ files/.vim/plugin/luarefvim.vim | 30 + files/.vim/plugin/matchit.vim | 812 ++++++ files/.vim/plugin/project.vim | 1293 ++++++++++ files/.vim/plugin/rails.vim | 4666 ++++++++++++++++++++++++++++++++++ files/.vim/plugin/remoteOpen.vim | 163 ++ files/.vim/plugin/snipMate.vim | 190 ++ files/.vim/plugin/sqlplus.vim | 257 ++ files/.vim/plugin/taglist.vim | 4546 +++++++++++++++++++++++++++++++++ 14 files changed, 20519 insertions(+) create mode 100755 files/.vim/plugin/NERD_commenter.vim create mode 100755 files/.vim/plugin/NERD_tree.vim create mode 100755 files/.vim/plugin/SyntaxFolds.vim create mode 100755 files/.vim/plugin/filebrowser.vim create mode 100755 files/.vim/plugin/imaps.vim create mode 100755 files/.vim/plugin/libList.vim create mode 100755 files/.vim/plugin/luarefvim.vim create mode 100755 files/.vim/plugin/matchit.vim create mode 100755 files/.vim/plugin/project.vim create mode 100755 files/.vim/plugin/rails.vim create mode 100755 files/.vim/plugin/remoteOpen.vim create mode 100755 files/.vim/plugin/snipMate.vim create mode 100755 files/.vim/plugin/sqlplus.vim create mode 100755 files/.vim/plugin/taglist.vim (limited to 'files/.vim/plugin') diff --git a/files/.vim/plugin/NERD_commenter.vim b/files/.vim/plugin/NERD_commenter.vim new file mode 100755 index 0000000..155f5ac --- /dev/null +++ b/files/.vim/plugin/NERD_commenter.vim @@ -0,0 +1,3349 @@ +" ============================================================================ +" File: NERD_commenter.vim +" Description: vim global plugin that provides easy code commenting +" Maintainer: Martin Grenfell +" Version: 2.2.1 +" Last Change: 13th November, 2008 +" License: This program is free software. It comes without any warranty, +" to the extent permitted by applicable law. You can redistribute +" it and/or modify it under the terms of the Do What The Fuck You +" Want To Public License, Version 2, as published by Sam Hocevar. +" See http://sam.zoy.org/wtfpl/COPYING for more details. +" +" ============================================================================ + +" Section: script init stuff {{{1 +if exists("loaded_nerd_comments") + finish +endif +if v:version < 700 + echoerr "NERDCommenter: this plugin requires vim >= 7. DOWNLOAD IT! You'll thank me later!" + finish +endif +let loaded_nerd_comments = 1 + +" Section: spaces init {{{2 +" Occasionally we need to grab a string of spaces so just make one here +let s:spaces = "" +while strlen(s:spaces) < 100 + let s:spaces = s:spaces . " " +endwhile + +" Function: s:InitVariable() function {{{2 +" This function is used to initialise a given variable to a given value. The +" variable is only initialised if it does not exist prior +" +" Args: +" -var: the name of the var to be initialised +" -value: the value to initialise var to +" +" Returns: +" 1 if the var is set, 0 otherwise +function s:InitVariable(var, value) + if !exists(a:var) + exec 'let ' . a:var . ' = ' . "'" . a:value . "'" + return 1 + endif + return 0 +endfunction + +" Section: space string init{{{2 +" When putting spaces after the left delim and before the right we use +" s:spaceStr for the space char. This way we can make it add anything after +" the left and before the right by modifying this variable +let s:spaceStr = ' ' +let s:lenSpaceStr = strlen(s:spaceStr) + +" Section: variable init calls {{{2 +call s:InitVariable("g:NERDAllowAnyVisualDelims", 1) +call s:InitVariable("g:NERDBlockComIgnoreEmpty", 0) +call s:InitVariable("g:NERDCommentWholeLinesInVMode", 0) +call s:InitVariable("g:NERDCompactSexyComs", 0) +call s:InitVariable("g:NERDCreateDefaultMappings", 1) +call s:InitVariable("g:NERDDefaultNesting", 1) +call s:InitVariable("g:NERDMenuMode", 3) +call s:InitVariable("g:NERDLPlace", "[>") +call s:InitVariable("g:NERDUsePlaceHolders", 1) +call s:InitVariable("g:NERDRemoveAltComs", 1) +call s:InitVariable("g:NERDRemoveExtraSpaces", 1) +call s:InitVariable("g:NERDRPlace", "<]") +call s:InitVariable("g:NERDShutUp", '0') +call s:InitVariable("g:NERDSpaceDelims", 0) +call s:InitVariable("g:NERDDelimiterRequests", 1) + + + +let s:NERDFileNameEscape="[]#*$%'\" ?`!&();<>\\" + +" Section: Comment mapping functions, autocommands and commands {{{1 +" ============================================================================ +" Section: Comment enabler autocommands {{{2 +" ============================================================================ + +augroup commentEnablers + + "if the user enters a buffer or reads a buffer then we gotta set up + "the comment delimiters for that new filetype + autocmd BufEnter,BufRead * :call s:SetUpForNewFiletype(&filetype, 0) + + "if the filetype of a buffer changes, force the script to reset the + "delims for the buffer + autocmd Filetype * :call s:SetUpForNewFiletype(&filetype, 1) +augroup END + + +" Function: s:SetUpForNewFiletype(filetype) function {{{2 +" This function is responsible for setting up buffer scoped variables for the +" given filetype. +" +" These variables include the comment delimiters for the given filetype and calls +" MapDelimiters or MapDelimitersWithAlternative passing in these delimiters. +" +" Args: +" -filetype: the filetype to set delimiters for +" -forceReset: 1 if the delimiters should be reset if they have already be +" set for this buffer. +" +function s:SetUpForNewFiletype(filetype, forceReset) + "if we have already set the delimiters for this buffer then dont go thru + "it again + if !a:forceReset && exists("b:left") && b:left != '' + return + endif + + let b:sexyComMarker = '' + + "check the filetype against all known filetypes to see if we have + "hardcoded the comment delimiters to use + if a:filetype == "" + call s:MapDelimiters('', '') + elseif a:filetype == "aap" + call s:MapDelimiters('#', '') + elseif a:filetype == "abaqus" + call s:MapDelimiters('**', '') + elseif a:filetype == "abc" + call s:MapDelimiters('%', '') + elseif a:filetype == "acedb" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "actionscript" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "ada" + call s:MapDelimitersWithAlternative('--','', '-- ', '') + elseif a:filetype == "ahdl" + call s:MapDelimiters('--', '') + elseif a:filetype == "ahk" + call s:MapDelimitersWithAlternative(';', '', '/*', '*/') + elseif a:filetype == "amiga" + call s:MapDelimiters(';', '') + elseif a:filetype == "aml" + call s:MapDelimiters('/*', '') + elseif a:filetype == "ampl" + call s:MapDelimiters('#', '') + elseif a:filetype == "ant" + call s:MapDelimiters('') + elseif a:filetype == "apache" + call s:MapDelimiters('#', '') + elseif a:filetype == "apachestyle" + call s:MapDelimiters('#', '') + elseif a:filetype == "asciidoc" + call s:MapDelimiters('//', '') + elseif a:filetype == "applescript" + call s:MapDelimitersWithAlternative('--', '', '(*', '*)') + elseif a:filetype == "asm68k" + call s:MapDelimiters(';', '') + elseif a:filetype == "asm" + call s:MapDelimitersWithAlternative(';', '', '#', '') + elseif a:filetype == "asn" + call s:MapDelimiters('--', '') + elseif a:filetype == "aspvbs" + call s:MapDelimiters('''', '') + elseif a:filetype == "asterisk" + call s:MapDelimiters(';', '') + elseif a:filetype == "asy" + call s:MapDelimiters('//', '') + elseif a:filetype == "atlas" + call s:MapDelimiters('C','$') + elseif a:filetype == "autohotkey" + call s:MapDelimiters(';','') + elseif a:filetype == "autoit" + call s:MapDelimiters(';','') + elseif a:filetype == "automake" + call s:MapDelimiters('##','') + elseif a:filetype == "ave" + call s:MapDelimiters("'",'') + elseif a:filetype == "awk" + call s:MapDelimiters('#','') + elseif a:filetype == "basic" + call s:MapDelimitersWithAlternative("'",'', 'REM ', '') + elseif a:filetype == "b" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "bbx" + call s:MapDelimiters('%', '') + elseif a:filetype == "bc" + call s:MapDelimiters('#', '') + elseif a:filetype == "bdf" + call s:MapDelimiters('COMMENT ', '') + elseif a:filetype == "bib" + call s:MapDelimiters('%','') + elseif a:filetype == "bindzone" + call s:MapDelimiters(';', '') + elseif a:filetype == "bst" + call s:MapDelimiters('%', '') + elseif a:filetype == "btm" + call s:MapDelimiters('::', '') + elseif a:filetype == "bzr" + call s:MapDelimiters('', '') + elseif a:filetype == "caos" + call s:MapDelimiters('*', '') + elseif a:filetype == "calibre" + call s:MapDelimiters('//','') + elseif a:filetype == "catalog" + call s:MapDelimiters('--','--') + elseif a:filetype == "c" + call s:MapDelimitersWithAlternative('/*','*/', '//', '') + elseif a:filetype == "cfg" + call s:MapDelimiters('#', '') + elseif a:filetype == "cg" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "ch" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "changelog" + call s:MapDelimiters('','') + elseif a:filetype == "cl" + call s:MapDelimiters('#', '') + elseif a:filetype == "clean" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "clipper" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "clojure" + call s:MapDelimiters(';', '') + elseif a:filetype == "cmake" + call s:MapDelimiters('#','') + elseif a:filetype == "cobol" + call s:MapDelimiters('', '') + elseif a:filetype == "conf" + call s:MapDelimiters('#', '') + elseif a:filetype == "config" + call s:MapDelimiters('dnl ', '') + elseif a:filetype == "conkyrc" + call s:MapDelimiters('#', '') + elseif a:filetype == "context" + call s:MapDelimiters('%','') + elseif a:filetype == "cpp" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "crontab" + call s:MapDelimiters('#', '') + elseif a:filetype == "cs" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "csc" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "csp" + call s:MapDelimiters('--', '') + elseif a:filetype == "css" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "cterm" + call s:MapDelimiters('*', '') + elseif a:filetype == "cupl" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "csv" + call s:MapDelimiters('','') + elseif a:filetype == "cvs" + call s:MapDelimiters('CVS:','') + elseif a:filetype == "CVSAnnotate" + call s:MapDelimiters('','') + elseif a:filetype == "CVScommit" + call s:MapDelimiters('','') + elseif a:filetype == "d" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "dcl" + call s:MapDelimiters('$!', '') + elseif a:filetype == "dakota" + call s:MapDelimiters('#', '') + elseif a:filetype == "debchangelog" + call s:MapDelimiters('', '') + elseif a:filetype == "debcontrol" + call s:MapDelimiters('#', '') + elseif a:filetype == "debsources" + call s:MapDelimiters('#', '') + elseif a:filetype == "def" + call s:MapDelimiters(';', '') + elseif a:filetype == "desktop" + call s:MapDelimiters('#', '') + elseif a:filetype == "diff" + call s:MapDelimiters('#', '') + elseif a:filetype == "django" + call s:MapDelimitersWithAlternative('', '{#', '#}') + elseif a:filetype == "docbk" + call s:MapDelimiters('') + elseif a:filetype == "dns" + call s:MapDelimiters(';', '') + elseif a:filetype == "dosbatch" + call s:MapDelimitersWithAlternative('REM ','', '::', '') + elseif a:filetype == "dosini" + call s:MapDelimiters(';', '') + elseif a:filetype == "dot" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "dracula" + call s:MapDelimiters(';', '') + elseif a:filetype == "dsl" + call s:MapDelimiters(';', '') + elseif a:filetype == "dtd" + call s:MapDelimiters('') + elseif a:filetype == "dtml" + call s:MapDelimiters('','') + elseif a:filetype == "dtrace" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "dylan" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == 'ebuild' + call s:MapDelimiters('#', '') + elseif a:filetype == "ecd" + call s:MapDelimiters('#', '') + elseif a:filetype == 'eclass' + call s:MapDelimiters('#', '') + elseif a:filetype == "eiffel" + call s:MapDelimiters('--', '') + elseif a:filetype == "elf" + call s:MapDelimiters("'", '') + elseif a:filetype == "elmfilt" + call s:MapDelimiters('#', '') + elseif a:filetype == "erlang" + call s:MapDelimiters('%', '') + elseif a:filetype == "eruby" + call s:MapDelimitersWithAlternative('', '<%#', '%>') + elseif a:filetype == "eterm" + call s:MapDelimiters('#', '') + elseif a:filetype == "expect" + call s:MapDelimiters('#', '') + elseif a:filetype == "exports" + call s:MapDelimiters('#', '') + elseif a:filetype == "factor" + call s:MapDelimitersWithAlternative('! ', '', '!# ', '') + elseif a:filetype == "fetchmail" + call s:MapDelimiters('#', '') + elseif a:filetype == "fgl" + call s:MapDelimiters('#', '') + elseif a:filetype == "focexec" + call s:MapDelimiters('-*', '') + elseif a:filetype == "form" + call s:MapDelimiters('*', '') + elseif a:filetype == "fortran" + call s:MapDelimiters('!', '') + elseif a:filetype == "foxpro" + call s:MapDelimiters('*', '') + elseif a:filetype == "fstab" + call s:MapDelimiters('#', '') + elseif a:filetype == "fvwm" + call s:MapDelimiters('#', '') + elseif a:filetype == "fx" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "gams" + call s:MapDelimiters('*', '') + elseif a:filetype == "gdb" + call s:MapDelimiters('#', '') + elseif a:filetype == "gdmo" + call s:MapDelimiters('--', '') + elseif a:filetype == "geek" + call s:MapDelimiters('GEEK_COMMENT:', '') + elseif a:filetype == "genshi" + call s:MapDelimitersWithAlternative('', '{#', '#}') + elseif a:filetype == "gentoo-conf-d" + call s:MapDelimiters('#', '') + elseif a:filetype == "gentoo-env-d" + call s:MapDelimiters('#', '') + elseif a:filetype == "gentoo-init-d" + call s:MapDelimiters('#', '') + elseif a:filetype == "gentoo-make-conf" + call s:MapDelimiters('#', '') + elseif a:filetype == 'gentoo-package-keywords' + call s:MapDelimiters('#', '') + elseif a:filetype == 'gentoo-package-mask' + call s:MapDelimiters('#', '') + elseif a:filetype == 'gentoo-package-use' + call s:MapDelimiters('#', '') + elseif a:filetype == 'git' + call s:MapDelimiters('', '') + elseif a:filetype == 'gitAnnotate' + call s:MapDelimiters('', '') + elseif a:filetype == 'gitcommit' + call s:MapDelimiters('#', '') + elseif a:filetype == 'gitconfig' + call s:MapDelimiters(';', '') + elseif a:filetype == 'gitdiff' + call s:MapDelimiters('', '') + elseif a:filetype == 'gitrebase' + call s:MapDelimiters('#', '') + elseif a:filetype == "gnuplot" + call s:MapDelimiters('#','') + elseif a:filetype == "groovy" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "group" + call s:MapDelimiters('','') + elseif a:filetype == "grub" + call s:MapDelimiters('#', '') + elseif a:filetype == "gtkrc" + call s:MapDelimiters('#', '') + elseif a:filetype == "haskell" + call s:MapDelimitersWithAlternative('{-','-}', '--', '') + elseif a:filetype == "hb" + call s:MapDelimiters('#', '') + elseif a:filetype == "h" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "haml" + call s:MapDelimitersWithAlternative('-#', '', '/', '') + elseif a:filetype == "help" + call s:MapDelimiters('"','') + elseif a:filetype == "hercules" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "hog" + call s:MapDelimiters('#', '') + elseif a:filetype == "hostsaccess" + call s:MapDelimiters('#', '') + elseif a:filetype == "html" + call s:MapDelimitersWithAlternative('', '//', '') + elseif a:filetype == "htmldjango" + call s:MapDelimitersWithAlternative('', '{#', '#}') + elseif a:filetype == "htmlos" + call s:MapDelimiters('#','/#') + elseif a:filetype == "ia64" + call s:MapDelimiters('#', '') + elseif a:filetype == "icon" + call s:MapDelimiters('#', '') + elseif a:filetype == "idlang" + call s:MapDelimiters(';', '') + elseif a:filetype == "idl" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "indent" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "inform" + call s:MapDelimiters('!', '') + elseif a:filetype == "inittab" + call s:MapDelimiters('#', '') + elseif a:filetype == "ishd" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "iss" + call s:MapDelimiters(';', '') + elseif a:filetype == "ist" + call s:MapDelimiters('%', '') + elseif a:filetype == "jam" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "java" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "javascript" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "jess" + call s:MapDelimiters(';', '') + elseif a:filetype == "jgraph" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "jproperties" + call s:MapDelimiters('#','') + elseif a:filetype == "jsp" + call s:MapDelimiters('<%--', '--%>') + elseif a:filetype == "kconfig" + call s:MapDelimiters('#', '') + elseif a:filetype == "kix" + call s:MapDelimiters(';', '') + elseif a:filetype == "kscript" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "lace" + call s:MapDelimiters('--', '') + elseif a:filetype == "ldif" + call s:MapDelimiters('#', '') + elseif a:filetype == "lex" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "lftp" + call s:MapDelimiters('#', '') + elseif a:filetype == "lhaskell" + call s:MapDelimiters('','') + elseif a:filetype == "lifelines" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "lilo" + call s:MapDelimiters('#', '') + elseif a:filetype == "lilypond" + call s:MapDelimiters('%', '') + elseif a:filetype == "liquid" + call s:MapDelimiters('{%', '%}') + elseif a:filetype == "lisp" + call s:MapDelimitersWithAlternative(';','', '#|', '|#') + elseif a:filetype == "lite" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "llvm" + call s:MapDelimiters(';','') + elseif a:filetype == "lookupfile" + call s:MapDelimiters('', '') + elseif a:filetype == "lotos" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "lout" + call s:MapDelimiters('#', '') + elseif a:filetype == "lprolog" + call s:MapDelimiters('%', '') + elseif a:filetype == "lscript" + call s:MapDelimiters("'", '') + elseif a:filetype == "lss" + call s:MapDelimiters('#', '') + elseif a:filetype == "lua" + call s:MapDelimitersWithAlternative('--','', '--[[', ']]') + elseif a:filetype == "lynx" + call s:MapDelimiters('#', '') + elseif a:filetype == "lytex" + call s:MapDelimiters('%', '') + elseif a:filetype == "m4" + call s:MapDelimiters('dnl ', '') + elseif a:filetype == "mail" + call s:MapDelimiters('> ','') + elseif a:filetype == "mailcap" + call s:MapDelimiters('#','') + elseif a:filetype == "make" + call s:MapDelimiters('#','') + elseif a:filetype == "mako" + call s:MapDelimiters('##', '') + elseif a:filetype == "man" + call s:MapDelimiters('."', '') + elseif a:filetype == "map" + call s:MapDelimiters('%', '') + elseif a:filetype == "maple" + call s:MapDelimiters('#', '') + elseif a:filetype == "markdown" + call s:MapDelimiters('') + elseif a:filetype == "masm" + call s:MapDelimiters(';', '') + elseif a:filetype == "mason" + call s:MapDelimiters('<% #', '%>') + elseif a:filetype == "master" + call s:MapDelimiters('$', '') + elseif a:filetype == "matlab" + call s:MapDelimiters('%', '') + elseif a:filetype == "mel" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "mf" + call s:MapDelimiters('%', '') + elseif a:filetype == "mib" + call s:MapDelimiters('--', '') + elseif a:filetype == "mkd" + call s:MapDelimiters('>', '') + elseif a:filetype == "mma" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "modconf" + call s:MapDelimiters('#', '') + elseif a:filetype == "model" + call s:MapDelimiters('$','$') + elseif a:filetype =~ "moduala." + call s:MapDelimiters('(*','*)') + elseif a:filetype == "modula2" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "modula3" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "monk" + call s:MapDelimiters(';', '') + elseif a:filetype == "mplayerconf" + call s:MapDelimiters('#', '') + elseif a:filetype == "mrxvtrc" + call s:MapDelimiters('#', '') + elseif a:filetype == "mush" + call s:MapDelimiters('#', '') + elseif a:filetype == "muttrc" + call s:MapDelimiters('#', '') + elseif a:filetype == "named" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "nasm" + call s:MapDelimiters(';', '') + elseif a:filetype == "nastran" + call s:MapDelimiters('$', '') + elseif a:filetype == "natural" + call s:MapDelimiters('/*', '') + elseif a:filetype == "ncf" + call s:MapDelimiters(';', '') + elseif a:filetype == "nerdtree" + call s:MapDelimiters('', '') + elseif a:filetype == "netdict" + call s:MapDelimiters('', '') + elseif a:filetype == "netrw" + call s:MapDelimiters('', '') + elseif a:filetype == "newlisp" + call s:MapDelimiters(';','') + elseif a:filetype == "nqc" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "nroff" + call s:MapDelimiters('\"', '') + elseif a:filetype == "nsis" + call s:MapDelimiters('#', '') + elseif a:filetype == "objc" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "objcpp" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "objj" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "ocaml" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "occam" + call s:MapDelimiters('--','') + elseif a:filetype == "omlet" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "omnimark" + call s:MapDelimiters(';', '') + elseif a:filetype == "openroad" + call s:MapDelimiters('//', '') + elseif a:filetype == "opl" + call s:MapDelimiters("REM", "") + elseif a:filetype == "ora" + call s:MapDelimiters('#', '') + elseif a:filetype == "otl" + call s:MapDelimiters('', '') + elseif a:filetype == "ox" + call s:MapDelimiters('//', '') + elseif a:filetype == "pamconf" + call s:MapDelimiters('#', '') + elseif a:filetype == "pascal" + call s:MapDelimitersWithAlternative('{','}', '(*', '*)') + elseif a:filetype == "passwd" + call s:MapDelimiters('','') + elseif a:filetype == "patran" + call s:MapDelimitersWithAlternative('$','','/*', '*/') + elseif a:filetype == "pcap" + call s:MapDelimiters('#', '') + elseif a:filetype == "pccts" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "perl" + call s:MapDelimiters('#', '') + elseif a:filetype == "pfmain" + call s:MapDelimiters('//', '') + elseif a:filetype == "php" + call s:MapDelimitersWithAlternative('//','','/*', '*/') + elseif a:filetype == "phtml" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "pic" + call s:MapDelimiters(';', '') + elseif a:filetype == "pike" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "pilrc" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "pine" + call s:MapDelimiters('#', '') + elseif a:filetype == "plaintex" + call s:MapDelimiters('%','') + elseif a:filetype == "plm" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "plsql" + call s:MapDelimitersWithAlternative('--', '', '/*', '*/') + elseif a:filetype == "po" + call s:MapDelimiters('#', '') + elseif a:filetype == "postscr" + call s:MapDelimiters('%', '') + elseif a:filetype == "potwiki" + call s:MapDelimiters('', '') + elseif a:filetype == "pov" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "povini" + call s:MapDelimiters(';', '') + elseif a:filetype == "ppd" + call s:MapDelimiters('%', '') + elseif a:filetype == "ppwiz" + call s:MapDelimiters(';;', '') + elseif a:filetype == "processing" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "procmail" + call s:MapDelimiters('#', '') + elseif a:filetype == "progress" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "prolog" + call s:MapDelimitersWithAlternative('%','','/*','*/') + elseif a:filetype == "ps1" + call s:MapDelimiters('#', '') + elseif a:filetype == "psf" + call s:MapDelimiters('#', '') + elseif a:filetype == "ptcap" + call s:MapDelimiters('#', '') + elseif a:filetype == "pyrex" + call s:MapDelimiters('#','') + elseif a:filetype == "python" + call s:MapDelimiters('#','') + elseif a:filetype == "qf" + call s:MapDelimiters('','') + elseif a:filetype == "radiance" + call s:MapDelimiters('#', '') + elseif a:filetype == "Rails-log" + call s:MapDelimiters('', '') + elseif a:filetype == "ratpoison" + call s:MapDelimiters('#', '') + elseif a:filetype == "r" + call s:MapDelimiters('#', '') + elseif a:filetype == "rc" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "readline" + call s:MapDelimiters('#', '') + elseif a:filetype == "rebol" + call s:MapDelimiters(';', '') + elseif a:filetype == "registry" + call s:MapDelimiters(';', '') + elseif a:filetype == "remind" + call s:MapDelimiters('#', '') + elseif a:filetype == "rexx" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "rib" + call s:MapDelimiters('#','') + elseif a:filetype == "robots" + call s:MapDelimiters('#', '') + elseif a:filetype == "rpl" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "rst" + call s:MapDelimiters('..', '') + elseif a:filetype == "rtf" + call s:MapDelimiters('', '') + elseif a:filetype == "ruby" + call s:MapDelimiters('#','') + elseif a:filetype == "sa" + call s:MapDelimiters('--','') + elseif a:filetype == "samba" + call s:MapDelimitersWithAlternative(';','', '#', '') + elseif a:filetype == "sas" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "sass" + call s:MapDelimitersWithAlternative('//','', '/*', '') + elseif a:filetype == "sather" + call s:MapDelimiters('--', '') + elseif a:filetype == "scala" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "scheme" + call s:MapDelimiters(';', '') + elseif a:filetype == "scilab" + call s:MapDelimiters('//', '') + elseif a:filetype == "screen" + call s:MapDelimiters('#', '') + elseif a:filetype == "scsh" + call s:MapDelimiters(';', '') + elseif a:filetype == "sdl" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "sed" + call s:MapDelimiters('#', '') + elseif a:filetype == "selectbuf" + call s:MapDelimiters('', '') + elseif a:filetype == "services" + call s:MapDelimiters('#', '') + elseif a:filetype == "sgml" + call s:MapDelimiters('') + elseif a:filetype == "sgmldecl" + call s:MapDelimiters('--','--') + elseif a:filetype == "sgmllnx" + call s:MapDelimiters('') + elseif a:filetype == "sicad" + call s:MapDelimiters('*', '') + elseif a:filetype == "sieve" + call s:MapDelimiters('#', '') + elseif a:filetype == "simula" + call s:MapDelimitersWithAlternative('%', '', '--', '') + elseif a:filetype == "sinda" + call s:MapDelimiters('$', '') + elseif a:filetype == "skill" + call s:MapDelimiters(';', '') + elseif a:filetype == "slang" + call s:MapDelimiters('%', '') + elseif a:filetype == "sl" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "slice" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "slrnrc" + call s:MapDelimiters('%', '') + elseif a:filetype == "sm" + call s:MapDelimiters('#', '') + elseif a:filetype == "smarty" + call s:MapDelimiters('{*', '*}') + elseif a:filetype == "smil" + call s:MapDelimiters('') + elseif a:filetype == "smith" + call s:MapDelimiters(';', '') + elseif a:filetype == "sml" + call s:MapDelimiters('(*','*)') + elseif a:filetype == "snnsnet" + call s:MapDelimiters('#', '') + elseif a:filetype == "snnspat" + call s:MapDelimiters('#', '') + elseif a:filetype == "snnsres" + call s:MapDelimiters('#', '') + elseif a:filetype == "snobol4" + call s:MapDelimiters('*', '') + elseif a:filetype == "spec" + call s:MapDelimiters('#', '') + elseif a:filetype == "specman" + call s:MapDelimiters('//', '') + elseif a:filetype == "spectre" + call s:MapDelimitersWithAlternative('//', '', '*', '') + elseif a:filetype == "spice" + call s:MapDelimiters('$', '') + elseif a:filetype == "sql" + call s:MapDelimiters('--', '') + elseif a:filetype == "sqlforms" + call s:MapDelimiters('--', '') + elseif a:filetype == "sqlj" + call s:MapDelimiters('--', '') + elseif a:filetype == "sqr" + call s:MapDelimiters('!', '') + elseif a:filetype == "squid" + call s:MapDelimiters('#', '') + elseif a:filetype == "st" + call s:MapDelimiters('"','') + elseif a:filetype == "stata" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "stp" + call s:MapDelimiters('--', '') + elseif a:filetype == "strace" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "sudoers" + call s:MapDelimiters('#', '') + elseif a:filetype == "SVKAnnotate" + call s:MapDelimiters('','') + elseif a:filetype == "svn" + call s:MapDelimiters('','') + elseif a:filetype == "SVNannotate" + call s:MapDelimiters('','') + elseif a:filetype == "SVNAnnotate" + call s:MapDelimiters('','') + elseif a:filetype == "SVNcommit" + call s:MapDelimiters('','') + elseif a:filetype == "SVNcommitlog" + call s:MapDelimiters('','') + elseif a:filetype == "SVNdiff" + call s:MapDelimiters('','') + elseif a:filetype == "SVNinfo" + call s:MapDelimiters('','') + elseif a:filetype == "systemverilog" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "tads" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "taglist" + call s:MapDelimiters('', '') + elseif a:filetype == "tags" + call s:MapDelimiters(';', '') + elseif a:filetype == "tak" + call s:MapDelimiters('$', '') + elseif a:filetype == "tar" + call s:MapDelimiters('', '') + elseif a:filetype == "tasm" + call s:MapDelimiters(';', '') + elseif a:filetype == "tcl" + call s:MapDelimiters('#','') + elseif a:filetype == "terminfo" + call s:MapDelimiters('#', '') + elseif a:filetype == "tex" + call s:MapDelimiters('%','') + elseif a:filetype == "text" + call s:MapDelimiters('','') + elseif a:filetype == "texinfo" + call s:MapDelimiters("@c ", "") + elseif a:filetype == "texmf" + call s:MapDelimiters('%', '') + elseif a:filetype == "tf" + call s:MapDelimiters(';', '') + elseif a:filetype == "tidy" + call s:MapDelimiters('#', '') + elseif a:filetype == "tli" + call s:MapDelimiters('#', '') + elseif a:filetype == "trasys" + call s:MapDelimiters("$", "") + elseif a:filetype == "tsalt" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "tsscl" + call s:MapDelimiters('#', '') + elseif a:filetype == "tssgm" + call s:MapDelimiters("comment = '","'") + elseif a:filetype == "txt2tags" + call s:MapDelimiters('%','') + elseif a:filetype == "uc" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "uil" + call s:MapDelimiters('!', '') + elseif a:filetype == "vb" + call s:MapDelimiters("'","") + elseif a:filetype == "vcscommit" + call s:MapDelimiters('','') + elseif a:filetype == "velocity" + call s:MapDelimitersWithAlternative("##","", '#*', '*#') + elseif a:filetype == "vera" + call s:MapDelimitersWithAlternative('/*','*/','//','') + elseif a:filetype == "verilog" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "verilog_systemverilog" + call s:MapDelimitersWithAlternative('//','', '/*','*/') + elseif a:filetype == "vgrindefs" + call s:MapDelimiters('#', '') + elseif a:filetype == "vhdl" + call s:MapDelimiters('--', '') + elseif a:filetype == "vim" + call s:MapDelimiters('"','') + elseif a:filetype == "viminfo" + call s:MapDelimiters('','') + elseif a:filetype == "vimperator" + call s:MapDelimiters('"','') + elseif a:filetype == "virata" + call s:MapDelimiters('%', '') + elseif a:filetype == "vo_base" + call s:MapDelimiters('', '') + elseif a:filetype == "vrml" + call s:MapDelimiters('#', '') + elseif a:filetype == "vsejcl" + call s:MapDelimiters('/*', '') + elseif a:filetype == "webmacro" + call s:MapDelimiters('##', '') + elseif a:filetype == "wget" + call s:MapDelimiters('#', '') + elseif a:filetype ==? "Wikipedia" + call s:MapDelimiters('') + elseif a:filetype == "winbatch" + call s:MapDelimiters(';', '') + elseif a:filetype == "wml" + call s:MapDelimiters('#', '') + elseif a:filetype =~ "[^w]*sh" + call s:MapDelimiters('#', '') + elseif a:filetype == "wvdial" + call s:MapDelimiters(';', '') + elseif a:filetype == "xdefaults" + call s:MapDelimiters('!', '') + elseif a:filetype == "xf86conf" + call s:MapDelimiters('#', '') + elseif a:filetype == "xhtml" + call s:MapDelimiters('') + elseif a:filetype == "xkb" + call s:MapDelimiters('//', '') + elseif a:filetype == "xmath" + call s:MapDelimiters('#', '') + elseif a:filetype == "xml" + call s:MapDelimiters('') + elseif a:filetype == "xmodmap" + call s:MapDelimiters('!', '') + elseif a:filetype == "xpm2" + call s:MapDelimiters('!', '') + elseif a:filetype == "xpm" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "xsd" + call s:MapDelimiters('') + elseif a:filetype == "xslt" + call s:MapDelimiters('') + elseif a:filetype == "yacc" + call s:MapDelimiters('/*','*/') + elseif a:filetype == "yaml" + call s:MapDelimiters('#','') + elseif a:filetype == "xquery" + call s:MapDelimiters('(:',':)') + elseif a:filetype == "z8a" + call s:MapDelimiters(';', '') + + elseif a:filetype == "" + call s:MapDelimitersWithAlternative("","", "", "") + + "we have not hardcoded the comment delimiters to use for this filetype so + "get them from &commentstring. + else + "print a disclaimer to the user :) + if !g:NERDShutUp + call s:NerdEcho("Unknown filetype '".a:filetype."', setting delimiters by &commentstring.\nPleeeeease email the author of the NERD commenter with this filetype\nand its delimiters!", 0) + endif + + "extract the delims from &commentstring + let left= substitute(&commentstring, '\(.*\)%s.*', '\1', '') + let right= substitute(&commentstring, '.*%s\(.*\)', '\1', 'g') + + call s:MapDelimiters(left,right) + endif +endfunction + +" Function: s:MapDelimiters(left, right) function {{{2 +" This function is a wrapper for s:MapDelimiters(left, right, leftAlt, rightAlt, useAlt) and is called when there +" is no alternative comment delimiters for the current filetype +" +" Args: +" -left: the left comment delimiter +" -right: the right comment delimiter +function s:MapDelimiters(left, right) + call s:MapDelimitersWithAlternative(a:left, a:right, "", "") +endfunction + +" Function: s:MapDelimitersWithAlternative(left, right, leftAlt, rightAlt) function {{{2 +" this function sets up the comment delimiter buffer variables +" +" Args: +" -left: the string defining the comment start delimiter +" -right: the string defining the comment end delimiter +" -leftAlt: the string for the alternative comment style defining the comment start delimiter +" -rightAlt: the string for the alternative comment style defining the comment end delimiter +function s:MapDelimitersWithAlternative(left, right, leftAlt, rightAlt) + if !exists('g:NERD_' . &filetype . '_alt_style') + let b:left = a:left + let b:right = a:right + let b:leftAlt = a:leftAlt + let b:rightAlt = a:rightAlt + else + let b:left = a:leftAlt + let b:right = a:rightAlt + let b:leftAlt = a:left + let b:rightAlt = a:right + endif +endfunction + +" Function: s:SwitchToAlternativeDelimiters(printMsgs) function {{{2 +" This function is used to swap the delimiters that are being used to the +" alternative delimiters for that filetype. For example, if a c++ file is +" being edited and // comments are being used, after this function is called +" /**/ comments will be used. +" +" Args: +" -printMsgs: if this is 1 then a message is echoed to the user telling them +" if this function changed the delimiters or not +function s:SwitchToAlternativeDelimiters(printMsgs) + "if both of the alternative delimiters are empty then there is no + "alternative comment style so bail out + if b:leftAlt == "" && b:rightAlt == "" + if a:printMsgs + call s:NerdEcho("Cannot use alternative delimiters, none are specified", 0) + endif + return 0 + endif + + "save the current delimiters + let tempLeft = b:left + let tempRight = b:right + + "swap current delimiters for alternative + let b:left = b:leftAlt + let b:right = b:rightAlt + + "set the previously current delimiters to be the new alternative ones + let b:leftAlt = tempLeft + let b:rightAlt = tempRight + + "tell the user what comment delimiters they are now using + if a:printMsgs + let leftNoEsc = b:left + let rightNoEsc = b:right + call s:NerdEcho("Now using " . leftNoEsc . " " . rightNoEsc . " to delimit comments", 1) + endif + + return 1 +endfunction + +" Section: Comment delimiter add/removal functions {{{1 +" ============================================================================ +" Function: s:AppendCommentToLine(){{{2 +" This function appends comment delimiters at the EOL and places the cursor in +" position to start typing the comment +function s:AppendCommentToLine() + let left = s:GetLeft(0,1,0) + let right = s:GetRight(0,1,0) + + " get the len of the right delim + let lenRight = strlen(right) + + let isLineEmpty = strlen(getline(".")) == 0 + let insOrApp = (isLineEmpty==1 ? 'i' : 'A') + + "stick the delimiters down at the end of the line. We have to format the + "comment with spaces as appropriate + execute ":normal! " . insOrApp . (isLineEmpty ? '' : ' ') . left . right . " " + + " if there is a right delimiter then we gotta move the cursor left + " by the len of the right delimiter so we insert between the delimiters + if lenRight > 0 + let leftMoveAmount = lenRight + execute ":normal! " . leftMoveAmount . "h" + endif + startinsert +endfunction + +" Function: s:CommentBlock(top, bottom, lSide, rSide, forceNested ) {{{2 +" This function is used to comment out a region of code. This region is +" specified as a bounding box by arguments to the function. +" +" Args: +" -top: the line number for the top line of code in the region +" -bottom: the line number for the bottom line of code in the region +" -lSide: the column number for the left most column in the region +" -rSide: the column number for the right most column in the region +" -forceNested: a flag indicating whether comments should be nested +function s:CommentBlock(top, bottom, lSide, rSide, forceNested ) + " we need to create local copies of these arguments so we can modify them + let top = a:top + let bottom = a:bottom + let lSide = a:lSide + let rSide = a:rSide + + "if the top or bottom line starts with tabs we have to adjust the left and + "right boundaries so that they are set as though the tabs were spaces + let topline = getline(top) + let bottomline = getline(bottom) + if s:HasLeadingTabs(topline, bottomline) + + "find out how many tabs are in the top line and adjust the left + "boundary accordingly + let numTabs = s:NumberOfLeadingTabs(topline) + if lSide < numTabs + let lSide = &ts * lSide + else + let lSide = (lSide - numTabs) + (&ts * numTabs) + endif + + "find out how many tabs are in the bottom line and adjust the right + "boundary accordingly + let numTabs = s:NumberOfLeadingTabs(bottomline) + let rSide = (rSide - numTabs) + (&ts * numTabs) + endif + + "we must check that bottom IS actually below top, if it is not then we + "swap top and bottom. Similarly for left and right. + if bottom < top + let temp = top + let top = bottom + let bottom = top + endif + if rSide < lSide + let temp = lSide + let lSide = rSide + let rSide = temp + endif + + "if the current delimiters arent multipart then we will switch to the + "alternative delims (if THEY are) as the comment will be better and more + "accurate with multipart delims + let switchedDelims = 0 + if !s:Multipart() && g:NERDAllowAnyVisualDelims && s:AltMultipart() + let switchedDelims = 1 + call s:SwitchToAlternativeDelimiters(0) + endif + + "start the commenting from the top and keep commenting till we reach the + "bottom + let currentLine=top + while currentLine <= bottom + + "check if we are allowed to comment this line + if s:CanCommentLine(a:forceNested, currentLine) + + "convert the leading tabs into spaces + let theLine = getline(currentLine) + let lineHasLeadTabs = s:HasLeadingTabs(theLine) + if lineHasLeadTabs + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + endif + + "dont comment lines that begin after the right boundary of the + "block unless the user has specified to do so + if theLine !~ '^ \{' . rSide . '\}' || !g:NERDBlockComIgnoreEmpty + + "attempt to place the cursor in on the left of the boundary box, + "then check if we were successful, if not then we cant comment this + "line + call setline(currentLine, theLine) + if s:CanPlaceCursor(currentLine, lSide) + + let leftSpaced = s:GetLeft(0,1,0) + let rightSpaced = s:GetRight(0,1,0) + + "stick the left delimiter down + let theLine = strpart(theLine, 0, lSide-1) . leftSpaced . strpart(theLine, lSide-1) + + if s:Multipart() + "stick the right delimiter down + let theLine = strpart(theLine, 0, rSide+strlen(leftSpaced)) . rightSpaced . strpart(theLine, rSide+strlen(leftSpaced)) + + let firstLeftDelim = s:FindDelimiterIndex(b:left, theLine) + let lastRightDelim = s:LastIndexOfDelim(b:right, theLine) + + if firstLeftDelim != -1 && lastRightDelim != -1 + let searchStr = strpart(theLine, 0, lastRightDelim) + let searchStr = strpart(searchStr, firstLeftDelim+strlen(b:left)) + + "replace the outter most delims in searchStr with + "place-holders + let theLineWithPlaceHolders = s:ReplaceDelims(b:left, b:right, g:NERDLPlace, g:NERDRPlace, searchStr) + + "add the right delimiter onto the line + let theLine = strpart(theLine, 0, firstLeftDelim+strlen(b:left)) . theLineWithPlaceHolders . strpart(theLine, lastRightDelim) + endif + endif + endif + endif + + "restore tabs if needed + if lineHasLeadTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + + call setline(currentLine, theLine) + endif + + let currentLine = currentLine + 1 + endwhile + + "if we switched delims then we gotta go back to what they were before + if switchedDelims == 1 + call s:SwitchToAlternativeDelimiters(0) + endif +endfunction + +" Function: s:CommentLines(forceNested, alignLeft, alignRight, firstLine, lastLine) {{{2 +" This function comments a range of lines. +" +" Args: +" -forceNested: a flag indicating whether the called is requesting the comment +" to be nested if need be +" -align: should be "left" or "both" or "none" +" -firstLine/lastLine: the top and bottom lines to comment +function s:CommentLines(forceNested, align, firstLine, lastLine) + " we need to get the left and right indexes of the leftmost char in the + " block of of lines and the right most char so that we can do alignment of + " the delimiters if the user has specified + let leftAlignIndx = s:LeftMostIndx(a:forceNested, 0, a:firstLine, a:lastLine) + let rightAlignIndx = s:RightMostIndx(a:forceNested, 0, a:firstLine, a:lastLine) + + " gotta add the length of the left delimiter onto the rightAlignIndx cos + " we'll be adding a left delim to the line + let rightAlignIndx = rightAlignIndx + strlen(s:GetLeft(0,1,0)) + + " now we actually comment the lines. Do it line by line + let currentLine = a:firstLine + while currentLine <= a:lastLine + + " get the next line, check commentability and convert spaces to tabs + let theLine = getline(currentLine) + let lineHasLeadingTabs = s:HasLeadingTabs(theLine) + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + if s:CanCommentLine(a:forceNested, currentLine) + "if the user has specified forceNesting then we check to see if we + "need to switch delimiters for place-holders + if a:forceNested && g:NERDUsePlaceHolders + let theLine = s:SwapOutterMultiPartDelimsForPlaceHolders(theLine) + endif + + " find out if the line is commented using normal delims and/or + " alternate ones + let isCommented = s:IsCommented(b:left, b:right, theLine) || s:IsCommented(b:leftAlt, b:rightAlt, theLine) + + " check if we can comment this line + if !isCommented || g:NERDUsePlaceHolders || s:Multipart() + if a:align == "left" || a:align == "both" + let theLine = s:AddLeftDelimAligned(s:GetLeft(0,1,0), theLine, leftAlignIndx) + else + let theLine = s:AddLeftDelim(s:GetLeft(0,1,0), theLine) + endif + if a:align == "both" + let theLine = s:AddRightDelimAligned(s:GetRight(0,1,0), theLine, rightAlignIndx) + else + let theLine = s:AddRightDelim(s:GetRight(0,1,0), theLine) + endif + endif + endif + + " restore leading tabs if appropriate + if lineHasLeadingTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + + " we are done with this line + call setline(currentLine, theLine) + let currentLine = currentLine + 1 + endwhile + +endfunction + +" Function: s:CommentLinesMinimal(firstLine, lastLine) {{{2 +" This function comments a range of lines in a minimal style. I +" +" Args: +" -firstLine/lastLine: the top and bottom lines to comment +function s:CommentLinesMinimal(firstLine, lastLine) + "check that minimal comments can be done on this filetype + if !s:HasMultipartDelims() + throw 'NERDCommenter.Delimiters exception: Minimal comments can only be used for filetypes that have multipart delimiters' + endif + + "if we need to use place holders for the comment, make sure they are + "enabled for this filetype + if !g:NERDUsePlaceHolders && s:DoesBlockHaveMultipartDelim(a:firstLine, a:lastLine) + throw 'NERDCommenter.Settings exception: Placeoholders are required but disabled.' + endif + + "get the left and right delims to smack on + let left = s:GetSexyComLeft(g:NERDSpaceDelims,0) + let right = s:GetSexyComRight(g:NERDSpaceDelims,0) + + "make sure all multipart delims on the lines are replaced with + "placeholders to prevent illegal syntax + let currentLine = a:firstLine + while(currentLine <= a:lastLine) + let theLine = getline(currentLine) + let theLine = s:ReplaceDelims(left, right, g:NERDLPlace, g:NERDRPlace, theLine) + call setline(currentLine, theLine) + let currentLine = currentLine + 1 + endwhile + + "add the delim to the top line + let theLine = getline(a:firstLine) + let lineHasLeadingTabs = s:HasLeadingTabs(theLine) + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + let theLine = s:AddLeftDelim(left, theLine) + if lineHasLeadingTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + call setline(a:firstLine, theLine) + + "add the delim to the bottom line + let theLine = getline(a:lastLine) + let lineHasLeadingTabs = s:HasLeadingTabs(theLine) + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + let theLine = s:AddRightDelim(right, theLine) + if lineHasLeadingTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + call setline(a:lastLine, theLine) +endfunction + +" Function: s:CommentLinesSexy(topline, bottomline) function {{{2 +" This function is used to comment lines in the 'Sexy' style. eg in c: +" /* +" * This is a sexy comment +" */ +" Args: +" -topline: the line num of the top line in the sexy comment +" -bottomline: the line num of the bottom line in the sexy comment +function s:CommentLinesSexy(topline, bottomline) + let left = s:GetSexyComLeft(0, 0) + let right = s:GetSexyComRight(0, 0) + + "check if we can do a sexy comment with the available delimiters + if left == -1 || right == -1 + throw 'NERDCommenter.Delimiters exception: cannot perform sexy comments with available delimiters.' + endif + + "make sure the lines arent already commented sexually + if !s:CanSexyCommentLines(a:topline, a:bottomline) + throw 'NERDCommenter.Nesting exception: cannot nest sexy comments' + endif + + + let sexyComMarker = s:GetSexyComMarker(0,0) + let sexyComMarkerSpaced = s:GetSexyComMarker(1,0) + + + " we jam the comment as far to the right as possible + let leftAlignIndx = s:LeftMostIndx(1, 1, a:topline, a:bottomline) + + "check if we should use the compact style i.e that the left/right + "delimiters should appear on the first and last lines of the code and not + "on separate lines above/below the first/last lines of code + if g:NERDCompactSexyComs + let spaceString = (g:NERDSpaceDelims ? s:spaceStr : '') + + "comment the top line + let theLine = getline(a:topline) + let lineHasTabs = s:HasLeadingTabs(theLine) + if lineHasTabs + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + endif + let theLine = s:SwapOutterMultiPartDelimsForPlaceHolders(theLine) + let theLine = s:AddLeftDelimAligned(left . spaceString, theLine, leftAlignIndx) + if lineHasTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + call setline(a:topline, theLine) + + "comment the bottom line + if a:bottomline != a:topline + let theLine = getline(a:bottomline) + let lineHasTabs = s:HasLeadingTabs(theLine) + if lineHasTabs + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + endif + let theLine = s:SwapOutterMultiPartDelimsForPlaceHolders(theLine) + endif + let theLine = s:AddRightDelim(spaceString . right, theLine) + if lineHasTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + call setline(a:bottomline, theLine) + else + + " add the left delimiter one line above the lines that are to be commented + call cursor(a:topline, 1) + execute 'normal! O' + call setline(a:topline, strpart(s:spaces, 0, leftAlignIndx) . left ) + + " add the right delimiter after bottom line (we have to add 1 cos we moved + " the lines down when we added the left delim + call cursor(a:bottomline+1, 1) + execute 'normal! o' + call setline(a:bottomline+2, strpart(s:spaces, 0, leftAlignIndx) . strpart(s:spaces, 0, strlen(left)-strlen(sexyComMarker)) . right ) + + endif + + " go thru each line adding the sexyComMarker marker to the start of each + " line in the appropriate place to align them with the comment delims + let currentLine = a:topline+1 + while currentLine <= a:bottomline + !g:NERDCompactSexyComs + " get the line and convert the tabs to spaces + let theLine = getline(currentLine) + let lineHasTabs = s:HasLeadingTabs(theLine) + if lineHasTabs + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + endif + + let theLine = s:SwapOutterMultiPartDelimsForPlaceHolders(theLine) + + " add the sexyComMarker + let theLine = strpart(s:spaces, 0, leftAlignIndx) . strpart(s:spaces, 0, strlen(left)-strlen(sexyComMarker)) . sexyComMarkerSpaced . strpart(theLine, leftAlignIndx) + + if lineHasTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + + + " set the line and move onto the next one + call setline(currentLine, theLine) + let currentLine = currentLine + 1 + endwhile + +endfunction + +" Function: s:CommentLinesToggle(forceNested, firstLine, lastLine) {{{2 +" Applies "toggle" commenting to the given range of lines +" +" Args: +" -forceNested: a flag indicating whether the called is requesting the comment +" to be nested if need be +" -firstLine/lastLine: the top and bottom lines to comment +function s:CommentLinesToggle(forceNested, firstLine, lastLine) + let currentLine = a:firstLine + while currentLine <= a:lastLine + + " get the next line, check commentability and convert spaces to tabs + let theLine = getline(currentLine) + let lineHasLeadingTabs = s:HasLeadingTabs(theLine) + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + if s:CanToggleCommentLine(a:forceNested, currentLine) + + "if the user has specified forceNesting then we check to see if we + "need to switch delimiters for place-holders + if g:NERDUsePlaceHolders + let theLine = s:SwapOutterMultiPartDelimsForPlaceHolders(theLine) + endif + + let theLine = s:AddLeftDelim(s:GetLeft(0, 1, 0), theLine) + let theLine = s:AddRightDelim(s:GetRight(0, 1, 0), theLine) + endif + + " restore leading tabs if appropriate + if lineHasLeadingTabs + let theLine = s:ConvertLeadingSpacesToTabs(theLine) + endif + + " we are done with this line + call setline(currentLine, theLine) + let currentLine = currentLine + 1 + endwhile + +endfunction + +" Function: s:CommentRegion(topline, topCol, bottomLine, bottomCol) function {{{2 +" This function comments chunks of text selected in visual mode. +" It will comment exactly the text that they have selected. +" Args: +" -topLine: the line num of the top line in the sexy comment +" -topCol: top left col for this comment +" -bottomline: the line num of the bottom line in the sexy comment +" -bottomCol: the bottom right col for this comment +" -forceNested: whether the caller wants comments to be nested if the +" line(s) are already commented +function s:CommentRegion(topLine, topCol, bottomLine, bottomCol, forceNested) + + "switch delims (if we can) if the current set isnt multipart + let switchedDelims = 0 + if !s:Multipart() && s:AltMultipart() && !g:NERDAllowAnyVisualDelims + let switchedDelims = 1 + call s:SwitchToAlternativeDelimiters(0) + endif + + "if there is only one line in the comment then just do it + if a:topLine == a:bottomLine + call s:CommentBlock(a:topLine, a:bottomLine, a:topCol, a:bottomCol, a:forceNested) + + "there are multiple lines in the comment + else + "comment the top line + call s:CommentBlock(a:topLine, a:topLine, a:topCol, strlen(getline(a:topLine)), a:forceNested) + + "comment out all the lines in the middle of the comment + let topOfRange = a:topLine+1 + let bottomOfRange = a:bottomLine-1 + if topOfRange <= bottomOfRange + call s:CommentLines(a:forceNested, "none", topOfRange, bottomOfRange) + endif + + "comment the bottom line + let bottom = getline(a:bottomLine) + let numLeadingSpacesTabs = strlen(substitute(bottom, '^\([ \t]*\).*$', '\1', '')) + call s:CommentBlock(a:bottomLine, a:bottomLine, numLeadingSpacesTabs+1, a:bottomCol, a:forceNested) + + endif + + "stick the cursor back on the char it was on before the comment + call cursor(a:topLine, a:topCol + strlen(b:left) + g:NERDSpaceDelims) + + "if we switched delims then we gotta go back to what they were before + if switchedDelims == 1 + call s:SwitchToAlternativeDelimiters(0) + endif + +endfunction + +" Function: s:InvertComment(firstLine, lastLine) function {{{2 +" Inverts the comments on the lines between and including the given line +" numbers i.e all commented lines are uncommented and vice versa +" Args: +" -firstLine: the top of the range of lines to be inverted +" -lastLine: the bottom of the range of lines to be inverted +function s:InvertComment(firstLine, lastLine) + + " go thru all lines in the given range + let currentLine = a:firstLine + while currentLine <= a:lastLine + let theLine = getline(currentLine) + + let sexyComBounds = s:FindBoundingLinesOfSexyCom(currentLine) + + " if the line is commented normally, uncomment it + if s:IsCommentedFromStartOfLine(b:left, theLine) || s:IsCommentedFromStartOfLine(b:leftAlt, theLine) + call s:UncommentLines(currentLine, currentLine) + let currentLine = currentLine + 1 + + " check if the line is commented sexually + elseif !empty(sexyComBounds) + let numLinesBeforeSexyComRemoved = s:NumLinesInBuf() + call s:UncommentLinesSexy(sexyComBounds[0], sexyComBounds[1]) + + "move to the line after last line of the sexy comment + let numLinesAfterSexyComRemoved = s:NumLinesInBuf() + let currentLine = bottomBound - (numLinesBeforeSexyComRemoved - numLinesAfterSexyComRemoved) + 1 + + " the line isnt commented + else + call s:CommentLinesToggle(1, currentLine, currentLine) + let currentLine = currentLine + 1 + endif + + endwhile +endfunction + +" Function: NERDComment(isVisual, type) function {{{2 +" This function is a Wrapper for the main commenting functions +" +" Args: +" -isVisual: a flag indicating whether the comment is requested in visual +" mode or not +" -type: the type of commenting requested. Can be 'sexy', 'invert', +" 'minimal', 'toggle', 'alignLeft', 'alignBoth', 'norm', +" 'nested', 'toEOL', 'append', 'insert', 'uncomment', 'yank' +function! NERDComment(isVisual, type) range + " we want case sensitivity when commenting + let oldIgnoreCase = &ignorecase + set noignorecase + + if a:isVisual + let firstLine = line("'<") + let lastLine = line("'>") + let firstCol = col("'<") + let lastCol = col("'>") - (&selection == 'exclusive' ? 1 : 0) + else + let firstLine = a:firstline + let lastLine = a:lastline + endif + + let countWasGiven = (a:isVisual == 0 && firstLine != lastLine) + + let forceNested = (a:type == 'nested' || g:NERDDefaultNesting) + + if a:type == 'norm' || a:type == 'nested' + if a:isVisual && visualmode() == "" + call s:CommentBlock(firstLine, lastLine, firstCol, lastCol, forceNested) + elseif a:isVisual && visualmode() == "v" && (g:NERDCommentWholeLinesInVMode==0 || (g:NERDCommentWholeLinesInVMode==2 && s:HasMultipartDelims())) + call s:CommentRegion(firstLine, firstCol, lastLine, lastCol, forceNested) + else + call s:CommentLines(forceNested, "none", firstLine, lastLine) + endif + + elseif a:type == 'alignLeft' || a:type == 'alignBoth' + let align = "none" + if a:type == "alignLeft" + let align = "left" + elseif a:type == "alignBoth" + let align = "both" + endif + call s:CommentLines(forceNested, align, firstLine, lastLine) + + elseif a:type == 'invert' + call s:InvertComment(firstLine, lastLine) + + elseif a:type == 'sexy' + try + call s:CommentLinesSexy(firstLine, lastLine) + catch /NERDCommenter.Delimiters/ + call s:CommentLines(forceNested, "none", firstLine, lastLine) + catch /NERDCommenter.Nesting/ + call s:NerdEcho("Sexy comment aborted. Nested sexy cannot be nested", 0) + endtry + + elseif a:type == 'toggle' + let theLine = getline(firstLine) + + if s:IsInSexyComment(firstLine) || s:IsCommentedFromStartOfLine(b:left, theLine) || s:IsCommentedFromStartOfLine(b:leftAlt, theLine) + call s:UncommentLines(firstLine, lastLine) + else + call s:CommentLinesToggle(forceNested, firstLine, lastLine) + endif + + elseif a:type == 'minimal' + try + call s:CommentLinesMinimal(firstLine, lastLine) + catch /NERDCommenter.Delimiters/ + call s:NerdEcho("Minimal comments can only be used for filetypes that have multipart delimiters.", 0) + catch /NERDCommenter.Settings/ + call s:NerdEcho("Place holders are required but disabled.", 0) + endtry + + elseif a:type == 'toEOL' + call s:SaveScreenState() + call s:CommentBlock(firstLine, firstLine, col("."), col("$")-1, 1) + call s:RestoreScreenState() + + elseif a:type == 'append' + call s:AppendCommentToLine() + + elseif a:type == 'insert' + call s:PlaceDelimitersAndInsBetween() + + elseif a:type == 'uncomment' + call s:UncommentLines(firstLine, lastLine) + + elseif a:type == 'yank' + if a:isVisual + normal! gvy + elseif countWasGiven + execute firstLine .','. lastLine .'yank' + else + normal! yy + endif + execute firstLine .','. lastLine .'call NERDComment('. a:isVisual .', "norm")' + endif + + let &ignorecase = oldIgnoreCase +endfunction + +" Function: s:PlaceDelimitersAndInsBetween() function {{{2 +" This is function is called to place comment delimiters down and place the +" cursor between them +function s:PlaceDelimitersAndInsBetween() + " get the left and right delimiters without any escape chars in them + let left = s:GetLeft(0, 1, 0) + let right = s:GetRight(0, 1, 0) + + let theLine = getline(".") + let lineHasLeadTabs = s:HasLeadingTabs(theLine) || (theLine =~ '^ *$' && !&expandtab) + + "convert tabs to spaces and adjust the cursors column to take this into + "account + let untabbedCol = s:UntabbedCol(theLine, col(".")) + call setline(line("."), s:ConvertLeadingTabsToSpaces(theLine)) + call cursor(line("."), untabbedCol) + + " get the len of the right delim + let lenRight = strlen(right) + + let isDelimOnEOL = col(".") >= strlen(getline(".")) + + " if the cursor is in the first col then we gotta insert rather than + " append the comment delimiters here + let insOrApp = (col(".")==1 ? 'i' : 'a') + + " place the delimiters down. We do it differently depending on whether + " there is a left AND right delimiter + if lenRight > 0 + execute ":normal! " . insOrApp . left . right + execute ":normal! " . lenRight . "h" + else + execute ":normal! " . insOrApp . left + + " if we are tacking the delim on the EOL then we gotta add a space + " after it cos when we go out of insert mode the cursor will move back + " one and the user wont be in position to type the comment. + if isDelimOnEOL + execute 'normal! a ' + endif + endif + normal! l + + "if needed convert spaces back to tabs and adjust the cursors col + "accordingly + if lineHasLeadTabs + let tabbedCol = s:TabbedCol(getline("."), col(".")) + call setline(line("."), s:ConvertLeadingSpacesToTabs(getline("."))) + call cursor(line("."), tabbedCol) + endif + + startinsert +endfunction + +" Function: s:RemoveDelimiters(left, right, line) {{{2 +" this function is called to remove the first left comment delimiter and the +" last right delimiter of the given line. +" +" The args left and right must be strings. If there is no right delimiter (as +" is the case for e.g vim file comments) them the arg right should be "" +" +" Args: +" -left: the left comment delimiter +" -right: the right comment delimiter +" -line: the line to remove the delimiters from +function s:RemoveDelimiters(left, right, line) + + let l:left = a:left + let l:right = a:right + let lenLeft = strlen(left) + let lenRight = strlen(right) + + let delimsSpaced = (g:NERDSpaceDelims || g:NERDRemoveExtraSpaces) + + let line = a:line + + "look for the left delimiter, if we find it, remove it. + let leftIndx = s:FindDelimiterIndex(a:left, line) + if leftIndx != -1 + let line = strpart(line, 0, leftIndx) . strpart(line, leftIndx+lenLeft) + + "if the user has specified that there is a space after the left delim + "then check for the space and remove it if it is there + if delimsSpaced && strpart(line, leftIndx, s:lenSpaceStr) == s:spaceStr + let line = strpart(line, 0, leftIndx) . strpart(line, leftIndx+s:lenSpaceStr) + endif + endif + + "look for the right delimiter, if we find it, remove it + let rightIndx = s:FindDelimiterIndex(a:right, line) + if rightIndx != -1 + let line = strpart(line, 0, rightIndx) . strpart(line, rightIndx+lenRight) + + "if the user has specified that there is a space before the right delim + "then check for the space and remove it if it is there + if delimsSpaced && strpart(line, rightIndx-s:lenSpaceStr, s:lenSpaceStr) == s:spaceStr && s:Multipart() + let line = strpart(line, 0, rightIndx-s:lenSpaceStr) . strpart(line, rightIndx) + endif + endif + + return line +endfunction + +" Function: s:UncommentLines(topLine, bottomLine) {{{2 +" This function uncomments the given lines +" +" Args: +" topLine: the top line of the visual selection to uncomment +" bottomLine: the bottom line of the visual selection to uncomment +function s:UncommentLines(topLine, bottomLine) + "make local copies of a:firstline and a:lastline and, if need be, swap + "them around if the top line is below the bottom + let l:firstline = a:topLine + let l:lastline = a:bottomLine + if firstline > lastline + let firstline = lastline + let lastline = a:topLine + endif + + "go thru each line uncommenting each line removing sexy comments + let currentLine = firstline + while currentLine <= lastline + + "check the current line to see if it is part of a sexy comment + let sexyComBounds = s:FindBoundingLinesOfSexyCom(currentLine) + if !empty(sexyComBounds) + + "we need to store the num lines in the buf before the comment is + "removed so we know how many lines were removed when the sexy com + "was removed + let numLinesBeforeSexyComRemoved = s:NumLinesInBuf() + + call s:UncommentLinesSexy(sexyComBounds[0], sexyComBounds[1]) + + "move to the line after last line of the sexy comment + let numLinesAfterSexyComRemoved = s:NumLinesInBuf() + let numLinesRemoved = numLinesBeforeSexyComRemoved - numLinesAfterSexyComRemoved + let currentLine = sexyComBounds[1] - numLinesRemoved + 1 + let lastline = lastline - numLinesRemoved + + "no sexy com was detected so uncomment the line as normal + else + call s:UncommentLinesNormal(currentLine, currentLine) + let currentLine = currentLine + 1 + endif + endwhile + +endfunction + +" Function: s:UncommentLinesSexy(topline, bottomline) {{{2 +" This function removes all the comment characters associated with the sexy +" comment spanning the given lines +" Args: +" -topline/bottomline: the top/bottom lines of the sexy comment +function s:UncommentLinesSexy(topline, bottomline) + let left = s:GetSexyComLeft(0,1) + let right = s:GetSexyComRight(0,1) + + + "check if it is even possible for sexy comments to exist with the + "available delimiters + if left == -1 || right == -1 + throw 'NERDCommenter.Delimiters exception: cannot uncomment sexy comments with available delimiters.' + endif + + let leftUnEsc = s:GetSexyComLeft(0,0) + let rightUnEsc = s:GetSexyComRight(0,0) + + let sexyComMarker = s:GetSexyComMarker(0, 1) + let sexyComMarkerUnEsc = s:GetSexyComMarker(0, 0) + + "the markerOffset is how far right we need to move the sexyComMarker to + "line it up with the end of the left delim + let markerOffset = strlen(leftUnEsc)-strlen(sexyComMarkerUnEsc) + + " go thru the intermediate lines of the sexy comment and remove the + " sexy comment markers (eg the '*'s on the start of line in a c sexy + " comment) + let currentLine = a:topline+1 + while currentLine < a:bottomline + let theLine = getline(currentLine) + + " remove the sexy comment marker from the line. We also remove the + " space after it if there is one and if appropriate options are set + let sexyComMarkerIndx = stridx(theLine, sexyComMarkerUnEsc) + if strpart(theLine, sexyComMarkerIndx+strlen(sexyComMarkerUnEsc), s:lenSpaceStr) == s:spaceStr && g:NERDSpaceDelims + let theLine = strpart(theLine, 0, sexyComMarkerIndx - markerOffset) . strpart(theLine, sexyComMarkerIndx+strlen(sexyComMarkerUnEsc)+s:lenSpaceStr) + else + let theLine = strpart(theLine, 0, sexyComMarkerIndx - markerOffset) . strpart(theLine, sexyComMarkerIndx+strlen(sexyComMarkerUnEsc)) + endif + + let theLine = s:SwapOutterPlaceHoldersForMultiPartDelims(theLine) + + let theLine = s:ConvertLeadingWhiteSpace(theLine) + + " move onto the next line + call setline(currentLine, theLine) + let currentLine = currentLine + 1 + endwhile + + " gotta make a copy of a:bottomline cos we modify the position of the + " last line it if we remove the topline + let bottomline = a:bottomline + + " get the first line so we can remove the left delim from it + let theLine = getline(a:topline) + + " if the first line contains only the left delim then just delete it + if theLine =~ '^[ \t]*' . left . '[ \t]*$' && !g:NERDCompactSexyComs + call cursor(a:topline, 1) + normal! dd + let bottomline = bottomline - 1 + + " topline contains more than just the left delim + else + + " remove the delim. If there is a space after it + " then remove this too if appropriate + let delimIndx = stridx(theLine, leftUnEsc) + if strpart(theLine, delimIndx+strlen(leftUnEsc), s:lenSpaceStr) == s:spaceStr && g:NERDSpaceDelims + let theLine = strpart(theLine, 0, delimIndx) . strpart(theLine, delimIndx+strlen(leftUnEsc)+s:lenSpaceStr) + else + let theLine = strpart(theLine, 0, delimIndx) . strpart(theLine, delimIndx+strlen(leftUnEsc)) + endif + let theLine = s:SwapOutterPlaceHoldersForMultiPartDelims(theLine) + call setline(a:topline, theLine) + endif + + " get the last line so we can remove the right delim + let theLine = getline(bottomline) + + " if the bottomline contains only the right delim then just delete it + if theLine =~ '^[ \t]*' . right . '[ \t]*$' + call cursor(bottomline, 1) + normal! dd + + " the last line contains more than the right delim + else + " remove the right delim. If there is a space after it and + " if the appropriate options are set then remove this too. + let delimIndx = s:LastIndexOfDelim(rightUnEsc, theLine) + if strpart(theLine, delimIndx+strlen(leftUnEsc), s:lenSpaceStr) == s:spaceStr && g:NERDSpaceDelims + let theLine = strpart(theLine, 0, delimIndx) . strpart(theLine, delimIndx+strlen(rightUnEsc)+s:lenSpaceStr) + else + let theLine = strpart(theLine, 0, delimIndx) . strpart(theLine, delimIndx+strlen(rightUnEsc)) + endif + + " if the last line also starts with a sexy comment marker then we + " remove this as well + if theLine =~ '^[ \t]*' . sexyComMarker + + " remove the sexyComMarker. If there is a space after it then + " remove that too + let sexyComMarkerIndx = stridx(theLine, sexyComMarkerUnEsc) + if strpart(theLine, sexyComMarkerIndx+strlen(sexyComMarkerUnEsc), s:lenSpaceStr) == s:spaceStr && g:NERDSpaceDelims + let theLine = strpart(theLine, 0, sexyComMarkerIndx - markerOffset ) . strpart(theLine, sexyComMarkerIndx+strlen(sexyComMarkerUnEsc)+s:lenSpaceStr) + else + let theLine = strpart(theLine, 0, sexyComMarkerIndx - markerOffset ) . strpart(theLine, sexyComMarkerIndx+strlen(sexyComMarkerUnEsc)) + endif + endif + + let theLine = s:SwapOutterPlaceHoldersForMultiPartDelims(theLine) + call setline(bottomline, theLine) + endif +endfunction + +" Function: s:UncommentLineNormal(line) {{{2 +" uncomments the given line and returns the result +" Args: +" -line: the line to uncomment +function s:UncommentLineNormal(line) + let line = a:line + + "get the comment status on the line so we know how it is commented + let lineCommentStatus = s:IsCommentedOuttermost(b:left, b:right, b:leftAlt, b:rightAlt, line) + + "it is commented with b:left and b:right so remove these delims + if lineCommentStatus == 1 + let line = s:RemoveDelimiters(b:left, b:right, line) + + "it is commented with b:leftAlt and b:rightAlt so remove these delims + elseif lineCommentStatus == 2 && g:NERDRemoveAltComs + let line = s:RemoveDelimiters(b:leftAlt, b:rightAlt, line) + + "it is not properly commented with any delims so we check if it has + "any random left or right delims on it and remove the outtermost ones + else + "get the positions of all delim types on the line + let indxLeft = s:FindDelimiterIndex(b:left, line) + let indxLeftAlt = s:FindDelimiterIndex(b:leftAlt, line) + let indxRight = s:FindDelimiterIndex(b:right, line) + let indxRightAlt = s:FindDelimiterIndex(b:rightAlt, line) + + "remove the outter most left comment delim + if indxLeft != -1 && (indxLeft < indxLeftAlt || indxLeftAlt == -1) + let line = s:RemoveDelimiters(b:left, '', line) + elseif indxLeftAlt != -1 + let line = s:RemoveDelimiters(b:leftAlt, '', line) + endif + + "remove the outter most right comment delim + if indxRight != -1 && (indxRight < indxRightAlt || indxRightAlt == -1) + let line = s:RemoveDelimiters('', b:right, line) + elseif indxRightAlt != -1 + let line = s:RemoveDelimiters('', b:rightAlt, line) + endif + endif + + + let indxLeft = s:FindDelimiterIndex(b:left, line) + let indxLeftAlt = s:FindDelimiterIndex(b:leftAlt, line) + let indxLeftPlace = s:FindDelimiterIndex(g:NERDLPlace, line) + + let indxRightPlace = s:FindDelimiterIndex(g:NERDRPlace, line) + let indxRightAlt = s:FindDelimiterIndex(b:rightAlt, line) + let indxRightPlace = s:FindDelimiterIndex(g:NERDRPlace, line) + + let right = b:right + let left = b:left + if !s:Multipart() + let right = b:rightAlt + let left = b:leftAlt + endif + + + "if there are place-holders on the line then we check to see if they are + "the outtermost delimiters on the line. If so then we replace them with + "real delimiters + if indxLeftPlace != -1 + if (indxLeftPlace < indxLeft || indxLeft==-1) && (indxLeftPlace < indxLeftAlt || indxLeftAlt==-1) + let line = s:ReplaceDelims(g:NERDLPlace, g:NERDRPlace, left, right, line) + endif + elseif indxRightPlace != -1 + if (indxRightPlace < indxLeft || indxLeft==-1) && (indxLeftPlace < indxLeftAlt || indxLeftAlt==-1) + let line = s:ReplaceDelims(g:NERDLPlace, g:NERDRPlace, left, right, line) + endif + + endif + + let line = s:ConvertLeadingWhiteSpace(line) + + return line +endfunction + +" Function: s:UncommentLinesNormal(topline, bottomline) {{{2 +" This function is called to uncomment lines that arent a sexy comment +" Args: +" -topline/bottomline: the top/bottom line numbers of the comment +function s:UncommentLinesNormal(topline, bottomline) + let currentLine = a:topline + while currentLine <= a:bottomline + let line = getline(currentLine) + call setline(currentLine, s:UncommentLineNormal(line)) + let currentLine = currentLine + 1 + endwhile +endfunction + + +" Section: Other helper functions {{{1 +" ============================================================================ + +" Function: s:AddLeftDelim(delim, theLine) {{{2 +" Args: +function s:AddLeftDelim(delim, theLine) + return substitute(a:theLine, '^\([ \t]*\)', '\1' . a:delim, '') +endfunction + +" Function: s:AddLeftDelimAligned(delim, theLine) {{{2 +" Args: +function s:AddLeftDelimAligned(delim, theLine, alignIndx) + + "if the line is not long enough then bung some extra spaces on the front + "so we can align the delim properly + let theLine = a:theLine + if strlen(theLine) < a:alignIndx + let theLine = strpart(s:spaces, 0, a:alignIndx - strlen(theLine)) + endif + + return strpart(theLine, 0, a:alignIndx) . a:delim . strpart(theLine, a:alignIndx) +endfunction + +" Function: s:AddRightDelim(delim, theLine) {{{2 +" Args: +function s:AddRightDelim(delim, theLine) + if a:delim == '' + return a:theLine + else + return substitute(a:theLine, '$', a:delim, '') + endif +endfunction + +" Function: s:AddRightDelimAligned(delim, theLine, alignIndx) {{{2 +" Args: +function s:AddRightDelimAligned(delim, theLine, alignIndx) + if a:delim == "" + return a:theLine + else + + " when we align the right delim we are just adding spaces + " so we get a string containing the needed spaces (it + " could be empty) + let extraSpaces = '' + let extraSpaces = strpart(s:spaces, 0, a:alignIndx-strlen(a:theLine)) + + " add the right delim + return substitute(a:theLine, '$', extraSpaces . a:delim, '') + endif +endfunction + +" Function: s:AltMultipart() {{{2 +" returns 1 if the alternative delims are multipart +function s:AltMultipart() + return b:rightAlt != '' +endfunction + +" Function: s:CanCommentLine(forceNested, line) {{{2 +"This function is used to determine whether the given line can be commented. +"It returns 1 if it can be and 0 otherwise +" +" Args: +" -forceNested: a flag indicating whether the caller wants comments to be nested +" if the current line is already commented +" -lineNum: the line num of the line to check for commentability +function s:CanCommentLine(forceNested, lineNum) + let theLine = getline(a:lineNum) + + " make sure we don't comment lines that are just spaces or tabs or empty. + if theLine =~ "^[ \t]*$" + return 0 + endif + + "if the line is part of a sexy comment then just flag it... + if s:IsInSexyComment(a:lineNum) + return 0 + endif + + let isCommented = s:IsCommentedNormOrSexy(a:lineNum) + + "if the line isnt commented return true + if !isCommented + return 1 + endif + + "if the line is commented but nesting is allowed then return true + if a:forceNested && (!s:Multipart() || g:NERDUsePlaceHolders) + return 1 + endif + + return 0 +endfunction + +" Function: s:CanPlaceCursor(line, col) {{{2 +" returns 1 if the cursor can be placed exactly in the given position +function s:CanPlaceCursor(line, col) + let c = col(".") + let l = line(".") + call cursor(a:line, a:col) + let success = (line(".") == a:line && col(".") == a:col) + call cursor(l,c) + return success +endfunction + +" Function: s:CanSexyCommentLines(topline, bottomline) {{{2 +" Return: 1 if the given lines can be commented sexually, 0 otherwise +function s:CanSexyCommentLines(topline, bottomline) + " see if the selected regions have any sexy comments + let currentLine = a:topline + while(currentLine <= a:bottomline) + if s:IsInSexyComment(currentLine) + return 0 + endif + let currentLine = currentLine + 1 + endwhile + return 1 +endfunction +" Function: s:CanToggleCommentLine(forceNested, line) {{{2 +"This function is used to determine whether the given line can be toggle commented. +"It returns 1 if it can be and 0 otherwise +" +" Args: +" -lineNum: the line num of the line to check for commentability +function s:CanToggleCommentLine(forceNested, lineNum) + let theLine = getline(a:lineNum) + if (s:IsCommentedFromStartOfLine(b:left, theLine) || s:IsCommentedFromStartOfLine(b:leftAlt, theLine)) && !a:forceNested + return 0 + endif + + " make sure we don't comment lines that are just spaces or tabs or empty. + if theLine =~ "^[ \t]*$" + return 0 + endif + + "if the line is part of a sexy comment then just flag it... + if s:IsInSexyComment(a:lineNum) + return 0 + endif + + return 1 +endfunction + +" Function: s:ConvertLeadingSpacesToTabs(line) {{{2 +" This function takes a line and converts all leading tabs on that line into +" spaces +" +" Args: +" -line: the line whose leading tabs will be converted +function s:ConvertLeadingSpacesToTabs(line) + let toReturn = a:line + while toReturn =~ '^\t*' . s:TabSpace() . '\(.*\)$' + let toReturn = substitute(toReturn, '^\(\t*\)' . s:TabSpace() . '\(.*\)$' , '\1\t\2' , "") + endwhile + + return toReturn +endfunction + + +" Function: s:ConvertLeadingTabsToSpaces(line) {{{2 +" This function takes a line and converts all leading spaces on that line into +" tabs +" +" Args: +" -line: the line whose leading spaces will be converted +function s:ConvertLeadingTabsToSpaces(line) + let toReturn = a:line + while toReturn =~ '^\( *\)\t' + let toReturn = substitute(toReturn, '^\( *\)\t', '\1' . s:TabSpace() , "") + endwhile + + return toReturn +endfunction + +" Function: s:ConvertLeadingWhiteSpace(line) {{{2 +" Converts the leading white space to tabs/spaces depending on &ts +" +" Args: +" -line: the line to convert +function s:ConvertLeadingWhiteSpace(line) + let toReturn = a:line + while toReturn =~ '^ *\t' + let toReturn = substitute(toReturn, '^ *\zs\t\ze', s:TabSpace(), "g") + endwhile + + if !&expandtab + let toReturn = s:ConvertLeadingSpacesToTabs(toReturn) + endif + + return toReturn +endfunction + + +" Function: s:CountNonESCedOccurances(str, searchstr, escChar) {{{2 +" This function counts the number of substrings contained in another string. +" These substrings are only counted if they are not escaped with escChar +" Args: +" -str: the string to look for searchstr in +" -searchstr: the substring to search for in str +" -escChar: the escape character which, when preceding an instance of +" searchstr, will cause it not to be counted +function s:CountNonESCedOccurances(str, searchstr, escChar) + "get the index of the first occurrence of searchstr + let indx = stridx(a:str, a:searchstr) + + "if there is an instance of searchstr in str process it + if indx != -1 + "get the remainder of str after this instance of searchstr is removed + let lensearchstr = strlen(a:searchstr) + let strLeft = strpart(a:str, indx+lensearchstr) + + "if this instance of searchstr is not escaped, add one to the count + "and recurse. If it is escaped, just recurse + if !s:IsEscaped(a:str, indx, a:escChar) + return 1 + s:CountNonESCedOccurances(strLeft, a:searchstr, a:escChar) + else + return s:CountNonESCedOccurances(strLeft, a:searchstr, a:escChar) + endif + endif +endfunction +" Function: s:DoesBlockHaveDelim(delim, top, bottom) {{{2 +" Returns 1 if the given block of lines has a delimiter (a:delim) in it +" Args: +" -delim: the comment delimiter to check the block for +" -top: the top line number of the block +" -bottom: the bottom line number of the block +function s:DoesBlockHaveDelim(delim, top, bottom) + let currentLine = a:top + while currentLine < a:bottom + let theline = getline(currentLine) + if s:FindDelimiterIndex(a:delim, theline) != -1 + return 1 + endif + let currentLine = currentLine + 1 + endwhile + return 0 +endfunction + +" Function: s:DoesBlockHaveMultipartDelim(top, bottom) {{{2 +" Returns 1 if the given block has a >= 1 multipart delimiter in it +" Args: +" -top: the top line number of the block +" -bottom: the bottom line number of the block +function s:DoesBlockHaveMultipartDelim(top, bottom) + if s:HasMultipartDelims() + if s:Multipart() + return s:DoesBlockHaveDelim(b:left, a:top, a:bottom) || s:DoesBlockHaveDelim(b:right, a:top, a:bottom) + else + return s:DoesBlockHaveDelim(b:leftAlt, a:top, a:bottom) || s:DoesBlockHaveDelim(b:rightAlt, a:top, a:bottom) + endif + endif + return 0 +endfunction + + +" Function: s:Esc(str) {{{2 +" Escapes all the tricky chars in the given string +function s:Esc(str) + let charsToEsc = '*/\."&$+' + return escape(a:str, charsToEsc) +endfunction + +" Function: s:FindDelimiterIndex(delimiter, line) {{{2 +" This function is used to get the string index of the input comment delimiter +" on the input line. If no valid comment delimiter is found in the line then +" -1 is returned +" Args: +" -delimiter: the delimiter we are looking to find the index of +" -line: the line we are looking for delimiter on +function s:FindDelimiterIndex(delimiter, line) + + "make sure the delimiter isnt empty otherwise we go into an infinite loop. + if a:delimiter == "" + return -1 + endif + + + let l:delimiter = a:delimiter + let lenDel = strlen(l:delimiter) + + "get the index of the first occurrence of the delimiter + let delIndx = stridx(a:line, l:delimiter) + + "keep looping thru the line till we either find a real comment delimiter + "or run off the EOL + while delIndx != -1 + + "if we are not off the EOL get the str before the possible delimiter + "in question and check if it really is a delimiter. If it is, return + "its position + if delIndx != -1 + if s:IsDelimValid(l:delimiter, delIndx, a:line) + return delIndx + endif + endif + + "we have not yet found a real comment delimiter so move past the + "current one we are lookin at + let restOfLine = strpart(a:line, delIndx + lenDel) + let distToNextDelim = stridx(restOfLine , l:delimiter) + + "if distToNextDelim is -1 then there is no more potential delimiters + "on the line so set delIndx to -1. Otherwise, move along the line by + "distToNextDelim + if distToNextDelim == -1 + let delIndx = -1 + else + let delIndx = delIndx + lenDel + distToNextDelim + endif + endwhile + + "there is no comment delimiter on this line + return -1 +endfunction + +" Function: s:FindBoundingLinesOfSexyCom(lineNum) {{{2 +" This function takes in a line number and tests whether this line number is +" the top/bottom/middle line of a sexy comment. If it is then the top/bottom +" lines of the sexy comment are returned +" Args: +" -lineNum: the line number that is to be tested whether it is the +" top/bottom/middle line of a sexy com +" Returns: +" A string that has the top/bottom lines of the sexy comment encoded in it. +" The format is 'topline,bottomline'. If a:lineNum turns out not to be the +" top/bottom/middle of a sexy comment then -1 is returned +function s:FindBoundingLinesOfSexyCom(lineNum) + + "find which delimiters to look for as the start/end delims of the comment + let left = '' + let right = '' + if s:Multipart() + let left = s:GetLeft(0,0,1) + let right = s:GetRight(0,0,1) + elseif s:AltMultipart() + let left = s:GetLeft(1,0,1) + let right = s:GetRight(1,0,1) + else + return [] + endif + + let sexyComMarker = s:GetSexyComMarker(0, 1) + + "initialise the top/bottom line numbers of the sexy comment to -1 + let top = -1 + let bottom = -1 + + let currentLine = a:lineNum + while top == -1 || bottom == -1 + let theLine = getline(currentLine) + + "check if the current line is the top of the sexy comment + if currentLine <= a:lineNum && theLine =~ '^[ \t]*' . left && theLine !~ '.*' . right && currentLine < s:NumLinesInBuf() + let top = currentLine + let currentLine = a:lineNum + + "check if the current line is the bottom of the sexy comment + elseif theLine =~ '^[ \t]*' . right && theLine !~ '.*' . left && currentLine > 1 + let bottom = currentLine + + "the right delimiter is on the same line as the last sexyComMarker + elseif theLine =~ '^[ \t]*' . sexyComMarker . '.*' . right + let bottom = currentLine + + "we have not found the top or bottom line so we assume currentLine is an + "intermediate line and look to prove otherwise + else + + "if the line doesnt start with a sexyComMarker then it is not a sexy + "comment + if theLine !~ '^[ \t]*' . sexyComMarker + return [] + endif + + endif + + "if top is -1 then we havent found the top yet so keep looking up + if top == -1 + let currentLine = currentLine - 1 + "if we have found the top line then go down looking for the bottom + else + let currentLine = currentLine + 1 + endif + + endwhile + + return [top, bottom] +endfunction + + +" Function: s:GetLeft(alt, space, esc) {{{2 +" returns the left/left-alternative delimiter +" Args: +" -alt: specifies whether to get left or left-alternative delim +" -space: specifies whether the delim should be spaced or not +" (the space string will only be added if NERDSpaceDelims is set) +" -esc: specifies whether the tricky chars in the delim should be ESCed +function s:GetLeft(alt, space, esc) + let delim = b:left + + if a:alt + if b:leftAlt == '' + return '' + else + let delim = b:leftAlt + endif + endif + if delim == '' + return '' + endif + + if a:space && g:NERDSpaceDelims + let delim = delim . s:spaceStr + endif + + if a:esc + let delim = s:Esc(delim) + endif + + return delim +endfunction + +" Function: s:GetRight(alt, space, esc) {{{2 +" returns the right/right-alternative delimiter +" Args: +" -alt: specifies whether to get right or right-alternative delim +" -space: specifies whether the delim should be spaced or not +" (the space string will only be added if NERDSpaceDelims is set) +" -esc: specifies whether the tricky chars in the delim should be ESCed +function s:GetRight(alt, space, esc) + let delim = b:right + + if a:alt + if !s:AltMultipart() + return '' + else + let delim = b:rightAlt + endif + endif + if delim == '' + return '' + endif + + if a:space && g:NERDSpaceDelims + let delim = s:spaceStr . delim + endif + + if a:esc + let delim = s:Esc(delim) + endif + + return delim +endfunction + + +" Function: s:GetSexyComMarker() {{{2 +" Returns the sexy comment marker for the current filetype. +" +" C style sexy comments are assumed if possible. If not then the sexy comment +" marker is the last char of the delimiter pair that has both left and right +" delims and has the longest left delim +" +" Args: +" -space: specifies whether the marker is to have a space string after it +" (the space string will only be added if NERDSpaceDelims is set) +" -esc: specifies whether the tricky chars in the marker are to be ESCed +function s:GetSexyComMarker(space, esc) + let sexyComMarker = b:sexyComMarker + + "if there is no hardcoded marker then we find one + if sexyComMarker == '' + + "if the filetype has c style comments then use standard c sexy + "comments + if s:HasCStyleComments() + let sexyComMarker = '*' + else + "find a comment marker by getting the longest available left delim + "(that has a corresponding right delim) and taking the last char + let lenLeft = strlen(b:left) + let lenLeftAlt = strlen(b:leftAlt) + let left = '' + let right = '' + if s:Multipart() && lenLeft >= lenLeftAlt + let left = b:left + elseif s:AltMultipart() + let left = b:leftAlt + else + return -1 + endif + + "get the last char of left + let sexyComMarker = strpart(left, strlen(left)-1) + endif + endif + + if a:space && g:NERDSpaceDelims + let sexyComMarker = sexyComMarker . s:spaceStr + endif + + if a:esc + let sexyComMarker = s:Esc(sexyComMarker) + endif + + return sexyComMarker +endfunction + +" Function: s:GetSexyComLeft(space, esc) {{{2 +" Returns the left delimiter for sexy comments for this filetype or -1 if +" there is none. C style sexy comments are used if possible +" Args: +" -space: specifies if the delim has a space string on the end +" (the space string will only be added if NERDSpaceDelims is set) +" -esc: specifies whether the tricky chars in the string are ESCed +function s:GetSexyComLeft(space, esc) + let lenLeft = strlen(b:left) + let lenLeftAlt = strlen(b:leftAlt) + let left = '' + + "assume c style sexy comments if possible + if s:HasCStyleComments() + let left = '/*' + else + "grab the longest left delim that has a right + if s:Multipart() && lenLeft >= lenLeftAlt + let left = b:left + elseif s:AltMultipart() + let left = b:leftAlt + else + return -1 + endif + endif + + if a:space && g:NERDSpaceDelims + let left = left . s:spaceStr + endif + + if a:esc + let left = s:Esc(left) + endif + + return left +endfunction + +" Function: s:GetSexyComRight(space, esc) {{{2 +" Returns the right delimiter for sexy comments for this filetype or -1 if +" there is none. C style sexy comments are used if possible. +" Args: +" -space: specifies if the delim has a space string on the start +" (the space string will only be added if NERDSpaceDelims +" is specified for the current filetype) +" -esc: specifies whether the tricky chars in the string are ESCed +function s:GetSexyComRight(space, esc) + let lenLeft = strlen(b:left) + let lenLeftAlt = strlen(b:leftAlt) + let right = '' + + "assume c style sexy comments if possible + if s:HasCStyleComments() + let right = '*/' + else + "grab the right delim that pairs with the longest left delim + if s:Multipart() && lenLeft >= lenLeftAlt + let right = b:right + elseif s:AltMultipart() + let right = b:rightAlt + else + return -1 + endif + endif + + if a:space && g:NERDSpaceDelims + let right = s:spaceStr . right + endif + + if a:esc + let right = s:Esc(right) + endif + + return right +endfunction + +" Function: s:HasMultipartDelims() {{{2 +" Returns 1 iff the current filetype has at least one set of multipart delims +function s:HasMultipartDelims() + return s:Multipart() || s:AltMultipart() +endfunction + +" Function: s:HasLeadingTabs(...) {{{2 +" Returns 1 if any of the given strings have leading tabs +function s:HasLeadingTabs(...) + for s in a:000 + if s =~ '^\t.*' + return 1 + end + endfor + return 0 +endfunction +" Function: s:HasCStyleComments() {{{2 +" Returns 1 iff the current filetype has c style comment delimiters +function s:HasCStyleComments() + return (b:left == '/*' && b:right == '*/') || (b:leftAlt == '/*' && b:rightAlt == '*/') +endfunction + +" Function: s:IsCommentedNormOrSexy(lineNum) {{{2 +"This function is used to determine whether the given line is commented with +"either set of delimiters or if it is part of a sexy comment +" +" Args: +" -lineNum: the line number of the line to check +function s:IsCommentedNormOrSexy(lineNum) + let theLine = getline(a:lineNum) + + "if the line is commented normally return 1 + if s:IsCommented(b:left, b:right, theLine) || s:IsCommented(b:leftAlt, b:rightAlt, theLine) + return 1 + endif + + "if the line is part of a sexy comment return 1 + if s:IsInSexyComment(a:lineNum) + return 1 + endif + return 0 +endfunction + +" Function: s:IsCommented(left, right, line) {{{2 +"This function is used to determine whether the given line is commented with +"the given delimiters +" +" Args: +" -line: the line that to check if commented +" -left/right: the left and right delimiters to check for +function s:IsCommented(left, right, line) + "if the line isnt commented return true + if s:FindDelimiterIndex(a:left, a:line) != -1 && (s:FindDelimiterIndex(a:right, a:line) != -1 || !s:Multipart()) + return 1 + endif + return 0 +endfunction + +" Function: s:IsCommentedFromStartOfLine(left, line) {{{2 +"This function is used to determine whether the given line is commented with +"the given delimiters at the start of the line i.e the left delimiter is the +"first thing on the line (apart from spaces\tabs) +" +" Args: +" -line: the line that to check if commented +" -left: the left delimiter to check for +function s:IsCommentedFromStartOfLine(left, line) + let theLine = s:ConvertLeadingTabsToSpaces(a:line) + let numSpaces = strlen(substitute(theLine, '^\( *\).*$', '\1', '')) + let delimIndx = s:FindDelimiterIndex(a:left, theLine) + return delimIndx == numSpaces +endfunction + +" Function: s:IsCommentedOuttermost(left, right, leftAlt, rightAlt, line) {{{2 +" Finds the type of the outtermost delims on the line +" +" Args: +" -line: the line that to check if the outtermost comments on it are +" left/right +" -left/right: the left and right delimiters to check for +" -leftAlt/rightAlt: the left and right alternative delimiters to check for +" +" Returns: +" 0 if the line is not commented with either set of delims +" 1 if the line is commented with the left/right delim set +" 2 if the line is commented with the leftAlt/rightAlt delim set +function s:IsCommentedOuttermost(left, right, leftAlt, rightAlt, line) + "get the first positions of the left delims and the last positions of the + "right delims + let indxLeft = s:FindDelimiterIndex(a:left, a:line) + let indxLeftAlt = s:FindDelimiterIndex(a:leftAlt, a:line) + let indxRight = s:LastIndexOfDelim(a:right, a:line) + let indxRightAlt = s:LastIndexOfDelim(a:rightAlt, a:line) + + "check if the line has a left delim before a leftAlt delim + if (indxLeft <= indxLeftAlt || indxLeftAlt == -1) && indxLeft != -1 + "check if the line has a right delim after any rightAlt delim + if (indxRight > indxRightAlt && indxRight > indxLeft) || !s:Multipart() + return 1 + endif + + "check if the line has a leftAlt delim before a left delim + elseif (indxLeftAlt <= indxLeft || indxLeft == -1) && indxLeftAlt != -1 + "check if the line has a rightAlt delim after any right delim + if (indxRightAlt > indxRight && indxRightAlt > indxLeftAlt) || !s:AltMultipart() + return 2 + endif + else + return 0 + endif + + return 0 + +endfunction + + +" Function: s:IsDelimValid(delimiter, delIndx, line) {{{2 +" This function is responsible for determining whether a given instance of a +" comment delimiter is a real delimiter or not. For example, in java the +" // string is a comment delimiter but in the line: +" System.out.println("//"); +" it does not count as a comment delimiter. This function is responsible for +" distinguishing between such cases. It does so by applying a set of +" heuristics that are not fool proof but should work most of the time. +" +" Args: +" -delimiter: the delimiter we are validating +" -delIndx: the position of delimiter in line +" -line: the line that delimiter occurs in +" +" Returns: +" 0 if the given delimiter is not a real delimiter (as far as we can tell) , +" 1 otherwise +function s:IsDelimValid(delimiter, delIndx, line) + "get the delimiter without the escchars + let l:delimiter = a:delimiter + + "get the strings before and after the delimiter + let preComStr = strpart(a:line, 0, a:delIndx) + let postComStr = strpart(a:line, a:delIndx+strlen(delimiter)) + + "to check if the delimiter is real, make sure it isnt preceded by + "an odd number of quotes and followed by the same (which would indicate + "that it is part of a string and therefore is not a comment) + if !s:IsNumEven(s:CountNonESCedOccurances(preComStr, '"', "\\")) && !s:IsNumEven(s:CountNonESCedOccurances(postComStr, '"', "\\")) + return 0 + endif + if !s:IsNumEven(s:CountNonESCedOccurances(preComStr, "'", "\\")) && !s:IsNumEven(s:CountNonESCedOccurances(postComStr, "'", "\\")) + return 0 + endif + if !s:IsNumEven(s:CountNonESCedOccurances(preComStr, "`", "\\")) && !s:IsNumEven(s:CountNonESCedOccurances(postComStr, "`", "\\")) + return 0 + endif + + + "if the comment delimiter is escaped, assume it isnt a real delimiter + if s:IsEscaped(a:line, a:delIndx, "\\") + return 0 + endif + + "vim comments are so fuckin stupid!! Why the hell do they have comment + "delimiters that are used elsewhere in the syntax?!?! We need to check + "some conditions especially for vim + if &filetype == "vim" + if !s:IsNumEven(s:CountNonESCedOccurances(preComStr, '"', "\\")) + return 0 + endif + + "if the delimiter is on the very first char of the line or is the + "first non-tab/space char on the line then it is a valid comment delimiter + if a:delIndx == 0 || a:line =~ "^[ \t]\\{" . a:delIndx . "\\}\".*$" + return 1 + endif + + let numLeftParen =s:CountNonESCedOccurances(preComStr, "(", "\\") + let numRightParen =s:CountNonESCedOccurances(preComStr, ")", "\\") + + "if the quote is inside brackets then assume it isnt a comment + if numLeftParen > numRightParen + return 0 + endif + + "if the line has an even num of unescaped "'s then we can assume that + "any given " is not a comment delimiter + if s:IsNumEven(s:CountNonESCedOccurances(a:line, "\"", "\\")) + return 0 + endif + endif + + return 1 + +endfunction + +" Function: s:IsNumEven(num) {{{2 +" A small function the returns 1 if the input number is even and 0 otherwise +" Args: +" -num: the number to check +function s:IsNumEven(num) + return (a:num % 2) == 0 +endfunction + +" Function: s:IsEscaped(str, indx, escChar) {{{2 +" This function takes a string, an index into that string and an esc char and +" returns 1 if the char at the index is escaped (i.e if it is preceded by an +" odd number of esc chars) +" Args: +" -str: the string to check +" -indx: the index into str that we want to check +" -escChar: the escape char the char at indx may be ESCed with +function s:IsEscaped(str, indx, escChar) + "initialise numEscChars to 0 and look at the char before indx + let numEscChars = 0 + let curIndx = a:indx-1 + + "keep going back thru str until we either reach the start of the str or + "run out of esc chars + while curIndx >= 0 && strpart(a:str, curIndx, 1) == a:escChar + + "we have found another esc char so add one to the count and move left + "one char + let numEscChars = numEscChars + 1 + let curIndx = curIndx - 1 + + endwhile + + "if there is an odd num of esc chars directly before the char at indx then + "the char at indx is escaped + return !s:IsNumEven(numEscChars) +endfunction + +" Function: s:IsInSexyComment(line) {{{2 +" returns 1 if the given line number is part of a sexy comment +function s:IsInSexyComment(line) + return !empty(s:FindBoundingLinesOfSexyCom(a:line)) +endfunction + +" Function: s:IsSexyComment(topline, bottomline) {{{2 +" This function takes in 2 line numbers and returns 1 if the lines between and +" including the given line numbers are a sexy comment. It returns 0 otherwise. +" Args: +" -topline: the line that the possible sexy comment starts on +" -bottomline: the line that the possible sexy comment stops on +function s:IsSexyComment(topline, bottomline) + + "get the delim set that would be used for a sexy comment + let left = '' + let right = '' + if s:Multipart() + let left = b:left + let right = b:right + elseif s:AltMultipart() + let left = b:leftAlt + let right = b:rightAlt + else + return 0 + endif + + "swap the top and bottom line numbers around if need be + let topline = a:topline + let bottomline = a:bottomline + if bottomline < topline + topline = bottomline + bottomline = a:topline + endif + + "if there is < 2 lines in the comment it cannot be sexy + if (bottomline - topline) <= 0 + return 0 + endif + + "if the top line doesnt begin with a left delim then the comment isnt sexy + if getline(a:topline) !~ '^[ \t]*' . left + return 0 + endif + + "if there is a right delim on the top line then this isnt a sexy comment + if s:FindDelimiterIndex(right, getline(a:topline)) != -1 + return 0 + endif + + "if there is a left delim on the bottom line then this isnt a sexy comment + if s:FindDelimiterIndex(left, getline(a:bottomline)) != -1 + return 0 + endif + + "if the bottom line doesnt begin with a right delim then the comment isnt + "sexy + if getline(a:bottomline) !~ '^.*' . right . '$' + return 0 + endif + + let sexyComMarker = s:GetSexyComMarker(0, 1) + + "check each of the intermediate lines to make sure they start with a + "sexyComMarker + let currentLine = a:topline+1 + while currentLine < a:bottomline + let theLine = getline(currentLine) + + if theLine !~ '^[ \t]*' . sexyComMarker + return 0 + endif + + "if there is a right delim in an intermediate line then the block isnt + "a sexy comment + if s:FindDelimiterIndex(right, theLine) != -1 + return 0 + endif + + let currentLine = currentLine + 1 + endwhile + + "we have not found anything to suggest that this isnt a sexy comment so + return 1 + +endfunction + +" Function: s:LastIndexOfDelim(delim, str) {{{2 +" This function takes a string and a delimiter and returns the last index of +" that delimiter in string +" Args: +" -delim: the delimiter to look for +" -str: the string to look for delim in +function s:LastIndexOfDelim(delim, str) + let delim = a:delim + let lenDelim = strlen(delim) + + "set index to the first occurrence of delim. If there is no occurrence then + "bail + let indx = s:FindDelimiterIndex(delim, a:str) + if indx == -1 + return -1 + endif + + "keep moving to the next instance of delim in str till there is none left + while 1 + + "search for the next delim after the previous one + let searchStr = strpart(a:str, indx+lenDelim) + let indx2 = s:FindDelimiterIndex(delim, searchStr) + + "if we find a delim update indx to record the position of it, if we + "dont find another delim then indx is the last one so break out of + "this loop + if indx2 != -1 + let indx = indx + indx2 + lenDelim + else + break + endif + endwhile + + return indx + +endfunction + +" Function: s:LeftMostIndx(countCommentedLines, countEmptyLines, topline, bottomline) {{{2 +" This function takes in 2 line numbers and returns the index of the left most +" char (that is not a space or a tab) on all of these lines. +" Args: +" -countCommentedLines: 1 if lines that are commented are to be checked as +" well. 0 otherwise +" -countEmptyLines: 1 if empty lines are to be counted in the search +" -topline: the top line to be checked +" -bottomline: the bottom line to be checked +function s:LeftMostIndx(countCommentedLines, countEmptyLines, topline, bottomline) + + " declare the left most index as an extreme value + let leftMostIndx = 1000 + + " go thru the block line by line updating leftMostIndx + let currentLine = a:topline + while currentLine <= a:bottomline + + " get the next line and if it is allowed to be commented, or is not + " commented, check it + let theLine = getline(currentLine) + if a:countEmptyLines || theLine !~ '^[ \t]*$' + if a:countCommentedLines || (!s:IsCommented(b:left, b:right, theLine) && !s:IsCommented(b:leftAlt, b:rightAlt, theLine)) + " convert spaces to tabs and get the number of leading spaces for + " this line and update leftMostIndx if need be + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + let leadSpaceOfLine = strlen( substitute(theLine, '\(^[ \t]*\).*$','\1','') ) + if leadSpaceOfLine < leftMostIndx + let leftMostIndx = leadSpaceOfLine + endif + endif + endif + + " move on to the next line + let currentLine = currentLine + 1 + endwhile + + if leftMostIndx == 1000 + return 0 + else + return leftMostIndx + endif +endfunction + +" Function: s:Multipart() {{{2 +" returns 1 if the current delims are multipart +function s:Multipart() + return b:right != '' +endfunction + +" Function: s:NerdEcho(msg, typeOfMsg) {{{2 +" Args: +" -msg: the message to echo +" -typeOfMsg: 0 = warning message +" 1 = normal message +function s:NerdEcho(msg, typeOfMsg) + if a:typeOfMsg == 0 + echohl WarningMsg + echo 'NERDCommenter:' . a:msg + echohl None + elseif a:typeOfMsg == 1 + echo 'NERDCommenter:' . a:msg + endif +endfunction + +" Function: s:NumberOfLeadingTabs(s) {{{2 +" returns the number of leading tabs in the given string +function s:NumberOfLeadingTabs(s) + return strlen(substitute(a:s, '^\(\t*\).*$', '\1', "")) +endfunction + +" Function: s:NumLinesInBuf() {{{2 +" Returns the number of lines in the current buffer +function s:NumLinesInBuf() + return line('$') +endfunction + +" Function: s:ReplaceDelims(toReplace1, toReplace2, replacor1, replacor2, str) {{{2 +" This function takes in a string, 2 delimiters in that string and 2 strings +" to replace these delimiters with. +" +" Args: +" -toReplace1: the first delimiter to replace +" -toReplace2: the second delimiter to replace +" -replacor1: the string to replace toReplace1 with +" -replacor2: the string to replace toReplace2 with +" -str: the string that the delimiters to be replaced are in +function s:ReplaceDelims(toReplace1, toReplace2, replacor1, replacor2, str) + let line = s:ReplaceLeftMostDelim(a:toReplace1, a:replacor1, a:str) + let line = s:ReplaceRightMostDelim(a:toReplace2, a:replacor2, line) + return line +endfunction + +" Function: s:ReplaceLeftMostDelim(toReplace, replacor, str) {{{2 +" This function takes a string and a delimiter and replaces the left most +" occurrence of this delimiter in the string with a given string +" +" Args: +" -toReplace: the delimiter in str that is to be replaced +" -replacor: the string to replace toReplace with +" -str: the string that contains toReplace +function s:ReplaceLeftMostDelim(toReplace, replacor, str) + let toReplace = a:toReplace + let replacor = a:replacor + "get the left most occurrence of toReplace + let indxToReplace = s:FindDelimiterIndex(toReplace, a:str) + + "if there IS an occurrence of toReplace in str then replace it and return + "the resulting string + if indxToReplace != -1 + let line = strpart(a:str, 0, indxToReplace) . replacor . strpart(a:str, indxToReplace+strlen(toReplace)) + return line + endif + + return a:str +endfunction + +" Function: s:ReplaceRightMostDelim(toReplace, replacor, str) {{{2 +" This function takes a string and a delimiter and replaces the right most +" occurrence of this delimiter in the string with a given string +" +" Args: +" -toReplace: the delimiter in str that is to be replaced +" -replacor: the string to replace toReplace with +" -str: the string that contains toReplace +" +function s:ReplaceRightMostDelim(toReplace, replacor, str) + let toReplace = a:toReplace + let replacor = a:replacor + let lenToReplace = strlen(toReplace) + + "get the index of the last delim in str + let indxToReplace = s:LastIndexOfDelim(toReplace, a:str) + + "if there IS a delimiter in str, replace it and return the result + let line = a:str + if indxToReplace != -1 + let line = strpart(a:str, 0, indxToReplace) . replacor . strpart(a:str, indxToReplace+strlen(toReplace)) + endif + return line +endfunction + +"FUNCTION: s:RestoreScreenState() {{{2 +" +"Sets the screen state back to what it was when s:SaveScreenState was last +"called. +" +function s:RestoreScreenState() + if !exists("t:NERDComOldTopLine") || !exists("t:NERDComOldPos") + throw 'NERDCommenter exception: cannot restore screen' + endif + + call cursor(t:NERDComOldTopLine, 0) + normal! zt + call setpos(".", t:NERDComOldPos) +endfunction + +" Function: s:RightMostIndx(countCommentedLines, countEmptyLines, topline, bottomline) {{{2 +" This function takes in 2 line numbers and returns the index of the right most +" char on all of these lines. +" Args: +" -countCommentedLines: 1 if lines that are commented are to be checked as +" well. 0 otherwise +" -countEmptyLines: 1 if empty lines are to be counted in the search +" -topline: the top line to be checked +" -bottomline: the bottom line to be checked +function s:RightMostIndx(countCommentedLines, countEmptyLines, topline, bottomline) + let rightMostIndx = -1 + + " go thru the block line by line updating rightMostIndx + let currentLine = a:topline + while currentLine <= a:bottomline + + " get the next line and see if it is commentable, otherwise it doesnt + " count + let theLine = getline(currentLine) + if a:countEmptyLines || theLine !~ '^[ \t]*$' + + if a:countCommentedLines || (!s:IsCommented(b:left, b:right, theLine) && !s:IsCommented(b:leftAlt, b:rightAlt, theLine)) + + " update rightMostIndx if need be + let theLine = s:ConvertLeadingTabsToSpaces(theLine) + let lineLen = strlen(theLine) + if lineLen > rightMostIndx + let rightMostIndx = lineLen + endif + endif + endif + + " move on to the next line + let currentLine = currentLine + 1 + endwhile + + return rightMostIndx +endfunction + +"FUNCTION: s:SaveScreenState() {{{2 +"Saves the current cursor position in the current buffer and the window +"scroll position +function s:SaveScreenState() + let t:NERDComOldPos = getpos(".") + let t:NERDComOldTopLine = line("w0") +endfunction + +" Function: s:SwapOutterMultiPartDelimsForPlaceHolders(line) {{{2 +" This function takes a line and swaps the outter most multi-part delims for +" place holders +" Args: +" -line: the line to swap the delims in +" +function s:SwapOutterMultiPartDelimsForPlaceHolders(line) + " find out if the line is commented using normal delims and/or + " alternate ones + let isCommented = s:IsCommented(b:left, b:right, a:line) + let isCommentedAlt = s:IsCommented(b:leftAlt, b:rightAlt, a:line) + + let line2 = a:line + + "if the line is commented and there is a right delimiter, replace + "the delims with place-holders + if isCommented && s:Multipart() + let line2 = s:ReplaceDelims(b:left, b:right, g:NERDLPlace, g:NERDRPlace, a:line) + + "similarly if the line is commented with the alternative + "delimiters + elseif isCommentedAlt && s:AltMultipart() + let line2 = s:ReplaceDelims(b:leftAlt, b:rightAlt, g:NERDLPlace, g:NERDRPlace, a:line) + endif + + return line2 +endfunction + +" Function: s:SwapOutterPlaceHoldersForMultiPartDelims(line) {{{2 +" This function takes a line and swaps the outtermost place holders for +" multi-part delims +" Args: +" -line: the line to swap the delims in +" +function s:SwapOutterPlaceHoldersForMultiPartDelims(line) + let left = '' + let right = '' + if s:Multipart() + let left = b:left + let right = b:right + elseif s:AltMultipart() + let left = b:leftAlt + let right = b:rightAlt + endif + + let line = s:ReplaceDelims(g:NERDLPlace, g:NERDRPlace, left, right, a:line) + return line +endfunction +" Function: s:TabbedCol(line, col) {{{2 +" Gets the col number for given line and existing col number. The new col +" number is the col number when all leading spaces are converted to tabs +" Args: +" -line:the line to get the rel col for +" -col: the abs col +function s:TabbedCol(line, col) + let lineTruncated = strpart(a:line, 0, a:col) + let lineSpacesToTabs = substitute(lineTruncated, s:TabSpace(), '\t', 'g') + return strlen(lineSpacesToTabs) +endfunction +"FUNCTION: s:TabSpace() {{{2 +"returns a string of spaces equal in length to &tabstop +function s:TabSpace() + let tabSpace = "" + let spacesPerTab = &tabstop + while spacesPerTab > 0 + let tabSpace = tabSpace . " " + let spacesPerTab = spacesPerTab - 1 + endwhile + return tabSpace +endfunction + +" Function: s:UnEsc(str, escChar) {{{2 +" This function removes all the escape chars from a string +" Args: +" -str: the string to remove esc chars from +" -escChar: the escape char to be removed +function s:UnEsc(str, escChar) + return substitute(a:str, a:escChar, "", "g") +endfunction + +" Function: s:UntabbedCol(line, col) {{{2 +" Takes a line and a col and returns the absolute column of col taking into +" account that a tab is worth 3 or 4 (or whatever) spaces. +" Args: +" -line:the line to get the abs col for +" -col: the col that doesnt take into account tabs +function s:UntabbedCol(line, col) + let lineTruncated = strpart(a:line, 0, a:col) + let lineTabsToSpaces = substitute(lineTruncated, '\t', s:TabSpace(), 'g') + return strlen(lineTabsToSpaces) +endfunction +" Section: Comment mapping setup {{{1 +" =========================================================================== + +" switch to/from alternative delimiters +nnoremap NERDCommenterAltDelims :call SwitchToAlternativeDelimiters(1) + +" comment out lines +nnoremap NERDCommenterComment :call NERDComment(0, "norm") +vnoremap NERDCommenterComment :call NERDComment(1, "norm") + +" toggle comments +nnoremap NERDCommenterToggle :call NERDComment(0, "toggle") +vnoremap NERDCommenterToggle :call NERDComment(1, "toggle") + +" minimal comments +nnoremap NERDCommenterMinimal :call NERDComment(0, "minimal") +vnoremap NERDCommenterMinimal :call NERDComment(1, "minimal") + +" sexy comments +nnoremap NERDCommenterSexy :call NERDComment(0, "sexy") +vnoremap NERDCommenterSexy :call NERDComment(1, "sexy") + +" invert comments +nnoremap NERDCommenterInvert :call NERDComment(0, "invert") +vnoremap NERDCommenterInvert :call NERDComment(1, "invert") + +" yank then comment +nmap NERDCommenterYank :call NERDComment(0, "yank") +vmap NERDCommenterYank :call NERDComment(1, "yank") + +" left aligned comments +nnoremap NERDCommenterAlignLeft :call NERDComment(0, "alignLeft") +vnoremap NERDCommenterAlignLeft :call NERDComment(1, "alignLeft") + +" left and right aligned comments +nnoremap NERDCommenterAlignBoth :call NERDComment(0, "alignBoth") +vnoremap NERDCommenterAlignBoth :call NERDComment(1, "alignBoth") + +" nested comments +nnoremap NERDCommenterNest :call NERDComment(0, "nested") +vnoremap NERDCommenterNest :call NERDComment(1, "nested") + +" uncomment +nnoremap NERDCommenterUncomment :call NERDComment(0, "uncomment") +vnoremap NERDCommenterUncomment :call NERDComment(1, "uncomment") + +" comment till the end of the line +nnoremap NERDCommenterToEOL :call NERDComment(0, "toEOL") + +" append comments +nmap NERDCommenterAppend :call NERDComment(0, "append") + +" insert comments +inoremap NERDCommenterInInsert :call NERDComment(0, "insert") + + +function! s:CreateMaps(target, combo) + if !hasmapto(a:target, 'n') + exec 'nmap ' . a:combo . ' ' . a:target + endif + + if !hasmapto(a:target, 'v') + exec 'vmap ' . a:combo . ' ' . a:target + endif +endfunction + +if g:NERDCreateDefaultMappings + call s:CreateMaps('NERDCommenterComment', ',cc') + call s:CreateMaps('NERDCommenterToggle', ',c') + call s:CreateMaps('NERDCommenterMinimal', ',cm') + call s:CreateMaps('NERDCommenterSexy', ',cs') + call s:CreateMaps('NERDCommenterInvert', ',ci') + call s:CreateMaps('NERDCommenterYank', ',cy') + call s:CreateMaps('NERDCommenterAlignLeft', ',cl') + call s:CreateMaps('NERDCommenterAlignBoth', ',cb') + call s:CreateMaps('NERDCommenterNest', ',cn') + call s:CreateMaps('NERDCommenterUncomment', ',cu') + call s:CreateMaps('NERDCommenterToEOL', ',c$') + call s:CreateMaps('NERDCommenterAppend', ',cA') + + if !hasmapto('NERDCommenterAltDelims', 'n') + nmap ,ca NERDCommenterAltDelims + endif +endif + + + +" Section: Menu item setup {{{1 +" =========================================================================== +"check if the user wants the menu to be displayed +if g:NERDMenuMode != 0 + + let menuRoot = "" + if g:NERDMenuMode == 1 + let menuRoot = 'comment' + elseif g:NERDMenuMode == 2 + let menuRoot = '&comment' + elseif g:NERDMenuMode == 3 + let menuRoot = '&Plugin.&comment' + endif + + function! s:CreateMenuItems(target, desc, root) + exec 'nmenu ' . a:root . '.' . a:desc . ' ' . a:target + exec 'vmenu ' . a:root . '.' . a:desc . ' ' . a:target + endfunction + call s:CreateMenuItems("NERDCommenterComment", 'Comment', menuRoot) + call s:CreateMenuItems("NERDCommenterToggle", 'Toggle', menuRoot) + call s:CreateMenuItems('NERDCommenterMinimal', 'Minimal', menuRoot) + call s:CreateMenuItems('NERDCommenterNest', 'Nested', menuRoot) + exec 'nmenu '. menuRoot .'.To\ EOL NERDCommenterToEOL' + call s:CreateMenuItems('NERDCommenterInvert', 'Invert', menuRoot) + call s:CreateMenuItems('NERDCommenterSexy', 'Sexy', menuRoot) + call s:CreateMenuItems('NERDCommenterYank', 'Yank\ then\ comment', menuRoot) + exec 'nmenu '. menuRoot .'.Append NERDCommenterAppend' + exec 'menu '. menuRoot .'.-Sep- :' + call s:CreateMenuItems('NERDCommenterAlignLeft', 'Left\ aligned', menuRoot) + call s:CreateMenuItems('NERDCommenterAlignBoth', 'Left\ and\ right\ aligned', menuRoot) + exec 'menu '. menuRoot .'.-Sep2- :' + call s:CreateMenuItems('NERDCommenterUncomment', 'Uncomment', menuRoot) + exec 'nmenu '. menuRoot .'.Switch\ Delimiters NERDCommenterAltDelims' + exec 'imenu '. menuRoot .'.Insert\ Comment\ Here NERDCommenterInInsert' + exec 'menu '. menuRoot .'.-Sep3- :' + exec 'menu '. menuRoot .'.Help :help NERDCommenterContents' +endif +" vim: set foldmethod=marker : diff --git a/files/.vim/plugin/NERD_tree.vim b/files/.vim/plugin/NERD_tree.vim new file mode 100755 index 0000000..96a38eb --- /dev/null +++ b/files/.vim/plugin/NERD_tree.vim @@ -0,0 +1,3559 @@ +" ============================================================================ +" File: NERD_tree.vim +" Description: vim global plugin that provides a nice tree explorer +" Maintainer: Martin Grenfell +" Last Change: 29 October, 2008 +" License: This program is free software. It comes without any warranty, +" to the extent permitted by applicable law. You can redistribute +" it and/or modify it under the terms of the Do What The Fuck You +" Want To Public License, Version 2, as published by Sam Hocevar. +" See http://sam.zoy.org/wtfpl/COPYING for more details. +" +" ============================================================================ +let s:NERD_tree_version = '2.14.2' + +" SECTION: Script init stuff {{{1 +"============================================================ +if exists("loaded_nerd_tree") + finish +endif +if v:version < 700 + echoerr "NERDTree: this plugin requires vim >= 7. DOWNLOAD IT! You'll thank me later!" + finish +endif +let loaded_nerd_tree = 1 + +"for line continuation - i.e dont want C in &cpo +let s:old_cpo = &cpo +set cpo&vim + +"Function: s:initVariable() function {{{2 +"This function is used to initialise a given variable to a given value. The +"variable is only initialised if it does not exist prior +" +"Args: +"var: the name of the var to be initialised +"value: the value to initialise var to +" +"Returns: +"1 if the var is set, 0 otherwise +function! s:initVariable(var, value) + if !exists(a:var) + exec 'let ' . a:var . ' = ' . "'" . a:value . "'" + return 1 + endif + return 0 +endfunction + +"SECTION: Init variable calls and other random constants {{{2 +call s:initVariable("g:NERDChristmasTree", 1) +call s:initVariable("g:NERDTreeAutoCenter", 1) +call s:initVariable("g:NERDTreeAutoCenterThreshold", 3) +call s:initVariable("g:NERDTreeCaseSensitiveSort", 0) +call s:initVariable("g:NERDTreeChDirMode", 0) +if !exists("g:NERDTreeIgnore") + let g:NERDTreeIgnore = ['\~$'] +endif +call s:initVariable("g:NERDTreeHighlightCursorline", 1) +call s:initVariable("g:NERDTreeBookmarksFile", expand('$HOME') . '/.NERDTreeBookmarks') +call s:initVariable("g:NERDTreeMouseMode", 1) +call s:initVariable("g:NERDTreeNotificationThreshold", 100) +call s:initVariable("g:NERDTreeQuitOnOpen", 0) +call s:initVariable("g:NERDTreeShowBookmarks", 0) +call s:initVariable("g:NERDTreeShowFiles", 1) +call s:initVariable("g:NERDTreeShowHidden", 0) +call s:initVariable("g:NERDTreeShowLineNumbers", 0) +call s:initVariable("g:NERDTreeSortDirs", 1) + +if !exists("g:NERDTreeSortOrder") + let g:NERDTreeSortOrder = ['\/$', '*', '\.swp$', '\.bak$', '\~$'] +else + "if there isnt a * in the sort sequence then add one + if count(g:NERDTreeSortOrder, '*') < 1 + call add(g:NERDTreeSortOrder, '*') + endif +endif + +"we need to use this number many times for sorting... so we calculate it only +"once here +let s:NERDTreeSortStarIndex = index(g:NERDTreeSortOrder, '*') + +call s:initVariable("g:NERDTreeWinPos", "left") +call s:initVariable("g:NERDTreeWinSize", 31) + +let s:running_windows = has("win16") || has("win32") || has("win64") + +"init the shell commands that will be used to copy nodes, and remove dir trees +" +"Note: the space after the command is important +if s:running_windows + call s:initVariable("g:NERDTreeRemoveDirCmd", 'rmdir /s /q ') +else + call s:initVariable("g:NERDTreeRemoveDirCmd", 'rm -rf ') + call s:initVariable("g:NERDTreeCopyCmd", 'cp -r ') +endif + + +"SECTION: Init variable calls for key mappings {{{2 +call s:initVariable("g:NERDTreeMapActivateNode", "o") +call s:initVariable("g:NERDTreeMapChangeRoot", "C") +call s:initVariable("g:NERDTreeMapChdir", "cd") +call s:initVariable("g:NERDTreeMapCloseChildren", "X") +call s:initVariable("g:NERDTreeMapCloseDir", "x") +call s:initVariable("g:NERDTreeMapDeleteBookmark", "D") +call s:initVariable("g:NERDTreeMapExecute", "!") +call s:initVariable("g:NERDTreeMapFilesystemMenu", "m") +call s:initVariable("g:NERDTreeMapHelp", "?") +call s:initVariable("g:NERDTreeMapJumpFirstChild", "K") +call s:initVariable("g:NERDTreeMapJumpLastChild", "J") +call s:initVariable("g:NERDTreeMapJumpNextSibling", "") +call s:initVariable("g:NERDTreeMapJumpParent", "p") +call s:initVariable("g:NERDTreeMapJumpPrevSibling", "") +call s:initVariable("g:NERDTreeMapJumpRoot", "P") +call s:initVariable("g:NERDTreeMapOpenExpl", "e") +call s:initVariable("g:NERDTreeMapOpenInTab", "t") +call s:initVariable("g:NERDTreeMapOpenInTabSilent", "T") +call s:initVariable("g:NERDTreeMapOpenRecursively", "O") +call s:initVariable("g:NERDTreeMapOpenSplit", "") +call s:initVariable("g:NERDTreeMapPreview", "g" . NERDTreeMapActivateNode) +call s:initVariable("g:NERDTreeMapPreviewSplit", "g" . NERDTreeMapOpenSplit) +call s:initVariable("g:NERDTreeMapQuit", "q") +call s:initVariable("g:NERDTreeMapRefresh", "r") +call s:initVariable("g:NERDTreeMapRefreshRoot", "R") +call s:initVariable("g:NERDTreeMapToggleBookmarks", "B") +call s:initVariable("g:NERDTreeMapToggleFiles", "F") +call s:initVariable("g:NERDTreeMapToggleFilters", "f") +call s:initVariable("g:NERDTreeMapToggleHidden", "H") +call s:initVariable("g:NERDTreeMapUpdir", "u") +call s:initVariable("g:NERDTreeMapUpdirKeepOpen", "U") + +"SECTION: Script level variable declaration{{{2 +let s:escape_chars = " \\`\|\"#%&,?()\*^<>" +let s:NERDTreeWinName = '_NERD_tree_' + +let s:tree_wid = 2 +let s:tree_markup_reg = '^[ `|]*[\-+~]' +let s:tree_up_dir_line = '.. (up a dir)' + +let s:os_slash = '/' +if s:running_windows + let s:os_slash = '\' +endif + +" SECTION: Commands {{{1 +"============================================================ +"init the command that users start the nerd tree with +command! -n=? -complete=dir NERDTree :call s:initNerdTree('') +command! -n=? -complete=dir NERDTreeToggle :call s:toggle('') +command! -n=0 NERDTreeClose :call s:closeTreeIfOpen() +command! -n=1 -complete=customlist,s:completeBookmarks NERDTreeFromBookmark call s:initNerdTree('') +" SECTION: Auto commands {{{1 +"============================================================ +"Save the cursor position whenever we close the nerd tree +exec "autocmd BufWinLeave *". s:NERDTreeWinName ." call saveScreenState()" +"cache bookmarks when vim loads +autocmd VimEnter * call s:Bookmark.CacheBookmarks(0) + +"SECTION: Classes {{{1 +"============================================================ +"CLASS: Bookmark {{{2 +"============================================================ +let s:Bookmark = {} +" FUNCTION: Bookmark.AddBookmark(name, path) {{{3 +" Class method to add a new bookmark to the list, if a previous bookmark exists +" with the same name, just update the path for that bookmark +function! s:Bookmark.AddBookmark(name, path) + for i in s:Bookmark.Bookmarks() + if i.name == a:name + let i.path = a:path + return + endif + endfor + call add(s:Bookmark.Bookmarks(), s:Bookmark.New(a:name, a:path)) + call s:Bookmark.Sort() +endfunction +" Function: Bookmark.Bookmarks() {{{3 +" Class method to get all bookmarks. Lazily initializes the bookmarks global +" variable +function! s:Bookmark.Bookmarks() + if !exists("g:NERDTreeBookmarks") + let g:NERDTreeBookmarks = [] + endif + return g:NERDTreeBookmarks +endfunction +" Function: Bookmark.BookmarkExistsFor(name) {{{3 +" class method that returns 1 if a bookmark with the given name is found, 0 +" otherwise +function! s:Bookmark.BookmarkExistsFor(name) + try + call s:Bookmark.BookmarkFor(a:name) + return 1 + catch /NERDTree.BookmarkNotFound/ + return 0 + endtry +endfunction +" Function: Bookmark.BookmarkFor(name) {{{3 +" Class method to get the bookmark that has the given name. {} is return if no +" bookmark is found +function! s:Bookmark.BookmarkFor(name) + for i in s:Bookmark.Bookmarks() + if i.name == a:name + return i + endif + endfor + throw "NERDTree.BookmarkNotFound exception: no bookmark found for name: \"". a:name .'"' +endfunction +" Function: Bookmark.BookmarkNames() {{{3 +" Class method to return an array of all bookmark names +function! s:Bookmark.BookmarkNames() + let names = [] + for i in s:Bookmark.Bookmarks() + call add(names, i.name) + endfor + return names +endfunction +" FUNCTION: Bookmark.CacheBookmarks(silent) {{{3 +" Class method to read all bookmarks from the bookmarks file intialize +" bookmark objects for each one. +" +" Args: +" silent - dont echo an error msg if invalid bookmarks are found +function! s:Bookmark.CacheBookmarks(silent) + if filereadable(g:NERDTreeBookmarksFile) + let g:NERDTreeBookmarks = [] + let g:NERDTreeInvalidBookmarks = [] + let bookmarkStrings = readfile(g:NERDTreeBookmarksFile) + let invalidBookmarksFound = 0 + for i in bookmarkStrings + + "ignore blank lines + if i != '' + + let name = substitute(i, '^\(.\{-}\) .*$', '\1', '') + let path = substitute(i, '^.\{-} \(.*\)$', '\1', '') + + try + let bookmark = s:Bookmark.New(name, s:Path.New(path)) + call add(g:NERDTreeBookmarks, bookmark) + catch /NERDTree.Path.InvalidArguments/ + call add(g:NERDTreeInvalidBookmarks, i) + let invalidBookmarksFound += 1 + endtry + endif + endfor + if invalidBookmarksFound + call s:Bookmark.Write() + if !a:silent + call s:echo(invalidBookmarksFound . " invalid bookmarks were read. See :help NERDTreeInvalidBookmarks for info.") + endif + endif + call s:Bookmark.Sort() + endif +endfunction +" FUNCTION: Bookmark.compareTo(otherbookmark) {{{3 +" Compare these two bookmarks for sorting purposes +function! s:Bookmark.compareTo(otherbookmark) + return a:otherbookmark.name < self.name +endfunction +" FUNCTION: Bookmark.ClearAll() {{{3 +" Class method to delete all bookmarks. +function! s:Bookmark.ClearAll() + for i in s:Bookmark.Bookmarks() + call i.delete() + endfor + call s:Bookmark.Write() +endfunction +" FUNCTION: Bookmark.delete() {{{3 +" Delete this bookmark. If the node for this bookmark is under the current +" root, then recache bookmarks for its Path object +function! s:Bookmark.delete() + let node = {} + try + let node = self.getNode(1) + catch /NERDTree.BookmarkedNodeNotFound/ + endtry + call remove(s:Bookmark.Bookmarks(), index(s:Bookmark.Bookmarks(), self)) + if !empty(node) + call node.path.cacheDisplayString() + endif + call s:Bookmark.Write() +endfunction +" FUNCTION: Bookmark.getNode(searchFromAbsoluteRoot) {{{3 +" Gets the treenode for this bookmark +" +" Args: +" searchFromAbsoluteRoot: specifies whether we should search from the current +" tree root, or the highest cached node +function! s:Bookmark.getNode(searchFromAbsoluteRoot) + let searchRoot = a:searchFromAbsoluteRoot ? s:TreeDirNode.AbsoluteTreeRoot() : t:NERDTreeRoot + let targetNode = searchRoot.findNode(self.path) + if empty(targetNode) + throw "NERDTree.BookmarkedNodeNotFound no node was found for bookmark: " . self.name + endif + return targetNode +endfunction +" FUNCTION: Bookmark.GetNodeForName(name, searchFromAbsoluteRoot) {{{3 +" Class method that finds the bookmark with the given name and returns the +" treenode for it. +function! s:Bookmark.GetNodeForName(name, searchFromAbsoluteRoot) + let bookmark = s:Bookmark.BookmarkFor(a:name) + return bookmark.getNode(a:searchFromAbsoluteRoot) +endfunction +" Function: Bookmark.InvalidBookmarks() {{{3 +" Class method to get all invalid bookmark strings read from the bookmarks +" file +function! s:Bookmark.InvalidBookmarks() + if !exists("g:NERDTreeInvalidBookmarks") + let g:NERDTreeInvalidBookmarks = [] + endif + return g:NERDTreeInvalidBookmarks +endfunction +" FUNCTION: Bookmark.mustExist() {{{3 +function! s:Bookmark.mustExist() + if !self.path.exists() + call s:Bookmark.CacheBookmarks(1) + throw "NERDTree.BookmarkPointsToInvalidLocation exception: the bookmark \"". + \ self.name ."\" points to a non existing location: \"". self.path.strForOS(0) + endif +endfunction +" FUNCTION: Bookmark.New(name, path) {{{3 +" Create a new bookmark object with the given name and path object +function! s:Bookmark.New(name, path) + if a:name =~ ' ' + throw "NERDTree.IllegalBookmarkName illegal name:" . a:name + endif + + let newBookmark = copy(self) + let newBookmark.name = a:name + let newBookmark.path = a:path + return newBookmark +endfunction +" Function: Bookmark.setPath(path) {{{3 +" makes this bookmark point to the given path +function! s:Bookmark.setPath(path) + let self.path = a:path +endfunction +" Function: Bookmark.Sort() {{{3 +" Class method that sorts all bookmarks +function! s:Bookmark.Sort() + let CompareFunc = function("s:compareBookmarks") + call sort(s:Bookmark.Bookmarks(), CompareFunc) +endfunction +" Function: Bookmark.str() {{{3 +" Get the string that should be rendered in the view for this bookmark +function! s:Bookmark.str() + let pathStrMaxLen = winwidth(s:getTreeWinNum()) - 4 - len(self.name) + if &nu + let pathStrMaxLen = pathStrMaxLen - &numberwidth + endif + + let pathStr = self.path.strForOS(0) + if len(pathStr) > pathStrMaxLen + let pathStr = '<' . strpart(pathStr, len(pathStr) - pathStrMaxLen) + endif + return '>' . self.name . ' ' . pathStr +endfunction +" FUNCTION: Bookmark.toRoot() {{{3 +" Make the node for this bookmark the new tree root +function! s:Bookmark.toRoot() + if self.validate() + try + let targetNode = self.getNode(1) + catch /NERDTree.BookmarkedNodeNotFound/ + let targetNode = s:TreeFileNode.New(s:Bookmark.BookmarkFor(self.name).path) + endtry + call targetNode.makeRoot() + call s:renderView() + call s:putCursorOnNode(targetNode, 0, 0) + endif +endfunction +" FUNCTION: Bookmark.ToRoot(name) {{{3 +" Make the node for this bookmark the new tree root +function! s:Bookmark.ToRoot(name) + let bookmark = s:Bookmark.BookmarkFor(a:name) + call bookmark.toRoot() +endfunction + + +"FUNCTION: Bookmark.validate() {{{3 +function! s:Bookmark.validate() + if self.path.exists() + return 1 + else + call s:Bookmark.CacheBookmarks(1) + call s:renderView() + call s:echo(self.name . "now points to an invalid location. See :help NERDTreeInvalidBookmarks for info.") + return 0 + endif +endfunction + +" Function: Bookmark.Write() {{{3 +" Class method to write all bookmarks to the bookmarks file +function! s:Bookmark.Write() + let bookmarkStrings = [] + for i in s:Bookmark.Bookmarks() + call add(bookmarkStrings, i.name . ' ' . i.path.strForOS(0)) + endfor + + "add a blank line before the invalid ones + call add(bookmarkStrings, "") + + for j in s:Bookmark.InvalidBookmarks() + call add(bookmarkStrings, j) + endfor + call writefile(bookmarkStrings, g:NERDTreeBookmarksFile) +endfunction +"CLASS: TreeFileNode {{{2 +"This class is the parent of the TreeDirNode class and constitures the +"'Component' part of the composite design pattern between the treenode +"classes. +"============================================================ +let s:TreeFileNode = {} +"FUNCTION: TreeFileNode.bookmark(name) {{{3 +"bookmark this node with a:name +function! s:TreeFileNode.bookmark(name) + try + let oldMarkedNode = s:Bookmark.GetNodeForName(a:name, 1) + call oldMarkedNode.path.cacheDisplayString() + catch /NERDTree.Bookmark\(DoesntExist\|NotFound\)/ + endtry + + call s:Bookmark.AddBookmark(a:name, self.path) + call self.path.cacheDisplayString() + call s:Bookmark.Write() +endfunction +"FUNCTION: TreeFileNode.cacheParent() {{{3 +"initializes self.parent if it isnt already +function! s:TreeFileNode.cacheParent() + if empty(self.parent) + let parentPath = self.path.getParent() + if parentPath.equals(self.path) + throw "NERDTree.CannotCacheParent exception: already at root" + endif + let self.parent = s:TreeFileNode.New(parentPath) + endif +endfunction +"FUNCTION: TreeFileNode.compareNodes {{{3 +"This is supposed to be a class level method but i cant figure out how to +"get func refs to work from a dict.. +" +"A class level method that compares two nodes +" +"Args: +"n1, n2: the 2 nodes to compare +function! s:compareNodes(n1, n2) + return a:n1.path.compareTo(a:n2.path) +endfunction + +"FUNCTION: TreeFileNode.clearBoomarks() {{{3 +function! s:TreeFileNode.clearBoomarks() + for i in s:Bookmark.Bookmarks() + if i.path.equals(self.path) + call i.delete() + end + endfor + call self.path.cacheDisplayString() +endfunction +"FUNCTION: TreeFileNode.copy(dest) {{{3 +function! s:TreeFileNode.copy(dest) + call self.path.copy(a:dest) + let newPath = s:Path.New(a:dest) + let parent = t:NERDTreeRoot.findNode(newPath.getParent()) + if !empty(parent) + call parent.refresh() + endif + return parent.findNode(newPath) +endfunction + +"FUNCTION: TreeFileNode.delete {{{3 +"Removes this node from the tree and calls the Delete method for its path obj +function! s:TreeFileNode.delete() + call self.path.delete() + call self.parent.removeChild(self) +endfunction + +"FUNCTION: TreeFileNode.equals(treenode) {{{3 +" +"Compares this treenode to the input treenode and returns 1 if they are the +"same node. +" +"Use this method instead of == because sometimes when the treenodes contain +"many children, vim seg faults when doing == +" +"Args: +"treenode: the other treenode to compare to +function! s:TreeFileNode.equals(treenode) + return self.path.str(1) == a:treenode.path.str(1) +endfunction + +"FUNCTION: TreeFileNode.findNode(path) {{{3 +"Returns self if this node.path.Equals the given path. +"Returns {} if not equal. +" +"Args: +"path: the path object to compare against +function! s:TreeFileNode.findNode(path) + if a:path.equals(self.path) + return self + endif + return {} +endfunction +"FUNCTION: TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) {{{3 +" +"Finds the next sibling for this node in the indicated direction. This sibling +"must be a directory and may/may not have children as specified. +" +"Args: +"direction: 0 if you want to find the previous sibling, 1 for the next sibling +" +"Return: +"a treenode object or {} if no appropriate sibling could be found +function! s:TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) + "if we have no parent then we can have no siblings + if self.parent != {} + let nextSibling = self.findSibling(a:direction) + + while nextSibling != {} + if nextSibling.path.isDirectory && nextSibling.hasVisibleChildren() && nextSibling.isOpen + return nextSibling + endif + let nextSibling = nextSibling.findSibling(a:direction) + endwhile + endif + + return {} +endfunction +"FUNCTION: TreeFileNode.findSibling(direction) {{{3 +" +"Finds the next sibling for this node in the indicated direction +" +"Args: +"direction: 0 if you want to find the previous sibling, 1 for the next sibling +" +"Return: +"a treenode object or {} if no sibling could be found +function! s:TreeFileNode.findSibling(direction) + "if we have no parent then we can have no siblings + if self.parent != {} + + "get the index of this node in its parents children + let siblingIndx = self.parent.getChildIndex(self.path) + + if siblingIndx != -1 + "move a long to the next potential sibling node + let siblingIndx = a:direction == 1 ? siblingIndx+1 : siblingIndx-1 + + "keep moving along to the next sibling till we find one that is valid + let numSiblings = self.parent.getChildCount() + while siblingIndx >= 0 && siblingIndx < numSiblings + + "if the next node is not an ignored node (i.e. wont show up in the + "view) then return it + if self.parent.children[siblingIndx].path.ignore() == 0 + return self.parent.children[siblingIndx] + endif + + "go to next node + let siblingIndx = a:direction == 1 ? siblingIndx+1 : siblingIndx-1 + endwhile + endif + endif + + return {} +endfunction + +"FUNCTION: TreeFileNode.isVisible() {{{3 +"returns 1 if this node should be visible according to the tree filters and +"hidden file filters (and their on/off status) +function! s:TreeFileNode.isVisible() + return !self.path.ignore() +endfunction + + +"FUNCTION: TreeFileNode.isRoot() {{{3 +"returns 1 if this node is t:NERDTreeRoot +function! s:TreeFileNode.isRoot() + if !s:treeExistsForTab() + throw "NERDTree.TreeFileNode.IsRoot exception: No tree exists for the current tab" + endif + return self.equals(t:NERDTreeRoot) +endfunction + +"FUNCTION: TreeFileNode.makeRoot() {{{3 +"Make this node the root of the tree +function! s:TreeFileNode.makeRoot() + if self.path.isDirectory + let t:NERDTreeRoot = self + else + call self.cacheParent() + let t:NERDTreeRoot = self.parent + endif + + call t:NERDTreeRoot.open() + + "change dir to the dir of the new root if instructed to + if g:NERDTreeChDirMode == 2 + exec "cd " . t:NERDTreeRoot.path.strForEditCmd() + endif +endfunction +"FUNCTION: TreeFileNode.New(path) {{{3 +"Returns a new TreeNode object with the given path and parent +" +"Args: +"path: a path object representing the full filesystem path to the file/dir that the node represents +function! s:TreeFileNode.New(path) + if a:path.isDirectory + return s:TreeDirNode.New(a:path) + else + let newTreeNode = {} + let newTreeNode = copy(self) + let newTreeNode.path = a:path + let newTreeNode.parent = {} + return newTreeNode + endif +endfunction + +"FUNCTION: TreeFileNode.refresh() {{{3 +function! s:TreeFileNode.refresh() + call self.path.refresh() +endfunction +"FUNCTION: TreeFileNode.rename() {{{3 +"Calls the rename method for this nodes path obj +function! s:TreeFileNode.rename(newName) + let newName = substitute(a:newName, '\(\\\|\/\)$', '', '') + call self.path.rename(newName) + call self.parent.removeChild(self) + + let parentPath = self.path.getPathTrunk() + let newParent = t:NERDTreeRoot.findNode(parentPath) + + if newParent != {} + call newParent.createChild(self.path, 1) + call newParent.refresh() + endif +endfunction +"FUNCTION: TreeFileNode.strDisplay() {{{3 +" +"Returns a string that specifies how the node should be represented as a +"string +" +"Return: +"a string that can be used in the view to represent this node +function! s:TreeFileNode.strDisplay() + return self.path.strDisplay() +endfunction + +"CLASS: TreeDirNode {{{2 +"This class is a child of the TreeFileNode class and constitutes the +"'Composite' part of the composite design pattern between the treenode +"classes. +"============================================================ +let s:TreeDirNode = copy(s:TreeFileNode) +"FUNCTION: TreeDirNode.AbsoluteTreeRoot(){{{3 +"class method that returns the highest cached ancestor of the current root +function! s:TreeDirNode.AbsoluteTreeRoot() + let currentNode = t:NERDTreeRoot + while currentNode.parent != {} + let currentNode = currentNode.parent + endwhile + return currentNode +endfunction +"FUNCTION: TreeDirNode.addChild(treenode, inOrder) {{{3 +"Adds the given treenode to the list of children for this node +" +"Args: +"-treenode: the node to add +"-inOrder: 1 if the new node should be inserted in sorted order +function! s:TreeDirNode.addChild(treenode, inOrder) + call add(self.children, a:treenode) + let a:treenode.parent = self + + if a:inOrder + call self.sortChildren() + endif +endfunction + +"FUNCTION: TreeDirNode.close() {{{3 +"Closes this directory +function! s:TreeDirNode.close() + let self.isOpen = 0 +endfunction + +"FUNCTION: TreeDirNode.closeChildren() {{{3 +"Closes all the child dir nodes of this node +function! s:TreeDirNode.closeChildren() + for i in self.children + if i.path.isDirectory + call i.close() + call i.closeChildren() + endif + endfor +endfunction + +"FUNCTION: TreeDirNode.createChild(path, inOrder) {{{3 +"Instantiates a new child node for this node with the given path. The new +"nodes parent is set to this node. +" +"Args: +"path: a Path object that this node will represent/contain +"inOrder: 1 if the new node should be inserted in sorted order +" +"Returns: +"the newly created node +function! s:TreeDirNode.createChild(path, inOrder) + let newTreeNode = s:TreeFileNode.New(a:path) + call self.addChild(newTreeNode, a:inOrder) + return newTreeNode +endfunction + +"FUNCTION: TreeDirNode.findNode(path) {{{3 +"Will find one of the children (recursively) that has the given path +" +"Args: +"path: a path object +unlet s:TreeDirNode.findNode +function! s:TreeDirNode.findNode(path) + if a:path.equals(self.path) + return self + endif + if stridx(a:path.str(1), self.path.str(1), 0) == -1 + return {} + endif + + if self.path.isDirectory + for i in self.children + let retVal = i.findNode(a:path) + if retVal != {} + return retVal + endif + endfor + endif + return {} +endfunction +"FUNCTION: TreeDirNode.getChildCount() {{{3 +"Returns the number of children this node has +function! s:TreeDirNode.getChildCount() + return len(self.children) +endfunction + +"FUNCTION: TreeDirNode.getChild(path) {{{3 +"Returns child node of this node that has the given path or {} if no such node +"exists. +" +"This function doesnt not recurse into child dir nodes +" +"Args: +"path: a path object +function! s:TreeDirNode.getChild(path) + if stridx(a:path.str(1), self.path.str(1), 0) == -1 + return {} + endif + + let index = self.getChildIndex(a:path) + if index == -1 + return {} + else + return self.children[index] + endif + +endfunction + +"FUNCTION: TreeDirNode.getChildByIndex(indx, visible) {{{3 +"returns the child at the given index +"Args: +"indx: the index to get the child from +"visible: 1 if only the visible children array should be used, 0 if all the +"children should be searched. +function! s:TreeDirNode.getChildByIndex(indx, visible) + let array_to_search = a:visible? self.getVisibleChildren() : self.children + if a:indx > len(array_to_search) + throw "NERDTree.TreeDirNode.InvalidArguments exception. Index is out of bounds." + endif + return array_to_search[a:indx] +endfunction + +"FUNCTION: TreeDirNode.getChildIndex(path) {{{3 +"Returns the index of the child node of this node that has the given path or +"-1 if no such node exists. +" +"This function doesnt not recurse into child dir nodes +" +"Args: +"path: a path object +function! s:TreeDirNode.getChildIndex(path) + if stridx(a:path.str(1), self.path.str(1), 0) == -1 + return -1 + endif + + "do a binary search for the child + let a = 0 + let z = self.getChildCount() + while a < z + let mid = (a+z)/2 + let diff = a:path.compareTo(self.children[mid].path) + + if diff == -1 + let z = mid + elseif diff == 1 + let a = mid+1 + else + return mid + endif + endwhile + return -1 +endfunction + +"FUNCTION: TreeDirNode.getVisibleChildCount() {{{3 +"Returns the number of visible children this node has +function! s:TreeDirNode.getVisibleChildCount() + return len(self.getVisibleChildren()) +endfunction + +"FUNCTION: TreeDirNode.getVisibleChildren() {{{3 +"Returns a list of children to display for this node, in the correct order +" +"Return: +"an array of treenodes +function! s:TreeDirNode.getVisibleChildren() + let toReturn = [] + for i in self.children + if i.path.ignore() == 0 + call add(toReturn, i) + endif + endfor + return toReturn +endfunction + +"FUNCTION: TreeDirNode.hasVisibleChildren() {{{3 +"returns 1 if this node has any childre, 0 otherwise.. +function! s:TreeDirNode.hasVisibleChildren() + return self.getVisibleChildCount() != 0 +endfunction + +"FUNCTION: TreeDirNode._initChildren() {{{3 +"Removes all childen from this node and re-reads them +" +"Args: +"silent: 1 if the function should not echo any "please wait" messages for +"large directories +" +"Return: the number of child nodes read +function! s:TreeDirNode._initChildren(silent) + "remove all the current child nodes + let self.children = [] + + "get an array of all the files in the nodes dir + let dir = self.path + let filesStr = globpath(dir.strForGlob(), '*') . "\n" . globpath(dir.strForGlob(), '.*') + let files = split(filesStr, "\n") + + if !a:silent && len(files) > g:NERDTreeNotificationThreshold + call s:echo("Please wait, caching a large dir ...") + endif + + let invalidFilesFound = 0 + for i in files + + "filter out the .. and . directories + "Note: we must match .. AND ../ cos sometimes the globpath returns + "../ for path with strange chars (eg $) + if i !~ '\.\.\/\?$' && i !~ '\.\/\?$' + + "put the next file in a new node and attach it + try + let path = s:Path.New(i) + call self.createChild(path, 0) + catch /^NERDTree.Path.\(InvalidArguments\|InvalidFiletype\)/ + let invalidFilesFound += 1 + endtry + endif + endfor + + call self.sortChildren() + + if !a:silent && len(files) > g:NERDTreeNotificationThreshold + call s:echo("Please wait, caching a large dir ... DONE (". self.getChildCount() ." nodes cached).") + endif + + if invalidFilesFound + call s:echoWarning(invalidFilesFound . " file(s) could not be loaded into the NERD tree") + endif + return self.getChildCount() +endfunction +"FUNCTION: TreeDirNode.New(path) {{{3 +"Returns a new TreeNode object with the given path and parent +" +"Args: +"path: a path object representing the full filesystem path to the file/dir that the node represents +unlet s:TreeDirNode.New +function! s:TreeDirNode.New(path) + if a:path.isDirectory != 1 + throw "NERDTree.TreeDirNode.InvalidArguments exception. A TreeDirNode object must be instantiated with a directory Path object." + endif + + let newTreeNode = copy(self) + let newTreeNode.path = a:path + + let newTreeNode.isOpen = 0 + let newTreeNode.children = [] + + let newTreeNode.parent = {} + + return newTreeNode +endfunction +"FUNCTION: TreeDirNode.open() {{{3 +"Reads in all this nodes children +" +"Return: the number of child nodes read +function! s:TreeDirNode.open() + let self.isOpen = 1 + if self.children == [] + return self._initChildren(0) + else + return 0 + endif +endfunction + +"FUNCTION: TreeDirNode.openRecursively() {{{3 +"Opens this treenode and all of its children whose paths arent 'ignored' +"because of the file filters. +" +"This method is actually a wrapper for the OpenRecursively2 method which does +"the work. +function! s:TreeDirNode.openRecursively() + call self._openRecursively2(1) +endfunction + +"FUNCTION: TreeDirNode._openRecursively2() {{{3 +"Opens this all children of this treenode recursively if either: +" *they arent filtered by file filters +" *a:forceOpen is 1 +" +"Args: +"forceOpen: 1 if this node should be opened regardless of file filters +function! s:TreeDirNode._openRecursively2(forceOpen) + if self.path.ignore() == 0 || a:forceOpen + let self.isOpen = 1 + if self.children == [] + call self._initChildren(1) + endif + + for i in self.children + if i.path.isDirectory == 1 + call i._openRecursively2(0) + endif + endfor + endif +endfunction + +"FUNCTION: TreeDirNode.refresh() {{{3 +unlet s:TreeDirNode.refresh +function! s:TreeDirNode.refresh() + call self.path.refresh() + + "if this node was ever opened, refresh its children + if self.isOpen || !empty(self.children) + "go thru all the files/dirs under this node + let newChildNodes = [] + let invalidFilesFound = 0 + let dir = self.path + let filesStr = globpath(dir.strForGlob(), '*') . "\n" . globpath(dir.strForGlob(), '.*') + let files = split(filesStr, "\n") + for i in files + if i !~ '\.\.$' && i !~ '\.$' + + try + "create a new path and see if it exists in this nodes children + let path = s:Path.New(i) + let newNode = self.getChild(path) + if newNode != {} + call newNode.refresh() + call add(newChildNodes, newNode) + + "the node doesnt exist so create it + else + let newNode = s:TreeFileNode.New(path) + let newNode.parent = self + call add(newChildNodes, newNode) + endif + + + catch /^NERDTree.InvalidArguments/ + let invalidFilesFound = 1 + endtry + endif + endfor + + "swap this nodes children out for the children we just read/refreshed + let self.children = newChildNodes + call self.sortChildren() + + if invalidFilesFound + call s:echoWarning("some files could not be loaded into the NERD tree") + endif + endif +endfunction + +"FUNCTION: TreeDirNode.removeChild(treenode) {{{3 +" +"Removes the given treenode from this nodes set of children +" +"Args: +"treenode: the node to remove +" +"Throws a NERDTree.TreeDirNode exception if the given treenode is not found +function! s:TreeDirNode.removeChild(treenode) + for i in range(0, self.getChildCount()-1) + if self.children[i].equals(a:treenode) + call remove(self.children, i) + return + endif + endfor + + throw "NERDTree.TreeDirNode exception: child node was not found" +endfunction + +"FUNCTION: TreeDirNode.sortChildren() {{{3 +" +"Sorts the children of this node according to alphabetical order and the +"directory priority. +" +function! s:TreeDirNode.sortChildren() + let CompareFunc = function("s:compareNodes") + call sort(self.children, CompareFunc) +endfunction + +"FUNCTION: TreeDirNode.toggleOpen() {{{3 +"Opens this directory if it is closed and vice versa +function! s:TreeDirNode.toggleOpen() + if self.isOpen == 1 + call self.close() + else + call self.open() + endif +endfunction + +"FUNCTION: TreeDirNode.transplantChild(newNode) {{{3 +"Replaces the child of this with the given node (where the child node's full +"path matches a:newNode's fullpath). The search for the matching node is +"non-recursive +" +"Arg: +"newNode: the node to graft into the tree +function! s:TreeDirNode.transplantChild(newNode) + for i in range(0, self.getChildCount()-1) + if self.children[i].equals(a:newNode) + let self.children[i] = a:newNode + let a:newNode.parent = self + break + endif + endfor +endfunction +"============================================================ +"CLASS: Path {{{2 +"============================================================ +let s:Path = {} +"FUNCTION: Path.bookmarkNames() {{{3 +function! s:Path.bookmarkNames() + if !exists("self._bookmarkNames") + call self.cacheDisplayString() + endif + return self._bookmarkNames +endfunction +"FUNCTION: Path.cacheDisplayString() {{{3 +function! s:Path.cacheDisplayString() + let self.cachedDisplayString = self.getLastPathComponent(1) + + if self.isExecutable + let self.cachedDisplayString = self.cachedDisplayString . '*' + endif + + let self._bookmarkNames = [] + for i in s:Bookmark.Bookmarks() + if i.path.equals(self) + call add(self._bookmarkNames, i.name) + endif + endfor + if !empty(self._bookmarkNames) + let self.cachedDisplayString .= ' {' . join(self._bookmarkNames) . '}' + endif + + if self.isSymLink + let self.cachedDisplayString .= ' -> ' . self.symLinkDest + endif + + if self.isReadOnly + let self.cachedDisplayString .= ' [RO]' + endif +endfunction +"FUNCTION: Path.changeToDir() {{{3 +function! s:Path.changeToDir() + let dir = self.strForCd() + if self.isDirectory == 0 + let dir = self.getPathTrunk().strForCd() + endif + + try + execute "cd " . dir + call s:echo("CWD is now: " . getcwd()) + catch + throw "NERDTree.Path.Change exception: cannot change to " . dir + endtry +endfunction + +"FUNCTION: Path.compareTo() {{{3 +" +"Compares this Path to the given path and returns 0 if they are equal, -1 if +"this Path is "less than" the given path, or 1 if it is "greater". +" +"Args: +"path: the path object to compare this to +" +"Return: +"1, -1 or 0 +function! s:Path.compareTo(path) + let thisPath = self.getLastPathComponent(1) + let thatPath = a:path.getLastPathComponent(1) + + "if the paths are the same then clearly we return 0 + if thisPath == thatPath + return 0 + endif + + let thisSS = self.getSortOrderIndex() + let thatSS = a:path.getSortOrderIndex() + + "compare the sort sequences, if they are different then the return + "value is easy + if thisSS < thatSS + return -1 + elseif thisSS > thatSS + return 1 + else + "if the sort sequences are the same then compare the paths + "alphabetically + let pathCompare = g:NERDTreeCaseSensitiveSort ? thisPath <# thatPath : thisPath >> + +"FUNCTION: s:compareBookmarks(first, second) {{{2 +"Compares two bookmarks +function! s:compareBookmarks(first, second) + return a:first.compareTo(a:second) +endfunction + +" FUNCTION: s:completeBookmarks(A,L,P) {{{2 +" completion function for the bookmark commands +function! s:completeBookmarks(A,L,P) + return filter(s:Bookmark.BookmarkNames(), 'v:val =~ "^' . a:A . '"') +endfunction +"FUNCTION: s:initNerdTree(name) {{{2 +"Initialise the nerd tree for this tab. The tree will start in either the +"given directory, or the directory associated with the given bookmark +" +"Args: +"name: the name of a bookmark or a directory +function! s:initNerdTree(name) + let path = {} + if s:Bookmark.BookmarkExistsFor(a:name) + let path = s:Bookmark.BookmarkFor(a:name).path + else + let dir = a:name == '' ? expand('%:p:h') : a:name + let dir = resolve(dir) + try + let path = s:Path.New(dir) + catch /NERDTree.Path.InvalidArguments/ + call s:echo("No bookmark or directory found for: " . a:name) + return + endtry + endif + if !path.isDirectory + let path = path.getParent() + endif + + "if instructed to, then change the vim CWD to the dir the NERDTree is + "inited in + if g:NERDTreeChDirMode != 0 + exec 'cd ' . path.strForCd() + endif + + let t:treeShowHelp = 0 + let t:NERDTreeIgnoreEnabled = 1 + let t:NERDTreeShowFiles = g:NERDTreeShowFiles + let t:NERDTreeShowHidden = g:NERDTreeShowHidden + let t:NERDTreeShowBookmarks = g:NERDTreeShowBookmarks + + if s:treeExistsForTab() + if s:isTreeOpen() + call s:closeTree() + endif + unlet t:NERDTreeRoot + endif + + let t:NERDTreeRoot = s:TreeDirNode.New(path) + call t:NERDTreeRoot.open() + + call s:createTreeWin() + call s:renderView() + call s:putCursorOnNode(t:NERDTreeRoot, 0, 0) +endfunction +" Function: s:treeExistsForTab() {{{2 +" Returns 1 if a nerd tree root exists in the current tab +function! s:treeExistsForTab() + return exists("t:NERDTreeRoot") +endfunction +" SECTION: Public Functions {{{1 +"============================================================ +"Returns the node that the cursor is currently on. +" +"If the cursor is not in the NERDTree window, it is temporarily put there. +" +"If no NERD tree window exists for the current tab, a NERDTree.NoTreeForTab +"exception is thrown. +" +"If the cursor is not on a node then an empty dictionary {} is returned. +function! NERDTreeGetCurrentNode() + if !s:treeExistsForTab() || !s:isTreeOpen() + throw "NERDTree.NoTreeForTab exception: there is no NERD tree open for the current tab" + endif + + let winnr = winnr() + if winnr != s:getTreeWinNum() + call s:putCursorInTreeWin() + endif + + let treenode = s:getSelectedNode() + + if winnr != winnr() + wincmd w + endif + + return treenode +endfunction + +"Returns the path object for the current node. +" +"Subject to the same conditions as NERDTreeGetCurrentNode +function! NERDTreeGetCurrentPath() + let node = NERDTreeGetCurrentNode() + if node != {} + return node.path + else + return {} + endif +endfunction + +" SECTION: View Functions {{{1 +"============================================================ +"FUNCTION: s:centerView() {{{2 +"centers the nerd tree window around the cursor (provided the nerd tree +"options permit) +function! s:centerView() + if g:NERDTreeAutoCenter + let current_line = winline() + let lines_to_top = current_line + let lines_to_bottom = winheight(s:getTreeWinNum()) - current_line + if lines_to_top < g:NERDTreeAutoCenterThreshold || lines_to_bottom < g:NERDTreeAutoCenterThreshold + normal! zz + endif + endif +endfunction +"FUNCTION: s:closeTree() {{{2 +"Closes the NERD tree window +function! s:closeTree() + if !s:isTreeOpen() + throw "NERDTree.view.closeTree exception: no NERDTree is open" + endif + + if winnr("$") != 1 + execute s:getTreeWinNum() . " wincmd w" + close + execute "wincmd p" + else + :q + endif +endfunction + +"FUNCTION: s:closeTreeIfOpen() {{{2 +"Closes the NERD tree window if it is open +function! s:closeTreeIfOpen() + if s:isTreeOpen() + call s:closeTree() + endif +endfunction +"FUNCTION: s:closeTreeIfQuitOnOpen() {{{2 +"Closes the NERD tree window if the close on open option is set +function! s:closeTreeIfQuitOnOpen() + if g:NERDTreeQuitOnOpen + call s:closeTree() + endif +endfunction +"FUNCTION: s:createTreeWin() {{{2 +"Inits the NERD tree window. ie. opens it, sizes it, sets all the local +"options etc +function! s:createTreeWin() + "create the nerd tree window + let splitLocation = (g:NERDTreeWinPos == "top" || g:NERDTreeWinPos == "left") ? "topleft " : "botright " + let splitMode = s:shouldSplitVertically() ? "vertical " : "" + let splitSize = g:NERDTreeWinSize + let t:NERDTreeWinName = localtime() . s:NERDTreeWinName + let cmd = splitLocation . splitMode . splitSize . ' new ' . t:NERDTreeWinName + silent! execute cmd + + setlocal winfixwidth + + "throwaway buffer options + setlocal noswapfile + setlocal buftype=nofile + setlocal bufhidden=delete + setlocal nowrap + setlocal foldcolumn=0 + setlocal nobuflisted + setlocal nospell + if g:NERDTreeShowLineNumbers + setlocal nu + else + setlocal nonu + endif + + iabc + + if g:NERDTreeHighlightCursorline + setlocal cursorline + endif + + + + call s:bindMappings() + setfiletype nerdtree + " syntax highlighting + if has("syntax") && exists("g:syntax_on") && !has("syntax_items") + call s:setupSyntaxHighlighting() + endif +endfunction + +"FUNCTION: s:drawTree {{{2 +"Draws the given node recursively +" +"Args: +"curNode: the node that is being rendered with this call +"depth: the current depth in the tree for this call +"drawText: 1 if we should actually draw the line for this node (if 0 then the +"child nodes are rendered only) +"vertMap: a binary array that indicates whether a vertical bar should be draw +"for each depth in the tree +"isLastChild:true if this curNode is the last child of its parent +function! s:drawTree(curNode, depth, drawText, vertMap, isLastChild) + if a:drawText == 1 + + let treeParts = '' + + "get all the leading spaces and vertical tree parts for this line + if a:depth > 1 + for j in a:vertMap[0:-2] + if j == 1 + let treeParts = treeParts . '| ' + else + let treeParts = treeParts . ' ' + endif + endfor + endif + + "get the last vertical tree part for this line which will be different + "if this node is the last child of its parent + if a:isLastChild + let treeParts = treeParts . '`' + else + let treeParts = treeParts . '|' + endif + + + "smack the appropriate dir/file symbol on the line before the file/dir + "name itself + if a:curNode.path.isDirectory + if a:curNode.isOpen + let treeParts = treeParts . '~' + else + let treeParts = treeParts . '+' + endif + else + let treeParts = treeParts . '-' + endif + let line = treeParts . a:curNode.strDisplay() + + call setline(line(".")+1, line) + call cursor(line(".")+1, col(".")) + endif + + "if the node is an open dir, draw its children + if a:curNode.path.isDirectory == 1 && a:curNode.isOpen == 1 + + let childNodesToDraw = a:curNode.getVisibleChildren() + if len(childNodesToDraw) > 0 + + "draw all the nodes children except the last + let lastIndx = len(childNodesToDraw)-1 + if lastIndx > 0 + for i in childNodesToDraw[0:lastIndx-1] + call s:drawTree(i, a:depth + 1, 1, add(copy(a:vertMap), 1), 0) + endfor + endif + + "draw the last child, indicating that it IS the last + call s:drawTree(childNodesToDraw[lastIndx], a:depth + 1, 1, add(copy(a:vertMap), 0), 1) + endif + endif +endfunction + + +"FUNCTION: s:dumpHelp {{{2 +"prints out the quick help +function! s:dumpHelp() + let old_h = @h + if t:treeShowHelp == 1 + let @h= "\" NERD tree (" . s:NERD_tree_version . ") quickhelp~\n" + let @h=@h."\" ============================\n" + let @h=@h."\" File node mappings~\n" + let @h=@h."\" ". (g:NERDTreeMouseMode == 3 ? "single" : "double") ."-click,\n" + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open in prev window\n" + let @h=@h."\" ". g:NERDTreeMapPreview .": preview\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTab.": open in new tab\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTabSilent .": open in new tab silently\n" + let @h=@h."\" middle-click,\n" + let @h=@h."\" ". g:NERDTreeMapOpenSplit .": open split\n" + let @h=@h."\" ". g:NERDTreeMapPreviewSplit .": preview split\n" + let @h=@h."\" ". g:NERDTreeMapExecute.": Execute file\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Directory node mappings~\n" + let @h=@h."\" ". (g:NERDTreeMouseMode == 1 ? "double" : "single") ."-click,\n" + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open & close node\n" + let @h=@h."\" ". g:NERDTreeMapOpenRecursively .": recursively open node\n" + let @h=@h."\" ". g:NERDTreeMapCloseDir .": close parent of node\n" + let @h=@h."\" ". g:NERDTreeMapCloseChildren .": close all child nodes of\n" + let @h=@h."\" current node recursively\n" + let @h=@h."\" middle-click,\n" + let @h=@h."\" ". g:NERDTreeMapOpenExpl.": Open netrw for selected\n" + let @h=@h."\" node\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Bookmark table mappings~\n" + let @h=@h."\" double-click,\n" + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open bookmark\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTab.": open in new tab\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTabSilent .": open in new tab silently\n" + let @h=@h."\" ". g:NERDTreeMapDeleteBookmark .": delete bookmark\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Tree navigation mappings~\n" + let @h=@h."\" ". g:NERDTreeMapJumpRoot .": go to root\n" + let @h=@h."\" ". g:NERDTreeMapJumpParent .": go to parent\n" + let @h=@h."\" ". g:NERDTreeMapJumpFirstChild .": go to first child\n" + let @h=@h."\" ". g:NERDTreeMapJumpLastChild .": go to last child\n" + let @h=@h."\" ". g:NERDTreeMapJumpNextSibling .": go to next sibling\n" + let @h=@h."\" ". g:NERDTreeMapJumpPrevSibling .": go to prev sibling\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Filesystem mappings~\n" + let @h=@h."\" ". g:NERDTreeMapChangeRoot .": change tree root to the\n" + let @h=@h."\" selected dir\n" + let @h=@h."\" ". g:NERDTreeMapUpdir .": move tree root up a dir\n" + let @h=@h."\" ". g:NERDTreeMapUpdirKeepOpen .": move tree root up a dir\n" + let @h=@h."\" but leave old root open\n" + let @h=@h."\" ". g:NERDTreeMapRefresh .": refresh cursor dir\n" + let @h=@h."\" ". g:NERDTreeMapRefreshRoot .": refresh current root\n" + let @h=@h."\" ". g:NERDTreeMapFilesystemMenu .": Show filesystem menu\n" + let @h=@h."\" ". g:NERDTreeMapChdir .":change the CWD to the\n" + let @h=@h."\" selected dir\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Tree filtering mappings~\n" + let @h=@h."\" ". g:NERDTreeMapToggleHidden .": hidden files (" . (t:NERDTreeShowHidden ? "on" : "off") . ")\n" + let @h=@h."\" ". g:NERDTreeMapToggleFilters .": file filters (" . (t:NERDTreeIgnoreEnabled ? "on" : "off") . ")\n" + let @h=@h."\" ". g:NERDTreeMapToggleFiles .": files (" . (t:NERDTreeShowFiles ? "on" : "off") . ")\n" + let @h=@h."\" ". g:NERDTreeMapToggleBookmarks .": bookmarks (" . (t:NERDTreeShowBookmarks ? "on" : "off") . ")\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Other mappings~\n" + let @h=@h."\" ". g:NERDTreeMapQuit .": Close the NERDTree window\n" + let @h=@h."\" ". g:NERDTreeMapHelp .": toggle help\n" + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Bookmark commands~\n" + let @h=@h."\" :Bookmark \n" + let @h=@h."\" :BookmarkToRoot \n" + let @h=@h."\" :RevealBookmark \n" + let @h=@h."\" :OpenBookmark \n" + let @h=@h."\" :ClearBookmarks []\n" + let @h=@h."\" :ClearAllBookmarks\n" + else + let @h="\" Press ". g:NERDTreeMapHelp ." for help\n" + endif + + silent! put h + + let @h = old_h +endfunction +"FUNCTION: s:echo {{{2 +"A wrapper for :echo. Appends 'NERDTree:' on the front of all messages +" +"Args: +"msg: the message to echo +function! s:echo(msg) + redraw + echomsg "NERDTree: " . a:msg +endfunction +"FUNCTION: s:echoWarning {{{2 +"Wrapper for s:echo, sets the message type to warningmsg for this message +"Args: +"msg: the message to echo +function! s:echoWarning(msg) + echohl warningmsg + call s:echo(a:msg) + echohl normal +endfunction +"FUNCTION: s:echoError {{{2 +"Wrapper for s:echo, sets the message type to errormsg for this message +"Args: +"msg: the message to echo +function! s:echoError(msg) + echohl errormsg + call s:echo(a:msg) + echohl normal +endfunction +"FUNCTION: s:findNodeLineNumber(treenode){{{2 +"Finds the line number for the given tree node +" +"Args: +"treenode: the node to find the line no. for +function! s:findNodeLineNumber(treenode) + "if the node is the root then return the root line no. + if a:treenode.isRoot() + return s:findRootNodeLineNumber() + endif + + let totalLines = line("$") + + "the path components we have matched so far + let pathcomponents = [substitute(t:NERDTreeRoot.path.str(0), '/ *$', '', '')] + "the index of the component we are searching for + let curPathComponent = 1 + + let fullpath = a:treenode.path.str(0) + + + let lnum = s:findRootNodeLineNumber() + while lnum > 0 + let lnum = lnum + 1 + "have we reached the bottom of the tree? + if lnum == totalLines+1 + return -1 + endif + + let curLine = getline(lnum) + + let indent = s:indentLevelFor(curLine) + if indent == curPathComponent + let curLine = s:stripMarkupFromLine(curLine, 1) + + let curPath = join(pathcomponents, '/') . '/' . curLine + if stridx(fullpath, curPath, 0) == 0 + if fullpath == curPath || strpart(fullpath, len(curPath)-1,1) == '/' + let curLine = substitute(curLine, '/ *$', '', '') + call add(pathcomponents, curLine) + let curPathComponent = curPathComponent + 1 + + if fullpath == curPath + return lnum + endif + endif + endif + endif + endwhile + return -1 +endfunction + +"FUNCTION: s:findRootNodeLineNumber(){{{2 +"Finds the line number of the root node +function! s:findRootNodeLineNumber() + let rootLine = 1 + while getline(rootLine) !~ '^/' + let rootLine = rootLine + 1 + endwhile + return rootLine +endfunction + +"FUNCTION: s:firstNormalWindow(){{{2 +"find the window number of the first normal window +function! s:firstNormalWindow() + let i = 1 + while i <= winnr("$") + let bnum = winbufnr(i) + if bnum != -1 && getbufvar(bnum, '&buftype') == '' + \ && !getwinvar(i, '&previewwindow') + return i + endif + + let i += 1 + endwhile + return -1 +endfunction +"FUNCTION: s:getPath(ln) {{{2 +"Gets the full path to the node that is rendered on the given line number +" +"Args: +"ln: the line number to get the path for +" +"Return: +"A path if a node was selected, {} if nothing is selected. +"If the 'up a dir' line was selected then the path to the parent of the +"current root is returned +function! s:getPath(ln) + let line = getline(a:ln) + + "check to see if we have the root node + if line =~ '^\/' + return t:NERDTreeRoot.path + endif + + " in case called from outside the tree + if line !~ '^ *[|`]' || line =~ '^$' + return {} + endif + + if line == s:tree_up_dir_line + return t:NERDTreeRoot.path.getParent() + endif + + let indent = s:indentLevelFor(line) + + "remove the tree parts and the leading space + let curFile = s:stripMarkupFromLine(line, 0) + + let wasdir = 0 + if curFile =~ '/$' + let wasdir = 1 + let curFile = substitute(curFile, '/\?$', '/', "") + endif + + + let dir = "" + let lnum = a:ln + while lnum > 0 + let lnum = lnum - 1 + let curLine = getline(lnum) + let curLineStripped = s:stripMarkupFromLine(curLine, 1) + + "have we reached the top of the tree? + if curLine =~ '^/' + let dir = substitute (curLine, ' *$', "", "") . dir + break + endif + if curLineStripped =~ '/$' + let lpindent = s:indentLevelFor(curLine) + if lpindent < indent + let indent = indent - 1 + + let dir = substitute (curLineStripped,'^\\', "", "") . dir + continue + endif + endif + endwhile + let curFile = t:NERDTreeRoot.path.drive . dir . curFile + let toReturn = s:Path.New(curFile) + return toReturn +endfunction + +"FUNCTION: s:getSelectedBookmark() {{{2 +"returns the bookmark the cursor is over in the bookmarks table or {} +function! s:getSelectedBookmark() + let line = getline(".") + let name = substitute(line, '^>\(.\{-}\) .\+$', '\1', '') + if name != line + try + return s:Bookmark.BookmarkFor(name) + catch /NERDTree.BookmarkNotFound/ + return {} + endtry + endif + return {} +endfunction + +"FUNCTION: s:getSelectedDir() {{{2 +"Returns the current node if it is a dir node, or else returns the current +"nodes parent +function! s:getSelectedDir() + let currentDir = s:getSelectedNode() + if currentDir != {} && !currentDir.isRoot() + if currentDir.path.isDirectory == 0 + let currentDir = currentDir.parent + endif + endif + return currentDir +endfunction +"FUNCTION: s:getSelectedNode() {{{2 +"gets the treenode that the cursor is currently over +function! s:getSelectedNode() + try + let path = s:getPath(line(".")) + if path == {} + return {} + endif + return t:NERDTreeRoot.findNode(path) + catch /^NERDTree/ + return {} + endtry +endfunction +"FUNCTION: s:getTreeWinNum() {{{2 +"gets the nerd tree window number for this tab +function! s:getTreeWinNum() + if exists("t:NERDTreeWinName") + return bufwinnr(t:NERDTreeWinName) + else + return -1 + endif +endfunction +"FUNCTION: s:indentLevelFor(line) {{{2 +function! s:indentLevelFor(line) + return match(a:line, '[^ \-+~`|]') / s:tree_wid +endfunction +"FUNCTION: s:isTreeOpen() {{{2 +function! s:isTreeOpen() + return s:getTreeWinNum() != -1 +endfunction +"FUNCTION: s:isWindowUsable(winnumber) {{{2 +"Returns 1 if opening a file from the tree in the given window requires it to +"be split +" +"Args: +"winnumber: the number of the window in question +function! s:isWindowUsable(winnumber) + "gotta split if theres only one window (i.e. the NERD tree) + if winnr("$") == 1 + return 0 + endif + + let oldwinnr = winnr() + exec a:winnumber . "wincmd p" + let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow') + let modified = &modified + exec oldwinnr . "wincmd p" + + "if its a special window e.g. quickfix or another explorer plugin then we + "have to split + if specialWindow + return 0 + endif + + if &hidden + return 1 + endif + + return !modified || s:bufInWindows(winbufnr(a:winnumber)) >= 2 +endfunction + +" FUNCTION: s:jumpToChild(direction) {{{2 +" Args: +" direction: 0 if going to first child, 1 if going to last +function! s:jumpToChild(direction) + let currentNode = s:getSelectedNode() + if currentNode == {} || currentNode.isRoot() + call s:echo("cannot jump to " . (a:direction ? "last" : "first") . " child") + return + end + let dirNode = currentNode.parent + let childNodes = dirNode.getVisibleChildren() + + let targetNode = childNodes[0] + if a:direction + let targetNode = childNodes[len(childNodes) - 1] + endif + + if targetNode.equals(currentNode) + let siblingDir = currentNode.parent.findOpenDirSiblingWithVisibleChildren(a:direction) + if siblingDir != {} + let indx = a:direction ? siblingDir.getVisibleChildCount()-1 : 0 + let targetNode = siblingDir.getChildByIndex(indx, 1) + endif + endif + + call s:putCursorOnNode(targetNode, 1, 0) + + call s:centerView() +endfunction + + +"FUNCTION: s:openDirNodeSplit(treenode) {{{2 +"Open the file represented by the given node in a new window. +"No action is taken for file nodes +" +"ARGS: +"treenode: file node to open +function! s:openDirNodeSplit(treenode) + if a:treenode.path.isDirectory == 1 + call s:openNodeSplit(a:treenode) + endif +endfunction + +" FUNCTION: s:openExplorerFor(treenode) {{{2 +" opens a netrw window for the given dir treenode +function! s:openExplorerFor(treenode) + let oldwin = winnr() + wincmd p + if oldwin == winnr() || (&modified && s:bufInWindows(winbufnr(winnr())) < 2) + wincmd p + call s:openDirNodeSplit(a:treenode) + else + exec ("silent edit " . a:treenode.path.strForEditCmd()) + endif +endfunction +"FUNCTION: s:openFileNode(treenode) {{{2 +"Open the file represented by the given node in the current window, splitting +"the window if needed +" +"ARGS: +"treenode: file node to open +function! s:openFileNode(treenode) + call s:putCursorInTreeWin() + + "if the file is already open in this tab then just stick the cursor in it + let winnr = bufwinnr('^' . a:treenode.path.strForOS(0) . '$') + if winnr != -1 + exec winnr . "wincmd w" + + else + if !s:isWindowUsable(winnr("#")) && s:firstNormalWindow() == -1 + call s:openFileNodeSplit(a:treenode) + else + try + if !s:isWindowUsable(winnr("#")) + exec s:firstNormalWindow() . "wincmd w" + else + wincmd p + endif + exec ("edit " . a:treenode.path.strForEditCmd()) + catch /^Vim\%((\a\+)\)\=:E37/ + call s:putCursorInTreeWin() + call s:echo("Cannot open file, it is already open and modified") + catch /^Vim\%((\a\+)\)\=:/ + echo v:exception + endtry + endif + endif +endfunction + +"FUNCTION: s:openFileNodeSplit(treenode) {{{2 +"Open the file represented by the given node in a new window. +"No action is taken for dir nodes +" +"ARGS: +"treenode: file node to open +function! s:openFileNodeSplit(treenode) + if a:treenode.path.isDirectory == 0 + try + call s:openNodeSplit(a:treenode) + catch /^NERDTree.view.FileOpen/ + call s:echo("Cannot open file, it is already open and modified" ) + endtry + endif +endfunction + +"FUNCTION: s:openNodeSplit(treenode) {{{2 +"Open the file/dir represented by the given node in a new window +" +"ARGS: +"treenode: file node to open +function! s:openNodeSplit(treenode) + call s:putCursorInTreeWin() + + " Save the user's settings for splitbelow and splitright + let savesplitbelow=&splitbelow + let savesplitright=&splitright + + " Figure out how to do the split based on the user's preferences. + " We want to split to the (left,right,top,bottom) of the explorer + " window, but we want to extract the screen real-estate from the + " window next to the explorer if possible. + " + " 'there' will be set to a command to move from the split window + " back to the explorer window + " + " 'back' will be set to a command to move from the explorer window + " back to the newly split window + " + " 'right' and 'below' will be set to the settings needed for + " splitbelow and splitright IF the explorer is the only window. + " + if s:shouldSplitVertically() + let there= g:NERDTreeWinPos == "left" ? "wincmd h" : "wincmd l" + let back = g:NERDTreeWinPos == "left" ? "wincmd l" : "wincmd h" + let right= g:NERDTreeWinPos == "left" + let below=0 + else + let there= g:NERDTreeWinPos == "top" ? "wincmd k" : "wincmd j" + let back = g:NERDTreeWinPos == "top" ? "wincmd j" : "wincmd k" + let below= g:NERDTreeWinPos == "top" + let right=0 + endif + + " Attempt to go to adjacent window + exec(back) + + let onlyOneWin = (winnr() == s:getTreeWinNum()) + + " If no adjacent window, set splitright and splitbelow appropriately + if onlyOneWin + let &splitright=right + let &splitbelow=below + else + " found adjacent window - invert split direction + let &splitright=!right + let &splitbelow=!below + endif + + " Create a variable to use if splitting vertically + let splitMode = "" + if (onlyOneWin && s:shouldSplitVertically()) || (!onlyOneWin && !s:shouldSplitVertically()) + let splitMode = "vertical" + endif + + echomsg splitMode + + " Open the new window + try + exec(splitMode." sp " . a:treenode.path.strForEditCmd()) + catch /^Vim\%((\a\+)\)\=:E37/ + call s:putCursorInTreeWin() + throw "NERDTree.view.FileOpen exception: ". a:treenode.path.str(0) ." is already open and modified." + catch /^Vim\%((\a\+)\)\=:/ + "do nothing + endtry + + "resize the tree window if no other window was open before + if onlyOneWin + let size = exists("t:NERDTreeOldWindowSize") ? t:NERDTreeOldWindowSize : g:NERDTreeWinSize + exec(there) + exec("silent ". splitMode ." resize ". size) + wincmd p + endif + + " Restore splitmode settings + let &splitbelow=savesplitbelow + let &splitright=savesplitright +endfunction + +"FUNCTION: s:promptToDelBuffer(bufnum, msg){{{2 +"prints out the given msg and, if the user responds by pushing 'y' then the +"buffer with the given bufnum is deleted +" +"Args: +"bufnum: the buffer that may be deleted +"msg: a message that will be echoed to the user asking them if they wish to +" del the buffer +function! s:promptToDelBuffer(bufnum, msg) + echo a:msg + if nr2char(getchar()) == 'y' + exec "silent bdelete! " . a:bufnum + endif +endfunction + +"FUNCTION: s:putCursorOnBookmarkTable(){{{2 +"Places the cursor at the top of the bookmarks table +function! s:putCursorOnBookmarkTable() + if !t:NERDTreeShowBookmarks + throw "NERDTree.IllegalOperation exception: cant find bookmark table, bookmarks arent active" + endif + + let rootNodeLine = s:findRootNodeLineNumber() + + let line = 1 + while getline(line) !~ '^>-\+Bookmarks-\+$' + let line = line + 1 + if line >= rootNodeLine + throw "NERDTree.BookmarkTableNotFound exception: didnt find the bookmarks table" + endif + endwhile + call cursor(line, 0) +endfunction + +"FUNCTION: s:putCursorOnNode(treenode, isJump, recurseUpward){{{2 +"Places the cursor on the line number representing the given node +" +"Args: +"treenode: the node to put the cursor on +"isJump: 1 if this cursor movement should be counted as a jump by vim +"recurseUpward: try to put the cursor on the parent if the this node isnt +"visible +function! s:putCursorOnNode(treenode, isJump, recurseUpward) + let ln = s:findNodeLineNumber(a:treenode) + if ln != -1 + if a:isJump + mark ' + endif + call cursor(ln, col(".")) + else + if a:recurseUpward + let node = a:treenode + while s:findNodeLineNumber(node) == -1 && node != {} + let node = node.parent + call node.open() + endwhile + call s:renderView() + call s:putCursorOnNode(a:treenode, a:isJump, 0) + endif + endif +endfunction + +"FUNCTION: s:putCursorInTreeWin(){{{2 +"Places the cursor in the nerd tree window +function! s:putCursorInTreeWin() + if !s:isTreeOpen() + throw "NERDTree.view.InvalidOperation Exception: No NERD tree window exists" + endif + + exec s:getTreeWinNum() . "wincmd w" +endfunction + +"FUNCTION: s:renderBookmarks {{{2 +function! s:renderBookmarks() + + call setline(line(".")+1, ">----------Bookmarks----------") + call cursor(line(".")+1, col(".")) + + for i in s:Bookmark.Bookmarks() + call setline(line(".")+1, i.str()) + call cursor(line(".")+1, col(".")) + endfor + + call setline(line(".")+1, '') + call cursor(line(".")+1, col(".")) +endfunction +"FUNCTION: s:renderView {{{2 +"The entry function for rendering the tree. Renders the root then calls +"s:drawTree to draw the children of the root +" +"Args: +function! s:renderView() + execute s:getTreeWinNum() . "wincmd w" + + setlocal modifiable + + "remember the top line of the buffer and the current line so we can + "restore the view exactly how it was + let curLine = line(".") + let curCol = col(".") + let topLine = line("w0") + + "delete all lines in the buffer (being careful not to clobber a register) + silent 1,$delete _ + + call s:dumpHelp() + + "delete the blank line before the help and add one after it + call setline(line(".")+1, "") + call cursor(line(".")+1, col(".")) + + if t:NERDTreeShowBookmarks + call s:renderBookmarks() + endif + + "add the 'up a dir' line + call setline(line(".")+1, s:tree_up_dir_line) + call cursor(line(".")+1, col(".")) + + "draw the header line + call setline(line(".")+1, t:NERDTreeRoot.path.str(0)) + call cursor(line(".")+1, col(".")) + + "draw the tree + call s:drawTree(t:NERDTreeRoot, 0, 0, [], t:NERDTreeRoot.getChildCount() == 1) + + "delete the blank line at the top of the buffer + silent 1,1delete _ + + "restore the view + let old_scrolloff=&scrolloff + let &scrolloff=0 + call cursor(topLine, 1) + normal! zt + call cursor(curLine, curCol) + let &scrolloff = old_scrolloff + + setlocal nomodifiable +endfunction + +"FUNCTION: s:renderViewSavingPosition {{{2 +"Renders the tree and ensures the cursor stays on the current node or the +"current nodes parent if it is no longer available upon re-rendering +function! s:renderViewSavingPosition() + let currentNode = s:getSelectedNode() + + "go up the tree till we find a node that will be visible or till we run + "out of nodes + while currentNode != {} && !currentNode.isVisible() && !currentNode.isRoot() + let currentNode = currentNode.parent + endwhile + + call s:renderView() + + if currentNode != {} + call s:putCursorOnNode(currentNode, 0, 0) + endif +endfunction +"FUNCTION: s:restoreScreenState() {{{2 +" +"Sets the screen state back to what it was when s:saveScreenState was last +"called. +" +"Assumes the cursor is in the NERDTree window +function! s:restoreScreenState() + if !exists("t:NERDTreeOldTopLine") || !exists("t:NERDTreeOldPos") || !exists("t:NERDTreeOldWindowSize") + return + endif + exec("silent ". (s:shouldSplitVertically() ? "vertical" : "") ." resize ".t:NERDTreeOldWindowSize) + + let old_scrolloff=&scrolloff + let &scrolloff=0 + call cursor(t:NERDTreeOldTopLine, 0) + normal! zt + call setpos(".", t:NERDTreeOldPos) + let &scrolloff=old_scrolloff +endfunction + +"FUNCTION: s:saveScreenState() {{{2 +"Saves the current cursor position in the current buffer and the window +"scroll position +function! s:saveScreenState() + let win = winnr() + try + call s:putCursorInTreeWin() + let t:NERDTreeOldPos = getpos(".") + let t:NERDTreeOldTopLine = line("w0") + let t:NERDTreeOldWindowSize = s:shouldSplitVertically() ? winwidth("") : winheight("") + exec win . "wincmd w" + catch /NERDTree.view.InvalidOperation/ + endtry +endfunction + +"FUNCTION: s:setupSyntaxHighlighting() {{{2 +function! s:setupSyntaxHighlighting() + "treeFlags are syntax items that should be invisible, but give clues as to + "how things should be highlighted + syn match treeFlag #\~# + syn match treeFlag #\[RO\]# + + "highlighting for the .. (up dir) line at the top of the tree + execute "syn match treeUp #". s:tree_up_dir_line ."#" + + "highlighting for the ~/+ symbols for the directory nodes + syn match treeClosable #\~\<# + syn match treeClosable #\~\.# + syn match treeOpenable #+\<# + syn match treeOpenable #+\.#he=e-1 + + "highlighting for the tree structural parts + syn match treePart #|# + syn match treePart #`# + syn match treePartFile #[|`]-#hs=s+1 contains=treePart + + "quickhelp syntax elements + syn match treeHelpKey #" \{1,2\}[^ ]*:#hs=s+2,he=e-1 + syn match treeHelpKey #" \{1,2\}[^ ]*,#hs=s+2,he=e-1 + syn match treeHelpTitle #" .*\~#hs=s+2,he=e-1 contains=treeFlag + syn match treeToggleOn #".*(on)#hs=e-2,he=e-1 contains=treeHelpKey + syn match treeToggleOff #".*(off)#hs=e-3,he=e-1 contains=treeHelpKey + syn match treeHelpCommand #" :.\{-}\>#hs=s+3 + syn match treeHelp #^".*# contains=treeHelpKey,treeHelpTitle,treeFlag,treeToggleOff,treeToggleOn,treeHelpCommand + + "highlighting for readonly files + syn match treeRO #[\/0-9a-zA-Z]\+.*\[RO\]# contains=treeFlag,treeBookmark + + "highlighting for sym links + syn match treeLink #[^-| `].* -> # contains=treeBookmark,treeOpenable,treeClosable,treeDirSlash + + "highlighing for directory nodes and file nodes + syn match treeDirSlash #/# + syn match treeDir #[^-| `].*/# contains=treeLink,treeDirSlash,treeOpenable,treeClosable + syn match treeExecFile #[|`]-.*\*\($\| \)# contains=treeLink,treePart,treeRO,treePartFile,treeBookmark + syn match treeFile #|-.*# contains=treeLink,treePart,treeRO,treePartFile,treeBookmark,treeExecFile + syn match treeFile #`-.*# contains=treeLink,treePart,treeRO,treePartFile,treeBookmark,treeExecFile + syn match treeCWD #^/.*$# + + "highlighting for bookmarks + syn match treeBookmark # {.*}#hs=s+1 + + "highlighting for the bookmarks table + syn match treeBookmarksLeader #^># + syn match treeBookmarksHeader #^>-\+Bookmarks-\+$# contains=treeBookmarksLeader + syn match treeBookmarkName #^>.\{-} #he=e-1 contains=treeBookmarksLeader + syn match treeBookmark #^>.*$# contains=treeBookmarksLeader,treeBookmarkName,treeBookmarksHeader + + if g:NERDChristmasTree + hi def link treePart Special + hi def link treePartFile Type + hi def link treeFile Normal + hi def link treeExecFile Title + hi def link treeDirSlash Identifier + hi def link treeClosable Type + else + hi def link treePart Normal + hi def link treePartFile Normal + hi def link treeFile Normal + hi def link treeClosable Title + endif + + hi def link treeBookmarksHeader statement + hi def link treeBookmarksLeader ignore + hi def link treeBookmarkName Identifier + hi def link treeBookmark normal + + hi def link treeHelp String + hi def link treeHelpKey Identifier + hi def link treeHelpCommand Identifier + hi def link treeHelpTitle Macro + hi def link treeToggleOn Question + hi def link treeToggleOff WarningMsg + + hi def link treeDir Directory + hi def link treeUp Directory + hi def link treeCWD Statement + hi def link treeLink Macro + hi def link treeOpenable Title + hi def link treeFlag ignore + hi def link treeRO WarningMsg + hi def link treeBookmark Statement + + hi def link NERDTreeCurrentNode Search +endfunction + +" Function: s:shouldSplitVertically() {{{2 +" Returns 1 if g:NERDTreeWinPos is 'left' or 'right' +function! s:shouldSplitVertically() + return g:NERDTreeWinPos == 'left' || g:NERDTreeWinPos == 'right' +endfunction +"FUNCTION: s:stripMarkupFromLine(line, removeLeadingSpaces){{{2 +"returns the given line with all the tree parts stripped off +" +"Args: +"line: the subject line +"removeLeadingSpaces: 1 if leading spaces are to be removed (leading spaces = +"any spaces before the actual text of the node) +function! s:stripMarkupFromLine(line, removeLeadingSpaces) + let line = a:line + "remove the tree parts and the leading space + let line = substitute (line, s:tree_markup_reg,"","") + + "strip off any read only flag + let line = substitute (line, ' \[RO\]', "","") + + "strip off any bookmark flags + let line = substitute (line, ' {[^}]*}', "","") + + "strip off any executable flags + let line = substitute (line, '*\ze\($\| \)', "","") + + let wasdir = 0 + if line =~ '/$' + let wasdir = 1 + endif + let line = substitute (line,' -> .*',"","") " remove link to + if wasdir == 1 + let line = substitute (line, '/\?$', '/', "") + endif + + if a:removeLeadingSpaces + let line = substitute (line, '^ *', '', '') + endif + + return line +endfunction + +"FUNCTION: s:toggle(dir) {{{2 +"Toggles the NERD tree. I.e the NERD tree is open, it is closed, if it is +"closed it is restored or initialized (if it doesnt exist) +" +"Args: +"dir: the full path for the root node (is only used if the NERD tree is being +"initialized. +function! s:toggle(dir) + if s:treeExistsForTab() + if !s:isTreeOpen() + call s:createTreeWin() + call s:renderView() + + call s:restoreScreenState() + else + call s:closeTree() + endif + else + call s:initNerdTree(a:dir) + endif +endfunction +"SECTION: Interface bindings {{{1 +"============================================================ +"FUNCTION: s:activateNode(forceKeepWindowOpen) {{{2 +"If the current node is a file, open it in the previous window (or a new one +"if the previous is modified). If it is a directory then it is opened. +" +"args: +"forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set +function! s:activateNode(forceKeepWindowOpen) + if getline(".") == s:tree_up_dir_line + return s:upDir(0) + endif + + let treenode = s:getSelectedNode() + if treenode != {} + if treenode.path.isDirectory + call treenode.toggleOpen() + call s:renderView() + call s:putCursorOnNode(treenode, 0, 0) + else + call s:openFileNode(treenode) + if !a:forceKeepWindowOpen + call s:closeTreeIfQuitOnOpen() + end + endif + else + let bookmark = s:getSelectedBookmark() + if !empty(bookmark) + if bookmark.path.isDirectory + call bookmark.toRoot() + else + if bookmark.validate() + call s:openFileNode(s:TreeFileNode.New(bookmark.path)) + endif + endif + endif + endif +endfunction + +"FUNCTION: s:bindMappings() {{{2 +function! s:bindMappings() + " set up mappings and commands for this buffer + nnoremap :call handleMiddleMouse() + nnoremap :call checkForActivate() + nnoremap <2-leftmouse> :call activateNode(0) + + exec "nnoremap ". g:NERDTreeMapActivateNode . " :call activateNode(0)" + exec "nnoremap ". g:NERDTreeMapOpenSplit ." :call openEntrySplit(0)" + + exec "nnoremap ". g:NERDTreeMapPreview ." :call previewNode(0)" + exec "nnoremap ". g:NERDTreeMapPreviewSplit ." :call previewNode(1)" + + + exec "nnoremap ". g:NERDTreeMapExecute ." :call executeNode()" + + exec "nnoremap ". g:NERDTreeMapOpenRecursively ." :call openNodeRecursively()" + + exec "nnoremap ". g:NERDTreeMapUpdirKeepOpen ." :call upDir(1)" + exec "nnoremap ". g:NERDTreeMapUpdir ." :call upDir(0)" + exec "nnoremap ". g:NERDTreeMapChangeRoot ." :call chRoot()" + + exec "nnoremap ". g:NERDTreeMapChdir ." :call chCwd()" + + exec "nnoremap ". g:NERDTreeMapQuit ." :NERDTreeToggle" + + exec "nnoremap ". g:NERDTreeMapRefreshRoot ." :call refreshRoot()" + exec "nnoremap ". g:NERDTreeMapRefresh ." :call refreshCurrent()" + + exec "nnoremap ". g:NERDTreeMapHelp ." :call displayHelp()" + exec "nnoremap ". g:NERDTreeMapToggleHidden ." :call toggleShowHidden()" + exec "nnoremap ". g:NERDTreeMapToggleFilters ." :call toggleIgnoreFilter()" + exec "nnoremap ". g:NERDTreeMapToggleFiles ." :call toggleShowFiles()" + exec "nnoremap ". g:NERDTreeMapToggleBookmarks ." :call toggleShowBookmarks()" + + exec "nnoremap ". g:NERDTreeMapCloseDir ." :call closeCurrentDir()" + exec "nnoremap ". g:NERDTreeMapCloseChildren ." :call closeChildren()" + + exec "nnoremap ". g:NERDTreeMapFilesystemMenu ." :call showFileSystemMenu()" + + exec "nnoremap ". g:NERDTreeMapJumpParent ." :call jumpToParent()" + exec "nnoremap ". g:NERDTreeMapJumpNextSibling ." :call jumpToSibling(1)" + exec "nnoremap ". g:NERDTreeMapJumpPrevSibling ." :call jumpToSibling(0)" + exec "nnoremap ". g:NERDTreeMapJumpFirstChild ." :call jumpToFirstChild()" + exec "nnoremap ". g:NERDTreeMapJumpLastChild ." :call jumpToLastChild()" + exec "nnoremap ". g:NERDTreeMapJumpRoot ." :call jumpToRoot()" + + exec "nnoremap ". g:NERDTreeMapOpenInTab ." :call openInNewTab(0)" + exec "nnoremap ". g:NERDTreeMapOpenInTabSilent ." :call openInNewTab(1)" + + exec "nnoremap ". g:NERDTreeMapOpenExpl ." :call openExplorer()" + + exec "nnoremap ". g:NERDTreeMapDeleteBookmark ." :call deleteBookmark()" + + command! -buffer -nargs=1 Bookmark :call bookmarkNode('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=1 RevealBookmark :call revealBookmark('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=1 OpenBookmark :call openBookmark('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=* ClearBookmarks call clearBookmarks('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=+ BookmarkToRoot call s:Bookmark.ToRoot('') + command! -buffer -nargs=0 ClearAllBookmarks call s:Bookmark.ClearAll() call renderView() + command! -buffer -nargs=0 ReadBookmarks call s:Bookmark.CacheBookmarks(0) call renderView() + command! -buffer -nargs=0 WriteBookmarks call s:Bookmark.Write() +endfunction + +" FUNCTION: s:bookmarkNode(name) {{{2 +" Associate the current node with the given name +function! s:bookmarkNode(name) + let currentNode = s:getSelectedNode() + if currentNode != {} + try + call currentNode.bookmark(a:name) + call s:renderView() + catch /NERDTree.IllegalBookmarkName/ + call s:echo("bookmark names must not contain spaces") + endtry + else + call s:echo("select a node first") + endif +endfunction +"FUNCTION: s:checkForActivate() {{{2 +"Checks if the click should open the current node, if so then activate() is +"called (directories are automatically opened if the symbol beside them is +"clicked) +function! s:checkForActivate() + let currentNode = s:getSelectedNode() + if currentNode != {} + let startToCur = strpart(getline(line(".")), 0, col(".")) + let char = strpart(startToCur, strlen(startToCur)-1, 1) + + "if they clicked a dir, check if they clicked on the + or ~ sign + "beside it + if currentNode.path.isDirectory + if startToCur =~ s:tree_markup_reg . '$' && char =~ '[+~]' + call s:activateNode(0) + return + endif + endif + + if (g:NERDTreeMouseMode == 2 && currentNode.path.isDirectory) || g:NERDTreeMouseMode == 3 + if char !~ s:tree_markup_reg && startToCur !~ '\/$' + call s:activateNode(0) + return + endif + endif + endif +endfunction + +" FUNCTION: s:chCwd() {{{2 +function! s:chCwd() + let treenode = s:getSelectedNode() + if treenode == {} + call s:echo("Select a node first") + return + endif + + try + call treenode.path.changeToDir() + catch /^NERDTree.Path.Change/ + call s:echoWarning("could not change cwd") + endtry +endfunction + +" FUNCTION: s:chRoot() {{{2 +" changes the current root to the selected one +function! s:chRoot() + let treenode = s:getSelectedNode() + if treenode == {} + call s:echo("Select a node first") + return + endif + + call treenode.makeRoot() + call s:renderView() + call s:putCursorOnNode(t:NERDTreeRoot, 0, 0) +endfunction + +" FUNCTION: s:clearBookmarks(bookmarks) {{{2 +function! s:clearBookmarks(bookmarks) + if a:bookmarks == '' + let currentNode = s:getSelectedNode() + if currentNode != {} + call currentNode.clearBoomarks() + endif + else + for name in split(a:bookmarks, ' ') + let bookmark = s:Bookmark.BookmarkFor(name) + call bookmark.delete() + endfor + endif + call s:renderView() +endfunction +" FUNCTION: s:closeChildren() {{{2 +" closes all childnodes of the current node +function! s:closeChildren() + let currentNode = s:getSelectedDir() + if currentNode == {} + call s:echo("Select a node first") + return + endif + + call currentNode.closeChildren() + call s:renderView() + call s:putCursorOnNode(currentNode, 0, 0) +endfunction +" FUNCTION: s:closeCurrentDir() {{{2 +" closes the parent dir of the current node +function! s:closeCurrentDir() + let treenode = s:getSelectedNode() + if treenode == {} + call s:echo("Select a node first") + return + endif + + let parent = treenode.parent + if parent.isRoot() + call s:echo("cannot close tree root") + else + call treenode.parent.close() + call s:renderView() + call s:putCursorOnNode(treenode.parent, 0, 0) + endif +endfunction + +" FUNCTION: s:copyNode() {{{2 +function! s:copyNode() + let currentNode = s:getSelectedNode() + if currentNode == {} + call s:echo("Put the cursor on a file node first") + return + endif + + let newNodePath = input("Copy the current node\n" . + \ "==========================================================\n" . + \ "Enter the new path to copy the node to: \n" . + \ "", currentNode.path.str(0)) + + if newNodePath != "" + "strip trailing slash + let newNodePath = substitute(newNodePath, '\/$', '', '') + + let confirmed = 1 + if currentNode.path.copyingWillOverwrite(newNodePath) + call s:echo("\nWarning: copying may overwrite files! Continue? (yN)") + let choice = nr2char(getchar()) + let confirmed = choice == 'y' + endif + + if confirmed + try + let newNode = currentNode.copy(newNodePath) + call s:renderView() + call s:putCursorOnNode(newNode, 0, 0) + catch /^NERDTree/ + call s:echoWarning("Could not copy node") + endtry + endif + else + call s:echo("Copy aborted.") + endif + redraw +endfunction + +" FUNCTION: s:deleteBookmark() {{{2 +" if the cursor is on a bookmark, prompt to delete +function! s:deleteBookmark() + let bookmark = s:getSelectedBookmark() + if bookmark == {} + call s:echo("Put the cursor on a bookmark") + return + endif + + echo "Are you sure you wish to delete the bookmark:\n\"" . bookmark.name . "\" (yN):" + + if nr2char(getchar()) == 'y' + try + call bookmark.delete() + call s:renderView() + redraw + catch /^NERDTree/ + call s:echoWarning("Could not remove bookmark") + endtry + else + call s:echo("delete aborted" ) + endif + +endfunction + +" FUNCTION: s:deleteNode() {{{2 +" if the current node is a file, pops up a dialog giving the user the option +" to delete it +function! s:deleteNode() + let currentNode = s:getSelectedNode() + if currentNode == {} + call s:echo("Put the cursor on a file node first") + return + endif + + let confirmed = 0 + + if currentNode.path.isDirectory + let choice =input("Delete the current node\n" . + \ "==========================================================\n" . + \ "STOP! To delete this entire directory, type 'yes'\n" . + \ "" . currentNode.path.strForOS(0) . ": ") + let confirmed = choice == 'yes' + else + echo "Delete the current node\n" . + \ "==========================================================\n". + \ "Are you sure you wish to delete the node:\n" . + \ "" . currentNode.path.strForOS(0) . " (yN):" + let choice = nr2char(getchar()) + let confirmed = choice == 'y' + endif + + + if confirmed + try + call currentNode.delete() + call s:renderView() + + "if the node is open in a buffer, ask the user if they want to + "close that buffer + let bufnum = bufnr(currentNode.path.str(0)) + if buflisted(bufnum) + let prompt = "\nNode deleted.\n\nThe file is open in buffer ". bufnum . (bufwinnr(bufnum) == -1 ? " (hidden)" : "") .". Delete this buffer? (yN)" + call s:promptToDelBuffer(bufnum, prompt) + endif + + redraw + catch /^NERDTree/ + call s:echoWarning("Could not remove node") + endtry + else + call s:echo("delete aborted" ) + endif + +endfunction + +" FUNCTION: s:displayHelp() {{{2 +" toggles the help display +function! s:displayHelp() + let t:treeShowHelp = t:treeShowHelp ? 0 : 1 + call s:renderView() + call s:centerView() +endfunction + +" FUNCTION: s:executeNode() {{{2 +function! s:executeNode() + let treenode = s:getSelectedNode() + if treenode == {} || treenode.path.isDirectory + call s:echo("Select an executable file node first" ) + else + echo "NERDTree executor\n" . + \ "==========================================================\n". + \ "Complete the command to execute (add arguments etc): \n\n" + let cmd = treenode.path.strForOS(1) + let cmd = input(':!', cmd . ' ') + + if cmd != '' + exec ':!' . cmd + else + call s:echo("command aborted") + endif + endif +endfunction + +" FUNCTION: s:handleMiddleMouse() {{{2 +function! s:handleMiddleMouse() + let curNode = s:getSelectedNode() + if curNode == {} + call s:echo("Put the cursor on a node first" ) + return + endif + + if curNode.path.isDirectory + call s:openExplorer() + else + call s:openEntrySplit(0) + endif +endfunction + + +" FUNCTION: s:insertNewNode() {{{2 +" Adds a new node to the filesystem and then into the tree +function! s:insertNewNode() + let curDirNode = s:getSelectedDir() + if curDirNode == {} + call s:echo("Put the cursor on a node first" ) + return + endif + + let newNodeName = input("Add a childnode\n". + \ "==========================================================\n". + \ "Enter the dir/file name to be created. Dirs end with a '/'\n" . + \ "", curDirNode.path.strForGlob() . s:os_slash) + + if newNodeName == '' + call s:echo("Node Creation Aborted.") + return + endif + + try + let newPath = s:Path.Create(newNodeName) + let parentNode = t:NERDTreeRoot.findNode(newPath.getPathTrunk()) + + let newTreeNode = s:TreeFileNode.New(newPath) + if parentNode.isOpen || !empty(parentNode.children) + call parentNode.addChild(newTreeNode, 1) + call s:renderView() + call s:putCursorOnNode(newTreeNode, 1, 0) + endif + catch /^NERDTree/ + call s:echoWarning("Node Not Created.") + endtry +endfunction + +" FUNCTION: s:jumpToFirstChild() {{{2 +" wrapper for the jump to child method +function! s:jumpToFirstChild() + call s:jumpToChild(0) +endfunction + +" FUNCTION: s:jumpToLastChild() {{{2 +" wrapper for the jump to child method +function! s:jumpToLastChild() + call s:jumpToChild(1) +endfunction + +" FUNCTION: s:jumpToParent() {{{2 +" moves the cursor to the parent of the current node +function! s:jumpToParent() + let currentNode = s:getSelectedNode() + if !empty(currentNode) + if !empty(currentNode.parent) + call s:putCursorOnNode(currentNode.parent, 1, 0) + call s:centerView() + else + call s:echo("cannot jump to parent") + endif + else + call s:echo("put the cursor on a node first") + endif +endfunction + +" FUNCTION: s:jumpToRoot() {{{2 +" moves the cursor to the root node +function! s:jumpToRoot() + call s:putCursorOnNode(t:NERDTreeRoot, 1, 0) + call s:centerView() +endfunction + +" FUNCTION: s:jumpToSibling() {{{2 +" moves the cursor to the sibling of the current node in the given direction +" +" Args: +" forward: 1 if the cursor should move to the next sibling, 0 if it should +" move back to the previous sibling +function! s:jumpToSibling(forward) + let currentNode = s:getSelectedNode() + if !empty(currentNode) + let sibling = currentNode.findSibling(a:forward) + + if !empty(sibling) + call s:putCursorOnNode(sibling, 1, 0) + call s:centerView() + endif + else + call s:echo("put the cursor on a node first") + endif +endfunction + +" FUNCTION: s:openBookmark(name) {{{2 +" put the cursor on the given bookmark and, if its a file, open it +function! s:openBookmark(name) + try + let targetNode = s:Bookmark.GetNodeForName(a:name, 0) + call s:putCursorOnNode(targetNode, 0, 1) + redraw! + catch /NERDTree.BookmarkedNodeNotFound/ + call s:echo("note - target node is not cached") + let bookmark = s:Bookmark.BookmarkFor(a:name) + let targetNode = s:TreeFileNode.New(bookmark.path) + endtry + if targetNode.path.isDirectory + call s:openExplorerFor(targetNode) + else + call s:openFileNode(targetNode) + endif +endfunction +" FUNCTION: s:openEntrySplit(forceKeepWindowOpen) {{{2 +"Opens the currently selected file from the explorer in a +"new window +" +"args: +"forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set +function! s:openEntrySplit(forceKeepWindowOpen) + let treenode = s:getSelectedNode() + if treenode != {} + call s:openFileNodeSplit(treenode) + if !a:forceKeepWindowOpen + call s:closeTreeIfQuitOnOpen() + endif + else + call s:echo("select a node first") + endif +endfunction + +" FUNCTION: s:openExplorer() {{{2 +function! s:openExplorer() + let treenode = s:getSelectedDir() + if treenode != {} + call s:openExplorerFor(treenode) + else + call s:echo("select a node first") + endif +endfunction + +" FUNCTION: s:openInNewTab(stayCurrentTab) {{{2 +" Opens the selected node or bookmark in a new tab +" Args: +" stayCurrentTab: if 1 then vim will stay in the current tab, if 0 then vim +" will go to the tab where the new file is opened +function! s:openInNewTab(stayCurrentTab) + let currentTab = tabpagenr() + + let treenode = s:getSelectedNode() + if treenode != {} + if treenode.path.isDirectory + tabnew + call s:initNerdTree(treenode.path.strForOS(0)) + else + exec "tabedit " . treenode.path.strForEditCmd() + endif + else + let bookmark = s:getSelectedBookmark() + if bookmark != {} + if bookmark.path.isDirectory + tabnew + call s:initNerdTree(bookmark.name) + else + exec "tabedit " . bookmark.path.strForEditCmd() + endif + endif + endif + if a:stayCurrentTab + exec "tabnext " . currentTab + endif +endfunction + +" FUNCTION: s:openNodeRecursively() {{{2 +function! s:openNodeRecursively() + let treenode = s:getSelectedNode() + if treenode == {} || treenode.path.isDirectory == 0 + call s:echo("Select a directory node first" ) + else + call s:echo("Recursively opening node. Please wait...") + call treenode.openRecursively() + call s:renderView() + redraw + call s:echo("Recursively opening node. Please wait... DONE") + endif + +endfunction + +"FUNCTION: s:previewNode() {{{2 +function! s:previewNode(openNewWin) + if a:openNewWin + call s:openEntrySplit(1) + else + call s:activateNode(1) + end + call s:putCursorInTreeWin() +endfunction + +" FUNCTION: s:revealBookmark(name) {{{2 +" put the cursor on the node associate with the given name +function! s:revealBookmark(name) + try + let targetNode = s:Bookmark.GetNodeForName(a:name, 0) + call s:putCursorOnNode(targetNode, 0, 1) + catch /NERDTree.BookmarkDoesntExist/ + call s:echo("Bookmark isnt cached under the current root") + endtry +endfunction +" FUNCTION: s:refreshRoot() {{{2 +" Reloads the current root. All nodes below this will be lost and the root dir +" will be reloaded. +function! s:refreshRoot() + call s:echo("Refreshing the root node. This could take a while...") + call t:NERDTreeRoot.refresh() + call s:renderView() + redraw + call s:echo("Refreshing the root node. This could take a while... DONE") +endfunction + +" FUNCTION: s:refreshCurrent() {{{2 +" refreshes the root for the current node +function! s:refreshCurrent() + let treenode = s:getSelectedDir() + if treenode == {} + call s:echo("Refresh failed. Select a node first") + return + endif + + call s:echo("Refreshing node. This could take a while...") + call treenode.refresh() + call s:renderView() + redraw + call s:echo("Refreshing node. This could take a while... DONE") +endfunction +" FUNCTION: s:renameCurrent() {{{2 +" allows the user to rename the current node +function! s:renameCurrent() + let curNode = s:getSelectedNode() + if curNode == {} + call s:echo("Put the cursor on a node first" ) + return + endif + + let newNodePath = input("Rename the current node\n" . + \ "==========================================================\n" . + \ "Enter the new path for the node: \n" . + \ "", curNode.path.strForOS(0)) + + if newNodePath == '' + call s:echo("Node Renaming Aborted.") + return + endif + + try + let bufnum = bufnr(curNode.path.str(0)) + + call curNode.rename(newNodePath) + call s:renderView() + + "if the node is open in a buffer, ask the user if they want to + "close that buffer + if bufnum != -1 + let prompt = "\nNode renamed.\n\nThe old file is open in buffer ". bufnum . (bufwinnr(bufnum) == -1 ? " (hidden)" : "") .". Delete this buffer? (yN)" + call s:promptToDelBuffer(bufnum, prompt) + endif + + call s:putCursorOnNode(curNode, 1, 0) + + redraw + catch /^NERDTree/ + call s:echoWarning("Node Not Renamed.") + endtry +endfunction + +" FUNCTION: s:showFileSystemMenu() {{{2 +function! s:showFileSystemMenu() + let curNode = s:getSelectedNode() + if curNode == {} + call s:echo("Put the cursor on a node first" ) + return + endif + + + let prompt = "NERDTree Filesystem Menu\n" . + \ "==========================================================\n". + \ "Select the desired operation: \n" . + \ " (a)dd a childnode\n". + \ " (m)ove the current node\n". + \ " (d)elete the current node\n" + if s:Path.CopyingSupported() + let prompt = prompt . " (c)opy the current node\n\n" + else + let prompt = prompt . " \n" + endif + + echo prompt + + let choice = nr2char(getchar()) + + if choice ==? "a" + call s:insertNewNode() + elseif choice ==? "m" + call s:renameCurrent() + elseif choice ==? "d" + call s:deleteNode() + elseif choice ==? "c" && s:Path.CopyingSupported() + call s:copyNode() + endif +endfunction + +" FUNCTION: s:toggleIgnoreFilter() {{{2 +" toggles the use of the NERDTreeIgnore option +function! s:toggleIgnoreFilter() + let t:NERDTreeIgnoreEnabled = !t:NERDTreeIgnoreEnabled + call s:renderViewSavingPosition() + call s:centerView() +endfunction + +" FUNCTION: s:toggleShowBookmarks() {{{2 +" toggles the display of bookmarks +function! s:toggleShowBookmarks() + let t:NERDTreeShowBookmarks = !t:NERDTreeShowBookmarks + if t:NERDTreeShowBookmarks + call s:renderView() + call s:putCursorOnBookmarkTable() + else + call s:renderViewSavingPosition() + endif + call s:centerView() +endfunction +" FUNCTION: s:toggleShowFiles() {{{2 +" toggles the display of hidden files +function! s:toggleShowFiles() + let t:NERDTreeShowFiles = !t:NERDTreeShowFiles + call s:renderViewSavingPosition() + call s:centerView() +endfunction + +" FUNCTION: s:toggleShowHidden() {{{2 +" toggles the display of hidden files +function! s:toggleShowHidden() + let t:NERDTreeShowHidden = !t:NERDTreeShowHidden + call s:renderViewSavingPosition() + call s:centerView() +endfunction + +"FUNCTION: s:upDir(keepState) {{{2 +"moves the tree up a level +" +"Args: +"keepState: 1 if the current root should be left open when the tree is +"re-rendered +function! s:upDir(keepState) + let cwd = t:NERDTreeRoot.path.str(0) + if cwd == "/" || cwd =~ '^[^/]..$' + call s:echo("already at top dir") + else + if !a:keepState + call t:NERDTreeRoot.close() + endif + + let oldRoot = t:NERDTreeRoot + + if empty(t:NERDTreeRoot.parent) + let path = t:NERDTreeRoot.path.getPathTrunk() + let newRoot = s:TreeDirNode.New(path) + call newRoot.open() + call newRoot.transplantChild(t:NERDTreeRoot) + let t:NERDTreeRoot = newRoot + else + let t:NERDTreeRoot = t:NERDTreeRoot.parent + + endif + + call s:renderView() + call s:putCursorOnNode(oldRoot, 0, 0) + endif +endfunction + + +"reset &cpo back to users setting +let &cpo = s:old_cpo + +" vim: set sw=4 sts=4 et fdm=marker: diff --git a/files/.vim/plugin/SyntaxFolds.vim b/files/.vim/plugin/SyntaxFolds.vim new file mode 100755 index 0000000..27c622c --- /dev/null +++ b/files/.vim/plugin/SyntaxFolds.vim @@ -0,0 +1,323 @@ +" ============================================================================== +" File: syntaxFolds.vim +" Author: Srinath Avadhanula +" ( srinath@fastmail.fm ) +" Last Change: Sun Oct 27 01:00 AM 2002 PST +" Description: Emulation of the syntax folding capability of vim using manual +" folding +" +" This script provides an emulation of the syntax folding of vim using manual +" folding. Just as in syntax folding, the folds are defined by regions. Each +" region is specified by a call to FoldRegions() which accepts 4 parameters: +" +" call FoldRegions(startpat, endpat, startoff, endoff) +" +" startpat: a line matching this pattern defines the beginning of a fold. +" endpat : a line matching this pattern defines the end of a fold. +" startoff: this is the offset from the starting line at which folding will +" actually start +" endoff : like startoff, but gives the offset of the actual fold end from +" the line satisfying endpat. +" startoff and endoff are necessary when the folding region does +" not have a specific end pattern corresponding to a start +" pattern. for example in latex, +" \begin{section} +" defines the beginning of a section, but its not necessary to +" have a corresponding +" \end{section} +" the section is assumed to end 1 line _before_ another section +" starts. +" startskip: a pattern which defines the beginning of a "skipped" region. +" +" For example, suppose we define a \itemize fold as follows: +" startpat = '^\s*\\item', +" endpat = '^\s*\\item\|^\s*\\end{\(enumerate\|itemize\|description\)}', +" startoff = 0, +" endoff = -1 +" +" This defines a fold which starts with a line beginning with an +" \item and ending one line before a line beginning with an +" \item or \end{enumerate} etc. +" +" Then, as long as \item's are not nested things are fine. +" However, once items begin to nest, the fold started by one +" \item can end because of an \item in an \itemize +" environment within this \item. i.e, the following can happen: +" +" \begin{itemize} +" \item Some text <------- fold will start here +" This item will contain a nested item +" \begin{itemize} <----- fold will end here because next line contains \item... +" \item Hello +" \end{itemize} <----- ... instead of here. +" \item Next item of the parent itemize +" \end{itemize} +" +" Therefore, in order to completely define a folding item which +" allows nesting, we need to also define a "skip" pattern. +" startskip and end skip do that. +" Leave '' when there is no nesting. +" endskip: the pattern which defines the end of the "skip" pattern for +" nested folds. +" +" Example: +" 1. A syntax fold region for a latex section is +" startpat = "\\section{" +" endpat = "\\section{" +" startoff = 0 +" endoff = -1 +" startskip = '' +" endskip = '' +" Note that the start and end patterns are thus the same and endoff has a +" negative value to capture the effect of a section ending one line before +" the next starts. +" 2. A syntax fold region for the \itemize environment is: +" startpat = '^\s*\\item', +" endpat = '^\s*\\item\|^\s*\\end{\(enumerate\|itemize\|description\)}', +" startoff = 0, +" endoff = -1, +" startskip = '^\s*\\begin{\(enumerate\|itemize\|description\)}', +" endskip = '^\s*\\end{\(enumerate\|itemize\|description\)}' +" Note the use of startskip and endskip to allow nesting. +" +" +" Each time a call is made to FoldRegions(), all the regions (which might be +" disjoint, but not nested) are folded up. +" Nested folds can be created by successive calls to FoldRegions(). The first +" call defines the region which is deepest in the folding. See MakeTexFolds() +" for an idea of how this works for latex files. + +" Function: AddSyntaxFoldItem (start, end, startoff, endoff [, skipStart, skipEnd]) {{{ +function! AddSyntaxFoldItem(start, end, startoff, endoff, ...) + if a:0 > 0 + let skipStart = a:1 + let skipEnd = a:2 + else + let skipStart = '' + let skipEnd = '' + end + if !exists('b:numFoldItems') + let b:numFoldItems = 0 + end + let b:numFoldItems = b:numFoldItems + 1 + + exe 'let b:startPat_'.b:numFoldItems.' = a:start' + exe 'let b:endPat_'.b:numFoldItems.' = a:end' + exe 'let b:startOff_'.b:numFoldItems.' = a:startoff' + exe 'let b:endOff_'.b:numFoldItems.' = a:endoff' + exe 'let b:skipStartPat_'.b:numFoldItems.' = skipStart' + exe 'let b:skipEndPat_'.b:numFoldItems.' = skipEnd' +endfunction + + +" }}} +" Function: MakeSyntaxFolds (force) {{{ +" Description: This function calls FoldRegions() several times with the +" parameters specifying various regions resulting in a nested fold +" structure for the file. +function! MakeSyntaxFolds(force, ...) + if exists('b:doneFolding') && a:force == 0 + return + end + + let skipEndPattern = '' + if a:0 > 0 + let line1 = a:1 + let skipEndPattern = '\|'.a:2 + else + let line1 = 1 + let r = line('.') + let c = virtcol('.') + + setlocal fdm=manual + normal! zE + end + if !exists('b:numFoldItems') + b:numFoldItems = 1000000 + end + + let i = 1 + + let maxline = line('.') + + while exists('b:startPat_'.i) && i <= b:numFoldItems + exe 'let startPat = b:startPat_'.i + exe 'let endPat = b:endPat_'.i + exe 'let startOff = b:startOff_'.i + exe 'let endOff = b:endOff_'.i + + let skipStart = '' + let skipEnd = '' + if exists('b:skipStartPat_'.i) + exe 'let skipStart = b:skipStartPat_'.i + exe 'let skipEnd = b:skipEndPat_'.i + end + exe line1 + let lastLoc = line1 + + if skipStart != '' + call InitStack('BeginSkipArray') + call FoldRegionsWithSkip(startPat, endPat, startOff, endOff, skipStart, skipEnd, 1, line('$')) + " call PrintError('done folding ['.startPat.']') + else + call FoldRegionsWithNoSkip(startPat, endPat, startOff, endOff, 1, line('$'), '') + end + + let i = i + 1 + endwhile + + exe maxline + + if a:0 == 0 + exe r + exe "normal! ".c."|" + if foldlevel(r) > 1 + exe "normal! ".(foldlevel(r) - 1)."zo" + end + let b:doneFolding = 0 + end +endfunction + + +" }}} +" FoldRegionsWithSkip: folding things such as \item's which can be nested. {{{ +function! FoldRegionsWithSkip(startpat, endpat, startoff, endoff, startskip, endskip, line1, line2) + exe a:line1 + " count the regions which have been skipped as we go along. do not want to + " create a fold which with a beginning or end line in one of the skipped + " regions. + let skippedRegions = '' + + " start searching for either the starting pattern or the end pattern. + while search(a:startskip.'\|'.a:endskip, 'W') + + if getline('.') =~ a:endskip + + let lastBegin = Pop('BeginSkipArray') + " call PrintError('popping '.lastBegin.' from stack and folding till '.line('.')) + call FoldRegionsWithNoSkip(a:startpat, a:endpat, a:startoff, a:endoff, lastBegin, line('.'), skippedRegions) + let skippedRegions = skippedRegions.lastBegin.','.line('.').'|' + + + " if this is the beginning of a skip region, then, push this line as + " the beginning of a skipped region. + elseif getline('.') =~ a:startskip + + " call PrintError('pushing '.line('.').' ['.getline('.').'] into stack') + call Push('BeginSkipArray', line('.')) + + end + endwhile + + " call PrintError('with skip starting at '.a:line1.' returning at line# '.line('.')) +endfunction + +" }}} +" FoldRegionsWithNoSkip: folding things such as \sections which do not nest. {{{ +function! FoldRegionsWithNoSkip(startpat, endpat, startoff, endoff, line1, line2, skippedRegions) + exe a:line1 + + " call PrintError('line1 = '.a:line1.', searching from '.line('.').'... for ['.a:startpat.'') + let lineBegin = s:MySearch(a:startpat, 'in') + " call PrintError('... and finding it at '.lineBegin) + + while lineBegin <= a:line2 + if IsInSkippedRegion(lineBegin, a:skippedRegions) + let lineBegin = s:MySearch(a:startpat, 'out') + " call PrintError(lineBegin.' is being skipped') + continue + end + let lineEnd = s:MySearch(a:endpat, 'out') + while IsInSkippedRegion(lineEnd, a:skippedRegions) && lineEnd <= a:line2 + let lineEnd = s:MySearch(a:endpat, 'out') + endwhile + if lineEnd > a:line2 + exe (lineBegin + a:startoff).','.a:line2.' fold' + break + else + " call PrintError ('for ['.a:startpat.'] '.(lineBegin + a:startoff).','.(lineEnd + a:endoff).' fold') + exe (lineBegin + a:startoff).','.(lineEnd + a:endoff).' fold' + end + + " call PrintError('line1 = '.a:line1.', searching from '.line('.').'... for ['.a:startpat.'') + let lineBegin = s:MySearch(a:startpat, 'in') + " call PrintError('... and finding it at '.lineBegin) + endwhile + + exe a:line2 + return +endfunction + +" }}} +" InitStack: initialize a stack {{{ +function! InitStack(name) + exe 'let s:'.a:name.'_numElems = 0' +endfunction +" }}} +" Push: push element into stack {{{ +function! Push(name, elem) + exe 'let numElems = s:'.a:name.'_numElems' + let numElems = numElems + 1 + exe 'let s:'.a:name.'_Element_'.numElems.' = a:elem' + exe 'let s:'.a:name.'_numElems = numElems' +endfunction +" }}} +" Pop: pops element off stack {{{ +function! Pop(name) + exe 'let numElems = s:'.a:name.'_numElems' + if numElems == 0 + return '' + else + exe 'let ret = s:'.a:name.'_Element_'.numElems + let numElems = numElems - 1 + exe 'let s:'.a:name.'_numElems = numElems' + return ret + end +endfunction +" }}} +" MySearch: just like search(), but returns large number on failure {{{ +function! MySearch(pat, opt) + if a:opt == 'in' + if getline('.') =~ a:pat + let ret = line('.') + else + let ret = search(a:pat, 'W') + end + else + normal! $ + let ret = search(a:pat, 'W') + end + + if ret == 0 + let ret = line('$') + 1 + end + return ret +endfunction +" }}} +" Function: IsInSkippedRegion (lnum, regions) {{{ +" Description: finds whether a given line number is within one of the regions +" skipped. +function! IsInSkippedRegion(lnum, regions) + let i = 1 + let subset = s:Strntok(a:regions, '|', i) + while subset != '' + let n1 = s:Strntok(subset, ',', 1) + let n2 = s:Strntok(subset, ',', 2) + if a:lnum >= n1 && a:lnum <= n2 + return 1 + end + + let subset = s:Strntok(a:regions, '|', i) + let i = i + 1 + endwhile + + return 0 +endfunction " }}} +" Function: Strntok (string, tok, n) {{{ +" extract the n^th token from s seperated by tok. +" example: Strntok('1,23,3', ',', 2) = 23 +fun! Strntok(s, tok, n) + return matchstr( a:s.a:tok[0], '\v(\zs([^'.a:tok.']*)\ze['.a:tok.']){'.a:n.'}') +endfun " }}} + +" vim600:fdm=marker diff --git a/files/.vim/plugin/filebrowser.vim b/files/.vim/plugin/filebrowser.vim new file mode 100755 index 0000000..e9de049 --- /dev/null +++ b/files/.vim/plugin/filebrowser.vim @@ -0,0 +1,251 @@ +" filebrowser.vim: utility file for vim 6.2+ +" +" Copyright: Srinath Avadhanula +" Parts of this file are taken from explorer.vim which is a plugin file +" distributed with vim under the Vim charityware license. +" License: distributed under the Vim charityware license. +" +" Settings: +" FB_CallBackFunction: the function name which gets called when the user +" presses on a file-name in the file browser. +" FB_AllowRegexp: A filename has to match this regexp to be displayed. +" FB_RejectRegexp: If a filename matches this regexp, then its not displayed. +" (Both these regexps are '' by default which means no filtering is +" done). + +" line continuation used here. +let s:save_cpo = &cpo +set cpo&vim + +"====================================================================== +" Globally visible functions (API) +"====================================================================== +" FB_OpenFileBrowser: opens a new buffer and displays the file list {{{ +" Description: +function! FB_OpenFileBrowser(dir) + if !isdirectory(a:dir) + return + endif + if exists('s:FB_BufferNumber') + if bufwinnr(s:FB_BufferNumber) != -1 + execute bufwinnr(s:FB_BufferNumber).' wincmd w' + return + endif + execute 'aboveleft split #'.s:FB_BufferNumber + else + aboveleft split __Choose_File__ + let s:FB_BufferNumber = bufnr('%') + endif + call FB_DisplayFiles(a:dir) +endfunction " }}} +" FB_DisplayFiles: displays the files in a given directory {{{ +" Description: +" Call this function only when the cursor is in a temporary buffer +function! FB_DisplayFiles(dir) + if !isdirectory(a:dir) + return + endif + call s:FB_SetSilentSettings() + " make this a "scratch" buffer + call s:FB_SetScratchSettings() + + let allowRegexp = s:FB_GetVar('FB_AllowRegexp', '') + let rejectRegexp = s:FB_GetVar('FB_RejectRegexp', '') + + " change to the directory to make processing simpler. + execute "lcd ".a:dir + " delete everything in the buffer. + " IMPORTANT: we need to be in a scratch buffer + 0,$ d_ + + let allFilenames = glob('*') + let dispFiles = "" + let subDirs = "../\n" + + let i = 1 + while 1 + let filename = s:FB_Strntok(allFilenames, "\n", i) + if filename == '' + break + endif + if isdirectory(filename) + let subDirs = subDirs.filename."/\n" + else + if allowRegexp != '' && filename !~ allowRegexp + elseif rejectRegexp != '' && filename =~ rejectRegexp + else + let dispFiles = dispFiles.filename."\n" + endif + endif + let i = i + 1 + endwhile + 0put!=dispFiles + 0put!=subDirs + " delte the last empty line resulting from the put + $ d_ + + call s:FB_SetHighlighting() + call s:FB_DisplayHelp() + call s:FB_SetMaps() + + " goto the first file/directory + 0 + call search('^"=', 'w') + normal! j: + + set nomodified nomodifiable + + call s:FB_ResetSilentSettings() +endfunction " }}} +" FB_SetVar: sets script local variables from outside this script {{{ +" Description: +function! FB_SetVar(varname, value) + let s:{a:varname} = a:value +endfunction " }}} + +" FB_SetHighlighting: sets syntax highlighting for the buffer {{{ +" Description: +" Origin: from explorer.vim in vim +function! FB_SetHighlighting() + " Set up syntax highlighting + " Something wrong with the evaluation of the conditional though... + if has("syntax") && exists("g:syntax_on") && !has("syntax_items") + syn match browseSynopsis "^\"[ -].*" + syn match browseDirectory "[^\"].*/ " + syn match browseDirectory "[^\"].*/$" + syn match browseCurDir "^\"= .*$" + syn match browseSortBy "^\" Sorted by .*$" contains=browseSuffixInfo + syn match browseSuffixInfo "(.*)$" contained + syn match browseFilter "^\" Not Showing:.*$" + syn match browseFiletime "«\d\+$" + + "hi def link browseSynopsis PreProc + hi def link browseSynopsis Special + hi def link browseDirectory Directory + hi def link browseCurDir Statement + hi def link browseSortBy String + hi def link browseSuffixInfo Type + hi def link browseFilter String + hi def link browseFiletime Ignore + hi def link browseSuffixes Type + endif +endfunction " }}} +" FB_SetMaps: sets buffer local maps {{{ +" Description: +function! FB_SetMaps() + nnoremap q :bdelete + nnoremap C :call FB_DisplayFiles(getcwd()) + nnoremap :bdelete + nnoremap :call FB_EditEntry() + nnoremap ? :call FB_ToggleHelp() + + " lock the user in this window + nnoremap +endfunction " }}} +" FB_SetSilentSettings: some settings which make things silent {{{ +" Description: +" Origin: from explorer.vim distributed with vim. +function! FB_SetSilentSettings() + let s:save_report=&report + let s:save_showcmd = &sc + set report=10000 noshowcmd +endfunction +" FB_ResetSilentSettings: reset settings set by FB_SetSilentSettings +" Description: +function! FB_ResetSilentSettings() + let &report=s:save_report + let &showcmd = s:save_showcmd +endfunction " }}} +" FB_SetScratchSettings: makes the present buffer a scratch buffer {{{ +" Description: +function! FB_SetScratchSettings() + " Turn off the swapfile, set the buffer type so that it won't get + " written, and so that it will get deleted when it gets hidden. + setlocal noreadonly modifiable + setlocal noswapfile + setlocal buftype=nowrite + setlocal bufhidden=delete + " Don't wrap around long lines + setlocal nowrap +endfunction + +" }}} +" FB_ToggleHelp: toggles verbosity of help {{{ +" Description: +function! FB_ToggleHelp() + let s:FB_VerboseHelp = 1 - s:FB_GetVar('FB_VerboseHelp', 0) + + call FB_DisplayFiles('.') +endfunction " }}} +" FB_DisplayHelp: displays a helpful header {{{ +" Description: +function! FB_DisplayHelp() + let verboseHelp = s:FB_GetVar('FB_VerboseHelp', 0) + if verboseHelp + let txt = + \ "\" : on file, choose the file and quit\n" + \ ."\" on dir, enter directory\n" + \ ."\" q/: quit without choosing\n" + \ ."\" C: change directory to getcwd()\n" + \ ."\" ?: toggle help verbosity\n" + \ ."\"= ".getcwd() + else + let txt = "\" ?: toggle help verbosity\n" + \ ."\"= ".getcwd() + endif + 0put!=txt +endfunction " }}} + +" Handles various actions in the file-browser +" FB_EditEntry: handles the user pressing on a line {{{ +" Description: +function! FB_EditEntry() + let line = getline('.') + + if isdirectory(line) + call FB_DisplayFiles(line) + endif + + " If the user has a call back function defined on choosing a file, handle + " it. + let cbf = s:FB_GetVar('FB_CallBackFunction', '') + if cbf != '' && line !~ '^" ' && filereadable(line) + let fname = fnamemodify(line, ':p') + bdelete + + let arguments = s:FB_GetVar('FB_CallBackFunctionArgs', '') + if arguments != '' + let arguments = ','.arguments + endif + call Tex_Debug('arguments = '.arguments, 'fb') + call Tex_Debug("call ".cbf."('".fname."'".arguments.')', 'fb') + exec "call ".cbf."('".fname."'".arguments.')' + endif +endfunction " }}} + +" FB_Strntok (string, tok, n) {{{ +" extract the n^th token from s seperated by tok. +" example: FB_Strntok('1,23,3', ',', 2) = 23 +fun! FB_Strntok(s, tok, n) + return matchstr( a:s.a:tok[0], '\v(\zs([^'.a:tok.']*)\ze['.a:tok.']){'.a:n.'}') +endfun " }}} +" FB_GetVar: gets the most local value of a variable {{{ +function! FB_GetVar(name, default) + if exists('s:'.a:name) + return s:{a:name} + elseif exists('w:'.a:name) + return w:{a:name} + elseif exists('b:'.a:name) + return b:{a:name} + elseif exists('g:'.a:name) + return g:{a:name} + else + return a:default + endif +endfunction + +" }}} + +let &cpo = s:save_cpo + +" vim:fdm=marker:ff=unix:noet:ts=4:sw=4:nowrap diff --git a/files/.vim/plugin/imaps.vim b/files/.vim/plugin/imaps.vim new file mode 100755 index 0000000..d871aa1 --- /dev/null +++ b/files/.vim/plugin/imaps.vim @@ -0,0 +1,831 @@ +" File: imaps.vim +" Authors: Srinath Avadhanula +" Benji Fisher +" +" WWW: http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/vim-latex/vimfiles/plugin/imaps.vim?only_with_tag=MAIN +" +" Description: insert mode template expander with cursor placement +" while preserving filetype indentation. +" +" $Id: imaps.vim 997 2006-03-20 09:45:45Z srinathava $ +" +" Documentation: {{{ +" +" Motivation: +" this script provides a way to generate insert mode mappings which do not +" suffer from some of the problem of mappings and abbreviations while allowing +" cursor placement after the expansion. It can alternatively be thought of as +" a template expander. +" +" Consider an example. If you do +" +" imap lhs something +" +" then a mapping is set up. However, there will be the following problems: +" 1. the 'ttimeout' option will generally limit how easily you can type the +" lhs. if you type the left hand side too slowly, then the mapping will not +" be activated. +" 2. if you mistype one of the letters of the lhs, then the mapping is +" deactivated as soon as you backspace to correct the mistake. +" +" If, in order to take care of the above problems, you do instead +" +" iab lhs something +" +" then the timeout problem is solved and so is the problem of mistyping. +" however, abbreviations are only expanded after typing a non-word character. +" which causes problems of cursor placement after the expansion and invariably +" spurious spaces are inserted. +" +" Usage Example: +" this script attempts to solve all these problems by providing an emulation +" of imaps wchich does not suffer from its attendant problems. Because maps +" are activated without having to press additional characters, therefore +" cursor placement is possible. furthermore, file-type specific indentation is +" preserved, because the rhs is expanded as if the rhs is typed in literally +" by the user. +" +" The script already provides some default mappings. each "mapping" is of the +" form: +" +" call IMAP (lhs, rhs, ft) +" +" Some characters in the RHS have special meaning which help in cursor +" placement. +" +" Example One: +" +" call IMAP ("bit`", "\\begin{itemize}\\\item <++>\\\end{itemize}<++>", "tex") +" +" This effectively sets up the map for "bit`" whenever you edit a latex file. +" When you type in this sequence of letters, the following text is inserted: +" +" \begin{itemize} +" \item * +" \end{itemize}<++> +" +" where * shows the cursor position. The cursor position after inserting the +" text is decided by the position of the first "place-holder". Place holders +" are special characters which decide cursor placement and movement. In the +" example above, the place holder characters are <+ and +>. After you have typed +" in the item, press and you will be taken to the next set of <++>'s. +" Therefore by placing the <++> characters appropriately, you can minimize the +" use of movement keys. +" +" NOTE: Set g:Imap_UsePlaceHolders to 0 to disable placeholders altogether. +" Set +" g:Imap_PlaceHolderStart and g:Imap_PlaceHolderEnd +" to something else if you want different place holder characters. +" Also, b:Imap_PlaceHolderStart and b:Imap_PlaceHolderEnd override the values +" of g:Imap_PlaceHolderStart and g:Imap_PlaceHolderEnd respectively. This is +" useful for setting buffer specific place hoders. +" +" Example Two: +" You can use the command to insert dynamic elements such as dates. +" call IMAP ('date`', "\=strftime('%b %d %Y')\", '') +" +" sets up the map for date` to insert the current date. +" +"--------------------------------------%<-------------------------------------- +" Bonus: This script also provides a command Snip which puts tearoff strings, +" '----%<----' above and below the visually selected range of lines. The +" length of the string is chosen to be equal to the longest line in the range. +" Recommended Usage: +" '<,'>Snip +"--------------------------------------%<-------------------------------------- +" }}} + +" line continuation used here. +let s:save_cpo = &cpo +set cpo&vim + +" ============================================================================== +" Script Options / Variables +" ============================================================================== +" Options {{{ +if !exists('g:Imap_StickyPlaceHolders') + let g:Imap_StickyPlaceHolders = 1 +endif +if !exists('g:Imap_DeleteEmptyPlaceHolders') + let g:Imap_DeleteEmptyPlaceHolders = 1 +endif +" }}} +" Variables {{{ +" s:LHS_{ft}_{char} will be generated automatically. It will look like +" s:LHS_tex_o = 'fo\|foo\|boo' and contain all mapped sequences ending in "o". +" s:Map_{ft}_{lhs} will be generated automatically. It will look like +" s:Map_c_foo = 'for(<++>; <++>; <++>)', the mapping for "foo". +" +" }}} + +" ============================================================================== +" functions for easy insert mode mappings. +" ============================================================================== +" IMAP: Adds a "fake" insert mode mapping. {{{ +" For example, doing +" IMAP('abc', 'def' ft) +" will mean that if the letters abc are pressed in insert mode, then +" they will be replaced by def. If ft != '', then the "mapping" will be +" specific to the files of type ft. +" +" Using IMAP has a few advantages over simply doing: +" imap abc def +" 1. with imap, if you begin typing abc, the cursor will not advance and +" long as there is a possible completion, the letters a, b, c will be +" displayed on on top of the other. using this function avoids that. +" 2. with imap, if a backspace or arrow key is pressed before completing +" the word, then the mapping is lost. this function allows movement. +" (this ofcourse means that this function is only limited to +" left-hand-sides which do not have movement keys or unprintable +" characters) +" It works by only mapping the last character of the left-hand side. +" when this character is typed in, then a reverse lookup is done and if +" the previous characters consititute the left hand side of the mapping, +" the previously typed characters and erased and the right hand side is +" inserted + +" IMAP: set up a filetype specific mapping. +" Description: +" "maps" the lhs to rhs in files of type 'ft'. If supplied with 2 +" additional arguments, then those are assumed to be the placeholder +" characters in rhs. If unspecified, then the placeholder characters +" are assumed to be '<+' and '+>' These placeholder characters in +" a:rhs are replaced with the users setting of +" [bg]:Imap_PlaceHolderStart and [bg]:Imap_PlaceHolderEnd settings. +" +function! IMAP(lhs, rhs, ft, ...) + + " Find the place holders to save for IMAP_PutTextWithMovement() . + if a:0 < 2 + let phs = '<+' + let phe = '+>' + else + let phs = a:1 + let phe = a:2 + endif + + let hash = s:Hash(a:lhs) + let s:Map_{a:ft}_{hash} = a:rhs + let s:phs_{a:ft}_{hash} = phs + let s:phe_{a:ft}_{hash} = phe + + " Add a:lhs to the list of left-hand sides that end with lastLHSChar: + let lastLHSChar = a:lhs[strlen(a:lhs)-1] + let hash = s:Hash(lastLHSChar) + if !exists("s:LHS_" . a:ft . "_" . hash) + let s:LHS_{a:ft}_{hash} = escape(a:lhs, '\') + else + let s:LHS_{a:ft}_{hash} = escape(a:lhs, '\') .'\|'. s:LHS_{a:ft}_{hash} + endif + + " map only the last character of the left-hand side. + if lastLHSChar == ' ' + let lastLHSChar = '' + end + exe 'inoremap ' + \ escape(lastLHSChar, '|') + \ '=LookupCharacter("' . + \ escape(lastLHSChar, '\|"') . + \ '")' +endfunction + +" }}} +" IMAP_list: list the rhs and place holders corresponding to a:lhs {{{ +" +" Added mainly for debugging purposes, but maybe worth keeping. +function! IMAP_list(lhs) + let char = a:lhs[strlen(a:lhs)-1] + let charHash = s:Hash(char) + if exists("s:LHS_" . &ft ."_". charHash) && a:lhs =~ s:LHS_{&ft}_{charHash} + let ft = &ft + elseif exists("s:LHS__" . charHash) && a:lhs =~ s:LHS__{charHash} + let ft = "" + else + return "" + endif + let hash = s:Hash(a:lhs) + return "rhs = " . s:Map_{ft}_{hash} . " place holders = " . + \ s:phs_{ft}_{hash} . " and " . s:phe_{ft}_{hash} +endfunction +" }}} +" LookupCharacter: inserts mapping corresponding to this character {{{ +" +" This function extracts from s:LHS_{&ft}_{a:char} or s:LHS__{a:char} +" the longest lhs matching the current text. Then it replaces lhs with the +" corresponding rhs saved in s:Map_{ft}_{lhs} . +" The place-holder variables are passed to IMAP_PutTextWithMovement() . +function! s:LookupCharacter(char) + if IMAP_GetVal('Imap_FreezeImap', 0) == 1 + return a:char + endif + let charHash = s:Hash(a:char) + + " The line so far, including the character that triggered this function: + let text = strpart(getline("."), 0, col(".")-1) . a:char + " Prefer a local map to a global one, even if the local map is shorter. + " Is this what we want? Do we care? + " Use '\V' (very no-magic) so that only '\' is special, and it was already + " escaped when building up s:LHS_{&ft}_{charHash} . + if exists("s:LHS_" . &ft . "_" . charHash) + \ && text =~ "\\C\\V\\(" . s:LHS_{&ft}_{charHash} . "\\)\\$" + let ft = &ft + elseif exists("s:LHS__" . charHash) + \ && text =~ "\\C\\V\\(" . s:LHS__{charHash} . "\\)\\$" + let ft = "" + else + " If this is a character which could have been used to trigger an + " abbreviation, check if an abbreviation exists. + if a:char !~ '\k' + let lastword = matchstr(getline('.'), '\k\+$', '') + call IMAP_Debug('getting lastword = ['.lastword.']', 'imap') + if lastword != '' + " An extremeley wierd way to get around the fact that vim + " doesn't have the equivalent of the :mapcheck() function for + " abbreviations. + let _a = @a + exec "redir @a | silent! iab ".lastword." | redir END" + let abbreviationRHS = matchstr(@a."\n", "\n".'i\s\+'.lastword.'\s\+@\?\zs.*\ze'."\n") + + call IMAP_Debug('getting abbreviationRHS = ['.abbreviationRHS.']', 'imap') + + if @a =~ "No abbreviation found" || abbreviationRHS == "" + let @a = _a + return a:char + endif + + let @a = _a + let abbreviationRHS = escape(abbreviationRHS, '\<"') + exec 'let abbreviationRHS = "'.abbreviationRHS.'"' + + let lhs = lastword.a:char + let rhs = abbreviationRHS.a:char + let phs = IMAP_GetPlaceHolderStart() + let phe = IMAP_GetPlaceHolderEnd() + else + return a:char + endif + else + return a:char + endif + endif + " Find the longest left-hand side that matches the line so far. + " matchstr() returns the match that starts first. This automatically + " ensures that the longest LHS is used for the mapping. + if !exists('lhs') || !exists('rhs') + let lhs = matchstr(text, "\\C\\V\\(" . s:LHS_{ft}_{charHash} . "\\)\\$") + let hash = s:Hash(lhs) + let rhs = s:Map_{ft}_{hash} + let phs = s:phs_{ft}_{hash} + let phe = s:phe_{ft}_{hash} + endif + + if strlen(lhs) == 0 + return a:char + endif + " enough back-spaces to erase the left-hand side; -1 for the last + " character typed: + let bs = substitute(strpart(lhs, 1), ".", "\", "g") + return bs . IMAP_PutTextWithMovement(rhs, phs, phe) +endfunction + +" }}} +" IMAP_PutTextWithMovement: returns the string with movement appended {{{ +" Description: +" If a:str contains "placeholders", then appends movement commands to +" str in a way that the user moves to the first placeholder and enters +" insert or select mode. If supplied with 2 additional arguments, then +" they are assumed to be the placeholder specs. Otherwise, they are +" assumed to be '<+' and '+>'. These placeholder chars are replaced +" with the users settings of [bg]:Imap_PlaceHolderStart and +" [bg]:Imap_PlaceHolderEnd. +function! IMAP_PutTextWithMovement(str, ...) + + " The placeholders used in the particular input string. These can be + " different from what the user wants to use. + if a:0 < 2 + let phs = '<+' + let phe = '+>' + else + let phs = escape(a:1, '\') + let phe = escape(a:2, '\') + endif + + let text = a:str + + " The user's placeholder settings. + let phsUser = IMAP_GetPlaceHolderStart() + let pheUser = IMAP_GetPlaceHolderEnd() + + " Problem: depending on the setting of the 'encoding' option, a character + " such as "\xab" may not match itself. We try to get around this by + " changing the encoding of all our strings. At the end, we have to + " convert text back. + let phsEnc = s:Iconv(phs, "encode") + let pheEnc = s:Iconv(phe, "encode") + let phsUserEnc = s:Iconv(phsUser, "encode") + let pheUserEnc = s:Iconv(pheUser, "encode") + let textEnc = s:Iconv(text, "encode") + if textEnc != text + let textEncoded = 1 + else + let textEncoded = 0 + endif + + let pattern = '\V\(\.\{-}\)' .phs. '\(\.\{-}\)' .phe. '\(\.\*\)' + " If there are no placeholders, just return the text. + if textEnc !~ pattern + call IMAP_Debug('Not getting '.phs.' and '.phe.' in '.textEnc, 'imap') + return text + endif + " Break text up into "initial <+template+> final"; any piece may be empty. + let initialEnc = substitute(textEnc, pattern, '\1', '') + let templateEnc = substitute(textEnc, pattern, '\2', '') + let finalEnc = substitute(textEnc, pattern, '\3', '') + + " If the user does not want to use placeholders, then remove all but the + " first placeholder. + " Otherwise, replace all occurences of the placeholders here with the + " user's choice of placeholder settings. + if exists('g:Imap_UsePlaceHolders') && !g:Imap_UsePlaceHolders + let finalEnc = substitute(finalEnc, '\V'.phs.'\.\{-}'.phe, '', 'g') + else + let finalEnc = substitute(finalEnc, '\V'.phs.'\(\.\{-}\)'.phe, + \ phsUserEnc.'\1'.pheUserEnc, 'g') + endif + + " The substitutions are done, so convert back, if necessary. + if textEncoded + let initial = s:Iconv(initialEnc, "decode") + let template = s:Iconv(templateEnc, "decode") + let final = s:Iconv(finalEnc, "decode") + else + let initial = initialEnc + let template = templateEnc + let final = finalEnc + endif + + " Build up the text to insert: + " 1. the initial text plus an extra character; + " 2. go to Normal mode with , so it works even if 'insertmode' + " is set, and mark the position; + " 3. replace the extra character with tamplate and final; + " 4. back to Normal mode and restore the cursor position; + " 5. call IMAP_Jumpfunc(). + let template = phsUser . template . pheUser + " Old trick: insert and delete a character to get the same behavior at + " start, middle, or end of line and on empty lines. + let text = initial . "X\\:call IMAP_Mark('set')\\"_s" + let text = text . template . final + let text = text . "\\:call IMAP_Mark('go')\" + let text = text . "i\=IMAP_Jumpfunc('', 1)\" + + call IMAP_Debug('IMAP_PutTextWithMovement: text = ['.text.']', 'imap') + return text +endfunction + +" }}} +" IMAP_Jumpfunc: takes user to next <+place-holder+> {{{ +" Author: Luc Hermitte +" Arguments: +" direction: flag for the search() function. If set to '', search forwards, +" if 'b', then search backwards. See the {flags} argument of the +" |search()| function for valid values. +" inclusive: In vim, the search() function is 'exclusive', i.e we always goto +" next cursor match even if there is a match starting from the +" current cursor position. Setting this argument to 1 makes +" IMAP_Jumpfunc() also respect a match at the current cursor +" position. 'inclusive'ness is necessary for IMAP() because a +" placeholder string can occur at the very beginning of a map which +" we want to select. +" We use a non-zero value only in special conditions. Most mappings +" should use a zero value. +function! IMAP_Jumpfunc(direction, inclusive) + + " The user's placeholder settings. + let phsUser = IMAP_GetPlaceHolderStart() + let pheUser = IMAP_GetPlaceHolderEnd() + + let searchString = '' + " If this is not an inclusive search or if it is inclusive, but the + " current cursor position does not contain a placeholder character, then + " search for the placeholder characters. + if !a:inclusive || strpart(getline('.'), col('.')-1) !~ '\V\^'.phsUser + let searchString = '\V'.phsUser.'\_.\{-}'.pheUser + endif + + " If we didn't find any placeholders return quietly. + if searchString != '' && !search(searchString, a:direction) + return '' + endif + + " Open any closed folds and make this part of the text visible. + silent! foldopen! + + " Calculate if we have an empty placeholder or if it contains some + " description. + let template = + \ matchstr(strpart(getline('.'), col('.')-1), + \ '\V\^'.phsUser.'\zs\.\{-}\ze\('.pheUser.'\|\$\)') + let placeHolderEmpty = !strlen(template) + + " If we are selecting in exclusive mode, then we need to move one step to + " the right + let extramove = '' + if &selection == 'exclusive' + let extramove = 'l' + endif + + " Select till the end placeholder character. + let movement = "\v/\\V".pheUser."/e\".extramove + + " First remember what the search pattern was. s:RemoveLastHistoryItem will + " reset @/ to this pattern so we do not create new highlighting. + let g:Tex_LastSearchPattern = @/ + + " Now either goto insert mode or select mode. + if placeHolderEmpty && g:Imap_DeleteEmptyPlaceHolders + " delete the empty placeholder into the blackhole. + return movement."\"_c\:".s:RemoveLastHistoryItem."\" + else + return movement."\\:".s:RemoveLastHistoryItem."\gv\" + endif + +endfunction + +" }}} +" Maps for IMAP_Jumpfunc {{{ +" +" These mappings use and thus provide for easy user customization. When +" the user wants to map some other key to jump forward, he can do for +" instance: +" nmap ,f IMAP_JumpForward +" etc. + +" jumping forward and back in insert mode. +imap IMAP_JumpForward =IMAP_Jumpfunc('', 0) +imap IMAP_JumpBack =IMAP_Jumpfunc('b', 0) + +" jumping in normal mode +nmap IMAP_JumpForward i=IMAP_Jumpfunc('', 0) +nmap IMAP_JumpBack i=IMAP_Jumpfunc('b', 0) + +" deleting the present selection and then jumping forward. +vmap IMAP_DeleteAndJumpForward "_i=IMAP_Jumpfunc('', 0) +vmap IMAP_DeleteAndJumpBack "_i=IMAP_Jumpfunc('b', 0) + +" jumping forward without deleting present selection. +vmap IMAP_JumpForward i=IMAP_Jumpfunc('', 0) +vmap IMAP_JumpBack `=IMAP_Jumpfunc('b', 0) + +" }}} +" Default maps for IMAP_Jumpfunc {{{ +" map only if there is no mapping already. allows for user customization. +" NOTE: Default mappings for jumping to the previous placeholder are not +" provided. It is assumed that if the user will create such mappings +" hself if e so desires. +if !hasmapto('IMAP_JumpForward', 'i') + imap IMAP_JumpForward +endif +if !hasmapto('IMAP_JumpForward', 'n') + nmap IMAP_JumpForward +endif +if exists('g:Imap_StickyPlaceHolders') && g:Imap_StickyPlaceHolders + if !hasmapto('IMAP_JumpForward', 'v') + vmap IMAP_JumpForward + endif +else + if !hasmapto('IMAP_DeleteAndJumpForward', 'v') + vmap IMAP_DeleteAndJumpForward + endif +endif +" }}} + +nmap