"============================================================================= " File: indexer.vim " Author: Dmitry Frank (dimon.frank@gmail.com) " Last Change: 26 Sep 2010 " Version: 1.8 "============================================================================= " See documentation in accompanying help file " You may use this code in whatever way you see fit. " s:ParsePath(sPath) " changing '\' to '/' or vice versa depending on OS (MS Windows or not) also calls simplify() function! s:ParsePath(sPath) if (has('win32') || has('win64')) let l:sPath = substitute(a:sPath, '/', '\', 'g') else let l:sPath = substitute(a:sPath, '\', '/', 'g') endif let l:sPath = simplify(l:sPath) " removing last "/" or "\" let l:sLastSymb = strpart(l:sPath, (strlen(l:sPath) - 1), 1) if (l:sLastSymb == '/' || l:sLastSymb == '\') let l:sPath = strpart(l:sPath, 0, (strlen(l:sPath) - 1)) endif return l:sPath endfunction " s:Trim(sString) " trims spaces from begin and end of string function! s:Trim(sString) return substitute(substitute(a:sString, '^\s\+', '', ''), '\s\+$', '', '') endfunction " s:IsAbsolutePath(path) <<< " this function from project.vim is written by Aric Blumer. " Returns true if filename has an absolute path. function! s:IsAbsolutePath(path) if a:path =~ '^ftp:' || a:path =~ '^rcp:' || a:path =~ '^scp:' || a:path =~ '^http:' return 2 endif let path=expand(a:path) " Expand any environment variables that might be in the path if path[0] == '/' || path[0] == '~' || path[0] == '\\' || path[1] == ':' return 1 endif return 0 endfunction " >>> function! s:GetDirsAndFilesFromIndexerList(aLines, projectName, dExistsResult) let l:aLines = a:aLines let l:dResult = a:dExistsResult let l:boolInNeededProject = (a:projectName == '' ? 1 : 0) let l:boolInProjectsParentSection = 0 let l:sProjectsParentFilter = '' let l:sCurProjName = '' for l:sLine in l:aLines " if line is not empty if l:sLine !~ '^\s*$' && l:sLine !~ '^\s*\#.*$' " look for project name [PrjName] let myMatch = matchlist(l:sLine, '^\s*\[\([^\]]\+\)\]') if (len(myMatch) > 0) " check for PROJECTS_PARENT section if (strpart(myMatch[1], 0, 15) == 'PROJECTS_PARENT') " this is projects parent section let l:sProjectsParentFilter = '' let filterMatch = matchlist(myMatch[1], 'filter="\([^"]\+\)"') if (len(filterMatch) > 0) let l:sProjectsParentFilter = filterMatch[1] endif let l:boolInProjectsParentSection = 1 else let l:boolInProjectsParentSection = 0 if (a:projectName != '') if (myMatch[1] == a:projectName) let l:boolInNeededProject = 1 else let l:boolInNeededProject = 0 endif endif if l:boolInNeededProject let l:sCurProjName = myMatch[1] let l:dResult[l:sCurProjName] = { 'files': [], 'paths': [], 'not_exist': [] } endif endif else if l:boolInProjectsParentSection " parsing one project parent let l:lFilter = split(l:sProjectsParentFilter, ' ') if (len(l:lFilter) == 0) let l:lFilter = ['*'] endif " removing \/* from end of path let l:projectsParent = substitute(Trim(l:sLine), '[\\/*]\+$', '', '') " creating list of projects let l:lProjects = split(expand(l:projectsParent.'/*'), '\n') let l:lIndexerFilesList = [] for l:sPrj in l:lProjects if (isdirectory(l:sPrj)) call add(l:lIndexerFilesList, '['.substitute(l:sPrj, '^.*[\\/]\([^\\/]\+\)$', '\1', '').']') for l:sCurFilter in l:lFilter call add(l:lIndexerFilesList, l:sPrj.'/**/'.l:sCurFilter) endfor call add(l:lIndexerFilesList, '') endif endfor " parsing this list let l:dResult = GetDirsAndFilesFromIndexerList(l:lIndexerFilesList, a:projectName, l:dResult) elseif l:boolInNeededProject " looks like there's path if l:sCurProjName == '' let l:sCurProjName = 'noname' let l:dResult[l:sCurProjName] = { 'files': [], 'paths': [], 'not_exist': [] } endif if (!g:indexer_ctagsDontSpecifyFilesIfPossible && s:indexer_projectName != '') " adding every file. let l:dResult[l:sCurProjName].files = ConcatLists(l:dResult[l:sCurProjName].files, split(expand(substitute(Trim(l:sLine), '\\\*\*', '**', 'g')), '\n')) else " adding just paths. (much more faster) let l:dResult[l:sCurProjName].paths = ConcatLists(l:dResult[l:sCurProjName].paths, [ParsePath(expand(substitute(substitute(substitute(l:sLine, '^\(.*\)[\\/][^\\/]\+$', '\1', 'g'), '^\([^*]\+\).*$', '\1', ''), '[\\/]$', '', '')))]) endif endif endif endif endfor " build paths if (!g:indexer_ctagsDontSpecifyFilesIfPossible && s:indexer_projectName != '') for l:sKey in keys(l:dResult) let l:lPaths = [] for l:sFile in l:dResult[l:sKey].files let l:sPath = substitute(l:sFile, '^\(.*\)[\\/][^\\/]\+$', '\1', 'g') call add(l:lPaths, l:sPath) endfor let l:dResult[l:sKey].paths = ConcatLists(l:dResult[l:sKey].paths, l:lPaths) endfor endif return l:dResult endfunction " getting dictionary with files, paths and non-existing files from indexer " project file function! s:GetDirsAndFilesFromIndexerFile(indexerFile, projectName) let l:aLines = readfile(a:indexerFile) let l:dResult = {} let l:dResult = GetDirsAndFilesFromIndexerList(l:aLines, a:projectName, l:dResult) return l:dResult endfunction " getting dictionary with files, paths and non-existing files from " project.vim's project file function! s:GetDirsAndFilesFromProjectFile(projectFile, projectName) let l:aLines = readfile(a:projectFile) " if projectName is empty, then we should add files from whole projectFile let l:boolInNeededProject = (a:projectName == '' ? 1 : 0) let l:iOpenedBraces = 0 " current count of opened { } let l:iOpenedBracesAtProjectStart = 0 let l:aPaths = [] " paths stack let l:sLastFoundPath = '' let l:dResult = {} let l:sCurProjName = '' for l:sLine in l:aLines " ignoring comments if l:sLine =~ '^#' | continue | endif let l:sLine = substitute(l:sLine, '#.\+$', '' ,'') " searching for closing brace { } let sTmpLine = l:sLine while (sTmpLine =~ '}') let l:iOpenedBraces = l:iOpenedBraces - 1 " if projectName is defined and there was last brace closed, then we " are finished parsing needed project if (l:iOpenedBraces <= l:iOpenedBracesAtProjectStart) && a:projectName != '' let l:boolInNeededProject = 0 " TODO: total break endif call remove(l:aPaths, len(l:aPaths) - 1) let sTmpLine = substitute(sTmpLine, '}', '', '') endwhile " searching for blabla=qweqwe let myMatch = matchlist(l:sLine, '\s*\(.\{-}\)=\(.\{-}\)\\\@ 0) " now we found start of project folder or subfolder " if !l:boolInNeededProject if (a:projectName != '' && myMatch[1] == a:projectName) let l:iOpenedBracesAtProjectStart = l:iOpenedBraces let l:boolInNeededProject = 1 endif endif if l:boolInNeededProject && (l:iOpenedBraces == l:iOpenedBracesAtProjectStart) let l:sCurProjName = myMatch[1] let l:dResult[myMatch[1]] = { 'files': [], 'paths': [], 'not_exist': [] } endif let l:sLastFoundPath = myMatch[2] " ADDED! Jkooij " Strip the path of surrounding " characters, if there are any let l:sLastFoundPath = substitute(l:sLastFoundPath, "\"\\(.*\\)\"", "\\1", "g") let l:sLastFoundPath = expand(l:sLastFoundPath) " Expand any environment variables that might be in the path let l:sLastFoundPath = s:ParsePath(l:sLastFoundPath) endif " searching for opening brace { } let sTmpLine = l:sLine while (sTmpLine =~ '{') if (s:IsAbsolutePath(l:sLastFoundPath) || len(l:aPaths) == 0) call add(l:aPaths, s:ParsePath(l:sLastFoundPath)) else call add(l:aPaths, s:ParsePath(l:aPaths[len(l:aPaths) - 1].'/'.l:sLastFoundPath)) endif let l:iOpenedBraces = l:iOpenedBraces + 1 if (l:boolInNeededProject && l:iOpenedBraces > l:iOpenedBracesAtProjectStart && isdirectory(l:aPaths[len(l:aPaths) - 1])) call add(l:dResult[l:sCurProjName].paths, l:aPaths[len(l:aPaths) - 1]) endif let sTmpLine = substitute(sTmpLine, '{', '', '') endwhile " searching for filename if (l:sLine =~ '^[^={}]*$' && l:sLine !~ '^\s*$') " here we found something like filename " if (l:boolInNeededProject && (!g:indexer_enableWhenProjectDirFound || s:indexer_projectName != '') && l:iOpenedBraces > l:iOpenedBracesAtProjectStart) " we are in needed project "let l:sCurFilename = expand(s:ParsePath(l:aPaths[len(l:aPaths) - 1].'/'.s:Trim(l:sLine))) " CHANGED! Jkooij " expand() will change slashes based on 'shellslash' flag, " so call s:ParsePath() on expand() result for consistent slashes let l:sCurFilename = s:ParsePath(expand(l:aPaths[len(l:aPaths) - 1].'/'.s:Trim(l:sLine))) if (filereadable(l:sCurFilename)) " file readable! adding it call add(l:dResult[l:sCurProjName].files, l:sCurFilename) elseif (!isdirectory(l:sCurFilename)) call add(l:dResult[l:sCurProjName].not_exist, l:sCurFilename) endif endif endif endfor return l:dResult endfunction " returns whether or not file exists in list function! s:IsFileExistsInList(aList, sFilename) let l:sFilename = s:ParsePath(a:sFilename) if (index(a:aList, l:sFilename, 0, 1)) >= 0 return 1 endif return 0 endfunction " updating tags using ctags. " if boolAppend then just appends existing tags file with new tags from " current file (%) function! s:UpdateTags(boolAppend) " one tags file let l:sTagsFileWOPath = substitute(join(g:indexer_indexedProjects, '_'), '\s', '_', 'g') let l:sTagsFile = s:tagsDirname.'/'.l:sTagsFileWOPath if !isdirectory(s:tagsDirname) call mkdir(s:tagsDirname, "p") endif let l:sSavedFile = ParsePath(expand('%:p')) if (IsFileExistsInList(s:lNotExistFiles, l:sSavedFile)) " moving file from non-existing list to existing list call remove(s:lNotExistFiles, index(s:lNotExistFiles, l:sSavedFile)) call add(s:lFileList, l:sSavedFile) endif let l:sRecurseCode = '' if (a:boolAppend && filereadable(l:sTagsFile)) let l:sAppendCode = '-a' if (IsFileExistsInList(s:lFileList, l:sSavedFile)) " saved file are in file list let l:sFiles = l:sSavedFile else let l:sFiles = '' endif else let l:sAppendCode = '' let l:sFiles = '' let l:sFile = '' if (g:indexer_ctagsDontSpecifyFilesIfPossible && s:sMode == 'IndexerFile') let l:sRecurseCode = '-R' for l:sPath in s:lPathList let l:sFiles = l:sFiles.' "'.l:sPath.'"' endfor else for l:sFile in s:lFileList let l:sFiles = l:sFiles.' "'.l:sFile.'"' endfor endif endif if l:sFiles != '' "if (has('win32') || has('win64')) let l:sTagsFile = '"'.l:sTagsFile.'"' "endif if (has('win32') || has('win64')) let l:cmd = 'ctags -f '.l:sTagsFile.' '.l:sRecurseCode.' '.l:sAppendCode.' '.g:indexer_ctagsCommandLineOptions.' '.l:sFiles else let l:cmd = 'ctags -f '.l:sTagsFile.' '.l:sRecurseCode.' '.l:sAppendCode.' '.g:indexer_ctagsCommandLineOptions.' '.l:sFiles.' &' endif let l:resp = system(l:cmd) exec 'set tags='.substitute(s:tagsDirname.'/'.l:sTagsFileWOPath, ' ', '\\\\\\ ', 'g') endif "multiple files, calls from bat file "exec 'set tags=' "let l:lLines = [] "for l:sFile in s:lFileList "let l:sTagFile = s:tagsDirname.'/'.substitute(l:sFile, '[\\/:]', '_', 'g') "call add(l:lLines, 'ctags -f '.l:sTagFile.' '.g:indexer_ctagsCommandLineOptions.' '.l:sFile) "exec 'set tags+='.l:sTagFile "endfor "call writefile(l:lLines, s:tagsDirname.'/maketags.bat') "let l:cmd = s:tagsDirname.'/maketags.bat' "let l:resp = system(l:cmd) endfunction function! s:ApplyProjectSettings(dParse) " paths for Vim set path=. for l:sPath in a:dParse.paths if isdirectory(l:sPath) exec 'set path+='.substitute(l:sPath, ' ', '\\ ', 'g') endif endfor let s:lFileList = a:dParse.files let s:lPathList = a:dParse.paths let s:lNotExistFiles = a:dParse.not_exist augroup Indexer_SavSrcFile autocmd! Indexer_SavSrcFile BufWritePost augroup END " collect extensions of files in project to make autocmd on save these " files let l:sExtsList = '' let l:lFullList = s:lFileList + s:lNotExistFiles for l:lFile in l:lFullList let l:sExt = substitute(l:lFile, '^.*\([.\\/][^.\\/]\+\)$', '\1', '') if strpart(l:sExt, 0, 1) != '.' let l:sExt = strpart(l:sExt, 1) endif if (stridx(l:sExtsList, l:sExt) == -1) if (l:sExtsList != '') let l:sExtsList = l:sExtsList.',' endif let l:sExtsList = l:sExtsList.'*'.l:sExt endif endfor " defining autocmd at source files save exec 'autocmd Indexer_SavSrcFile BufWritePost '.l:sExtsList.' call UpdateTags('.(g:indexer_ctagsJustAppendTagsAtFileSave ? '1' : '0').')' " start full tags update call UpdateTags(0) endfunction " concatenates two lists preventing duplicates function! s:ConcatLists(lExistingList, lAddingList) let l:lResList = a:lExistingList for l:sItem in a:lAddingList if (index(l:lResList, l:sItem) == -1) call add(l:lResList, l:sItem) endif endfor return l:lResList endfunction function! s:GetDirsAndFilesFromAvailableFile() if (filereadable(g:indexer_indexerListFilename)) " read all projects from proj file let s:sMode = 'IndexerFile' let s:dParseAll = s:GetDirsAndFilesFromIndexerFile(g:indexer_indexerListFilename, s:indexer_projectName) elseif (filereadable(g:indexer_projectsSettingsFilename)) let s:sMode = 'ProjectFile' let s:dParseAll = s:GetDirsAndFilesFromProjectFile(g:indexer_projectsSettingsFilename, s:indexer_projectName) else let s:sMode = '' let s:dParseAll = {} endif endfunction function! s:ParseProjectSettingsFile() call GetDirsAndFilesFromAvailableFile() " let's found what files we should to index. " now we will search for project directory up by dir tree let l:i = 0 let l:sCurPath = '' while (g:indexer_enableWhenProjectDirFound && s:indexer_projectName == '' && l:i < 10) for l:sKey in keys(s:dParseAll) if (IsFileExistsInList(s:dParseAll[l:sKey].paths, expand('%:p:h').l:sCurPath)) let s:indexer_projectName = l:sKey if !(s:sMode == 'IndexerFile' && g:indexer_ctagsDontSpecifyFilesIfPossible) call GetDirsAndFilesFromAvailableFile() else call add(g:indexer_indexedProjects, l:sKey) endif break endif endfor let l:sCurPath = l:sCurPath.'/..' let l:i = l:i + 1 endwhile if !(s:sMode == 'IndexerFile' && g:indexer_ctagsDontSpecifyFilesIfPossible) let s:iTotalFilesAvailableCnt = 0 if (!s:boolIndexingModeOn) for l:sKey in keys(s:dParseAll) let s:iTotalFilesAvailableCnt = s:iTotalFilesAvailableCnt + len(s:dParseAll[l:sKey].files) if ((g:indexer_enableWhenProjectDirFound && IsFileExistsInList(s:dParseAll[l:sKey].paths, expand('%:p:h'))) || (IsFileExistsInList(s:dParseAll[l:sKey].files, expand('%:p')))) " user just opened file from project l:sKey. We should add it to " result lists " adding name of this project to g:indexer_indexedProjects call add(g:indexer_indexedProjects, l:sKey) endif endfor endif endif " build final list of files, paths and non-existing files let l:dParse = { 'files':[], 'paths':[], 'not_exist':[] } for l:sKey in g:indexer_indexedProjects let l:dParse.files = ConcatLists(l:dParse.files, s:dParseAll[l:sKey].files) let l:dParse.paths = ConcatLists(l:dParse.paths, s:dParseAll[l:sKey].paths) let l:dParse.not_exist = ConcatLists(l:dParse.not_exist, s:dParseAll[l:sKey].not_exist) endfor if (s:boolIndexingModeOn) call ApplyProjectSettings(l:dParse) else if (len(l:dParse.files) > 0 || len(l:dParse.paths) > 0) let s:boolIndexingModeOn = 1 " creating auto-refresh index at project file save augroup Indexer_SavPrjFile autocmd! Indexer_SavPrjFile BufWritePost augroup END if (filereadable(g:indexer_indexerListFilename)) let l:sIdxFile = substitute(g:indexer_indexerListFilename, '^.*[\\/]\([^\\/]\+\)$', '\1', '') exec 'autocmd Indexer_SavPrjFile BufWritePost '.l:sIdxFile.' call ParseProjectSettingsFile()' elseif (filereadable(g:indexer_projectsSettingsFilename)) let l:sPrjFile = substitute(g:indexer_projectsSettingsFilename, '^.*[\\/]\([^\\/]\+\)$', '\1', '') exec 'autocmd Indexer_SavPrjFile BufWritePost '.l:sPrjFile.' call ParseProjectSettingsFile()' endif call ApplyProjectSettings(l:dParse) let l:iNonExistingCnt = len(s:lNotExistFiles) if (l:iNonExistingCnt > 0) if l:iNonExistingCnt < 100 echo "Indexer Warning: project loaded, but there's ".l:iNonExistingCnt." non-existing files: \n\n".join(s:lNotExistFiles, "\n") else echo "Indexer Warning: project loaded, but there's ".l:iNonExistingCnt." non-existing files. Type :IndexerInfo for details." endif endif else " there's no project started. " we should define autocmd to detect if file from project will be opened later augroup Indexer_LoadFile autocmd! Indexer_LoadFile BufReadPost autocmd Indexer_LoadFile BufReadPost * call IndexerInit() augroup END endif endif endfunction function! s:IndexerInfo() if (s:sMode == '') echo '* Filelist: not found' elseif (s:sMode == 'IndexerFile') echo '* Filelist: indexer file: '.g:indexer_indexerListFilename elseif (s:sMode == 'ProjectFile') echo '* Filelist: project file: '.g:indexer_projectsSettingsFilename else echo '* Filelist: Unknown' endif echo '* Projects indexed: '.join(g:indexer_indexedProjects, ', ') if !(s:sMode == 'IndexerFile' && g:indexer_ctagsDontSpecifyFilesIfPossible) echo "* Files indexed: there's ".len(s:lFileList).' files. Type :IndexerFiles to list' echo "* Files not found: there's ".len(s:lNotExistFiles).' non-existing files. '.join(s:lNotExistFiles, ', ') endif echo '* Paths: '.&path echo '* Tags file: '.&tags echo '* Project root: '.($INDEXER_PROJECT_ROOT != '' ? $INDEXER_PROJECT_ROOT : 'not found').' (Project root is a directory which contains "'.g:indexer_dirNameForSearch.'" directory)' endfunction function! s:IndexerFilesList() echo "* Files indexed: ".join(s:lFileList, ', ') endfunction function! s:IndexerInit() augroup Indexer_LoadFile autocmd! Indexer_LoadFile BufReadPost augroup END " actual tags dirname. If .vimprj directory will be found then this tags " dirname will be /path/to/dir/.vimprj/tags let s:tagsDirname = g:indexer_tagsDirname let g:indexer_indexedProjects = [] let s:sMode = '' let s:lFileList = [] let s:lNotExistFiles = [] let s:boolIndexingModeOn = 0 if g:indexer_lookForProjectDir " need to look for .vimprj directory let l:i = 0 let l:sCurPath = '' let $INDEXER_PROJECT_ROOT = '' while (l:i < g:indexer_recurseUpCount) if (isdirectory(expand('%:p:h').l:sCurPath.'/'.g:indexer_dirNameForSearch)) let $INDEXER_PROJECT_ROOT = simplify(expand('%:p:h').l:sCurPath) exec 'cd '.substitute($INDEXER_PROJECT_ROOT, ' ', '\\ ', 'g') break endif let l:sCurPath = l:sCurPath.'/..' let l:i = l:i + 1 endwhile if $INDEXER_PROJECT_ROOT != '' " project root was found. " " set directory for tags in .vimprj dir let s:tagsDirname = $INDEXER_PROJECT_ROOT.'/'.g:indexer_dirNameForSearch.'/tags' " sourcing all *vim files in .vimprj dir let l:lSourceFilesList = split(glob($INDEXER_PROJECT_ROOT.'/'.g:indexer_dirNameForSearch.'/*vim'), '\n') let l:sThisFile = expand('%:p') for l:sFile in l:lSourceFilesList if (l:sFile != l:sThisFile) exec 'source '.l:sFile endif endfor endif endif call s:ParseProjectSettingsFile() endfunction " --------- init variables -------- if !exists('g:indexer_lookForProjectDir') let g:indexer_lookForProjectDir = 1 endif if !exists('g:indexer_dirNameForSearch') let g:indexer_dirNameForSearch = '.vimprj' endif if !exists('g:indexer_recurseUpCount') let g:indexer_recurseUpCount = 10 endif if !exists('g:indexer_indexerListFilename') let g:indexer_indexerListFilename = $HOME.'/.indexer_files' endif if !exists('g:indexer_projectsSettingsFilename') let g:indexer_projectsSettingsFilename = $HOME.'/.vimprojects' endif if !exists('g:indexer_projectName') let g:indexer_projectName = '' endif if !exists('g:indexer_enableWhenProjectDirFound') let g:indexer_enableWhenProjectDirFound = '1' endif if !exists('g:indexer_tagsDirname') let g:indexer_tagsDirname = $HOME.'/.vimtags' endif if !exists('g:indexer_ctagsCommandLineOptions') let g:indexer_ctagsCommandLineOptions = '--c++-kinds=+p+l --fields=+iaS --extra=+q' endif if !exists('g:indexer_ctagsJustAppendTagsAtFileSave') let g:indexer_ctagsJustAppendTagsAtFileSave = 1 endif if !exists('g:indexer_ctagsDontSpecifyFilesIfPossible') let g:indexer_ctagsDontSpecifyFilesIfPossible = '0' endif let s:indexer_projectName = g:indexer_projectName " -------- init commands --------- if exists(':IndexerInfo') != 2 command -nargs=? -complete=file IndexerInfo call IndexerInfo() endif if exists(':IndexerFiles') != 2 command -nargs=? -complete=file IndexerFiles call IndexerFilesList() endif if exists(':IndexerRebuild') != 2 command -nargs=? -complete=file IndexerRebuild call UpdateTags(0) endif augroup Indexer_LoadFile autocmd! Indexer_LoadFile BufReadPost autocmd Indexer_LoadFile BufReadPost * call IndexerInit() augroup END call IndexerInit()