diff options
| -rw-r--r-- | Makefile | 41 | ||||
| -rw-r--r-- | README.md | 146 | ||||
| -rw-r--r-- | TODO.md | 7 | ||||
| -rw-r--r-- | autoload.vim | 272 | ||||
| -rw-r--r-- | doc/README.footer | 2 | ||||
| -rw-r--r-- | doc/README.header | 20 | ||||
| -rw-r--r-- | easytags.vim | 93 | 
7 files changed, 581 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3dada56 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +DEPENDS=$(HOME)/.vim/autoload/xolox/escape.vim \ +		$(HOME)/.vim/autoload/xolox/timer.vim +VIMDOC=doc/easytags.txt +HTMLDOC=doc/readme.html +ZIPDIR := $(shell mktemp -d) +ZIPFILE := $(shell mktemp -u) + +# NOTE: Make does NOT expand the following back ticks! +VERSION=`grep '^" Version:' easytags.vim | awk '{print $$3}'` + +# The main rule builds a ZIP that can be published to http://www.vim.org. +archive: Makefile easytags.vim autoload.vim $(VIMDOC) $(HTMLDOC) +	@echo "Creating \`easytags-$(VERSION).zip' .." +	@mkdir -p $(ZIPDIR)/plugin $(ZIPDIR)/autoload/xolox $(ZIPDIR)/doc +	@cp easytags.vim $(ZIPDIR)/plugin +	@cp autoload.vim $(ZIPDIR)/autoload/easytags.vim +	@cp $(DEPENDS) $(ZIPDIR)/autoload/xolox +	@cp $(VIMDOC) $(ZIPDIR)/doc/easytags.txt +	@cp $(HTMLDOC) $(ZIPDIR)/doc/easytags.html +	@cd $(ZIPDIR) && zip -r $(ZIPFILE) . >/dev/null +	@rm -R $(ZIPDIR) +	@mv $(ZIPFILE) easytags-$(VERSION).zip + +# This rule converts the Markdown README to Vim documentation. +$(VIMDOC): Makefile README.md +	@echo "Creating \`$(VIMDOC)' .." +	@mkd2vimdoc.py `basename $(VIMDOC)` < README.md > $(VIMDOC) + +# This rule converts the Markdown README to HTML, which reads easier. +$(HTMLDOC): Makefile README.md doc/README.header doc/README.footer +	@echo "Creating \`$(HTMLDOC)' .." +	@cat doc/README.header > $(HTMLDOC) +	@cat README.md | markdown | SmartyPants >> $(HTMLDOC) +	@cat doc/README.footer >> $(HTMLDOC) + +# This is only useful for myself, it uploads the latest README to my website. +web: $(HTMLDOC) +	@echo "Uploading homepage .." +	@scp -q $(HTMLDOC) vps:/home/peterodding.com/public/files/code/vim/easytags/index.html + +all: archive web diff --git a/README.md b/README.md new file mode 100644 index 0000000..bebb52f --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# Automated tag generation and syntax highlighting in Vim + +[Vim] [vim] has long been my favorite text editor and combined with [Exuberant +Ctags] [exuberant_ctags] it has the potential to provide most of what I expect +from an [integrated development environment] [ide]. Exuberant Ctags is the +latest incarnation of a [family of computer programs] [ctags] that scan +source code files to create an index of identifiers (tags) and where they are +defined. Vim uses this index (a so-called tags file) to enable you to jump to +the definition of any identifier using the `Ctrl-]` mapping. + +When you're familiar with integrated development environments you may recognize +this feature as "Go-to definition". One advantage of the combination of Vim and +Exuberant Ctags over integrated development environments is that Vim supports +syntax highlighting for [over 500 file types] [vim_support] (!) and Exuberant +Ctags can generate tags for [over 40 file types] [ctags_support] as well... + +There's just one problem: You have to manually keep your tags files up-to-date +and this turns about to be a royal pain in the ass! So I set out to write a Vim +plug-in that would do this boring work for me. When I finished the plug-in's +basic functionality (one automatic command and a call to `system()`) I became +interested in dynamic syntax highlighting, so I added that as well to see if it +would work -- surprisingly well I'm happy to report! + +## Install & first use + +Unzip the most recent [ZIP archive] [latest_zip] file inside your Vim profile +directory (usually this is `~/.vim` on UNIX and `%USERPROFILE%\vimfiles` on +Windows), restart Vim and try it out: Edit any file type supported by Exuberant +Ctags and within ten seconds the plug-in should create/update your tags file +(`~/.vimtags` on UNIX, `~/_vimtags` on Windows) with the tags defined in the +file you just edited! This means that whatever file you're editing in Vim (as +long as its on the local file system), tags will always be available by the +time you need them! + +Additionally if the file you just opened is a C, Lua, PHP, Python or Vim source +file you should also notice that the function and type names defined in the +file have been syntax highlighted. + +## Configuration + +The plug-in is intended to work without configuration but can be customized by +changing the following options: + +### The `easytags_file` option + +As mentioned above the plug-in will store your tags in `~/.vimtags` on UNIX and +`~/_vimtags` on Windows. To change the location of this file, set the global +variable `easytags_file`, e.g.: + +    :let g:easytags_file = '~/.vim/tags' + +A leading `~` in the `easytags_file` variable is expanded to your current home +directory (`$HOME` on UNIX, `%USERPROFILE%` on Windows). + +### The `easytags_always_enabled` option + +By default the plug-in automatically generates and highlights tags when you +stop typing for a few seconds. This means that when you edit a file, the +dynamic highlighting won't appear until you pause for a moment. If you don't +want this you can configure the plug-in to always enable dynamic highlighting: + +    :let g:easytags_always_enabled = 1 + +Be warned that after setting this option you'll probably notice why it's +disabled by default: Every time you edit a file in Vim, the plug-in will first +run Exuberant Ctags and then highlight the tags, which slows Vim down quite a +lot. I have some ideas on how to improve this latency by executing Exuberant +Ctags in the background, so stay tuned! + +Note: If you change this option it won't apply until you restart Vim, so you'll +have to set this option in your `~/.vimrc` script (`~/_vimrc` on Windows). + +### The `easytags_on_cursorhold` option + +As I explained above the plug-in by default doesn't update or highlight your +tags until you stop typing for a moment. The plug-in tries hard to do the least +amount of work possible in this break but it might still interrupt your +workflow. If it does you can disable the periodic update: + +    :let g:easytags_on_cursorhold = 0 +     +Note: Like the `easytags_always_enabled` option, if you change this option it +won't apply until you restart Vim, so you'll have to set this option in your +`~/.vimrc` script (`~/_vimrc` on Windows). + +### The `easytags_resolve_links` option + +UNIX has [symbolic links] [symlinks] and [hard links] [hardlinks], both of +which conflict with the concept of having one unique location for every +identifier. With regards to hard links there's not much anyone can do, but +because I use symbolic links quite a lot I've added this option. It's disabled +by default since it has a small performance impact and might not do what +unknowing users expect it to: When you enable this option the plug-in will +resolve symbolic links in pathnames, which means your tags file will only +contain entries with [canonical pathnames] [canon]. To enable this option +(which I strongly suggest doing when you run UNIX and use symbolic links) +execute the following Vim command: + +    :let easytags_resolve_links = 1 + +## Troubleshooting + +Once or twice now in several years I've experienced Exuberant Ctags getting +into an infinite loop when given garbage input. In my case this happened by +accident a few days ago :-|. Because my plug-in executes `ctags` in the +foreground this will block Vim indefinitely! If this happens you might be +able to kill `ctags` by pressing `Ctrl-C` but if that doesn't work you can also +kill it without stopping Vim using a task manager or the `pkill` command: + +    pkill -KILL ctags + +If Vim seems very slow and you suspect this plug-in might be the one to blame, +increase Vim's verbosity level: + +    :set vbs=1 + +Every time the plug-in executes it will time how long the execution takes and +add the results to Vim's message history, which you can view by executing the +`:messages` command. + +## Contact + +If you have questions, bug reports, suggestions, etc. the author can be +contacted at <peter@peterodding.com>. The latest version is available at +<http://peterodding.com/code/vim/easytags> and +<http://github.com/xolox/vim-easytags>. If you like this plug-in please vote +for it on [www.vim.org] [vim_scripts_entry]. + +## License + +This software is licensed under the [MIT license] [mit_license].   +© 2010 Peter Odding <<peter@peterodding.com>>. + + +[canon]: http://en.wikipedia.org/wiki/Canonicalization +[ctags]: http://en.wikipedia.org/wiki/Ctags +[ctags_support]: http://ctags.sourceforge.net/languages.html +[exuberant_ctags]: http://ctags.sourceforge.net/ +[hardlinks]: http://en.wikipedia.org/wiki/Hard_link +[ide]: http://en.wikipedia.org/wiki/Integrated_development_environment +[latest_zip]: http://github.com/downloads/xolox/vim-easytags/easytags-latest.zip +[mit_license]: http://en.wikipedia.org/wiki/MIT_License +[symlinks]: http://en.wikipedia.org/wiki/Symbolic_link +[vim]: http://www.vim.org/ +[vim_scripts_entry]: http://www.vim.org/scripts/script.php?script_id=3114 +[vim_support]: http://ftp.vim.org/vim/runtime/syntax/ @@ -0,0 +1,7 @@ +# Long term plans + + * Integration with my unreleased project plug-in so that when you edit any +   file in a project, all related files are automatically scanned for tags? + + * Use separate tags files for each language stored in ~/.vim/tags/ to increase +   performance because a single, global tags file quickly grows to a megabyte? diff --git a/autoload.vim b/autoload.vim new file mode 100644 index 0000000..5900b85 --- /dev/null +++ b/autoload.vim @@ -0,0 +1,272 @@ +" Vim script +" Maintainer: Peter Odding <peter@peterodding.com> +" Last Change: June 6, 2010 +" URL: http://peterodding.com/code/vim/easytags + +function! easytags#autoload() " {{{1 + +  " Update the entries for the current file in the global tags file? +  let start = xolox#timer#start() +  if getftime(expand('%')) > getftime(easytags#get_tagsfile()) +    UpdateTags +    call xolox#timer#stop(start, "easytags.vim: Automatically updated tags in %s second(s)") +  endif + +  " Apply highlighting of tags in global tags file to current buffer? +  if &eventignore !~? '\<syntax\>' +    let start = xolox#timer#start() +    if !exists('b:easytags_last_highlighted') +      HighlightTags +    else +      for tagfile in tagfiles() +        if getftime(tagfile) > b:easytags_last_highlighted +          HighlightTags +          break +        endif +      endfor +    endif +    let b:easytags_last_highlighted = localtime() +    call xolox#timer#stop(start, "easytags.vim: Automatically highlighted tags in %s second(s)") +  endif + +endfunction + +function! easytags#update_cmd(filter_invalid_tags) " {{{1 +  if !exists('s:supported_filetypes') +    let start = xolox#timer#start() +    let listing = system(g:easytags_cmd . ' --list-languages') +    if v:shell_error +      throw "Failed to get Exuberant Ctags language mappings!" +    endif +    let s:supported_filetypes = split(listing, '\n') +    call map(s:supported_filetypes, 'easytags#to_vim_ft(v:val)') +    call xolox#timer#stop(start, "easytags.vim: Parsed language mappings in %s second(s)") +  endif +  let supported_filetype = index(s:supported_filetypes, &ft) >= 0 +  if supported_filetype || a:filter_invalid_tags +    let start = xolox#timer#start() +    let tagsfile = easytags#get_tagsfile() +    let filename = expand('%:p') +    if g:easytags_resolve_links +      let filename = resolve(filename) +    endif +    let command = [g:easytags_cmd, '-f', shellescape(tagsfile)] +    if filereadable(tagsfile) +      call add(command, '-a') +      let start_filter = xolox#timer#start() +      let lines = readfile(tagsfile) +      let filters = [] +      if supported_filetype +        call add(filters, 'v:val !~ ' . string('\s' . xolox#escape#pattern(filename) . '\s')) +      endif +      if a:filter_invalid_tags +        call add(filters, 'filereadable(get(split(v:val, "\t"), 1))') +      endif +      let filter = 'v:val =~ "^!_TAG_" || (' . join(filters, ' && ') . ')' +      let filtered = filter(copy(lines), filter) +      if lines != filtered +        call writefile(filtered, tagsfile) +      endif +      call xolox#timer#stop(start_filter, "easytags.vim: Filtered tags file in %s second(s)") +    endif +    if supported_filetype +      call add(command, '--language-force=' . easytags#to_ctags_ft(&ft)) +      call add(command, shellescape(filename)) +      let listing = system(join(command)) +      if v:shell_error +        let message = "Failed to update tags file! (%s)" +        throw printf(message, listing) +      endif +    endif +    call xolox#timer#stop(start, "easytags.vim: Updated tags in %s second(s)") +  endif +endfunction + +function! easytags#highlight_cmd() " {{{1 +  if exists('g:syntax_on') && has_key(s:tagkinds, &ft) +    let start = xolox#timer#start() +    let taglist = filter(taglist('.'), "get(v:val, 'language', '') ==? &ft") +    for tagkind in s:tagkinds[&ft] +      let hlgroup_tagged = tagkind.hlgroup . 'Tag' +      if hlexists(hlgroup_tagged) +        execute 'syntax clear' hlgroup_tagged +      else +        execute 'highlight def link' hlgroup_tagged tagkind.hlgroup +      endif +      let matches = filter(copy(taglist), tagkind.filter) +      call map(matches, 'xolox#escape#pattern(get(v:val, "name"))') +      let pattern = tagkind.pattern_prefix . '\%(' . join(s:unique(matches), '\|') . '\)' . tagkind.pattern_suffix +      let command = 'syntax match %s /%s/ containedin=ALLBUT,.*String.*,.*Comment.*' +      execute printf(command, hlgroup_tagged, escape(pattern, '/')) +    endfor +    redraw +    call xolox#timer#stop(start, "easytags.vim: Highlighted tags in %s second(s)") +  endif +endfunction + +function! s:unique(list) +	let index = 0 +	while index < len(a:list) +		let value = a:list[index] +		let match = index(a:list, value, index+1) +		if match >= 0 +			call remove(a:list, match) +		else +			let index += 1 +		endif +		unlet value +	endwhile +	return a:list +endfunction + +function! easytags#get_tagsfile() " {{{1 +  let tagsfile = expand(g:easytags_file) +  if filereadable(tagsfile) && filewritable(tagsfile) != 1 +    let message = "easytags.vim: The tags file isn't writable! (%s)" +    echoerr printf(message, fnamemodify(directory, ':~')) +  endif +  return tagsfile +endfunction + +function! easytags#define_tagkind(object) " {{{1 +  if !has_key(a:object, 'pattern_prefix') +    let a:object.pattern_prefix = '\C\<' +  endif +  if !has_key(a:object, 'pattern_suffix') +    let a:object.pattern_suffix = '\>' +  endif +  if !has_key(s:tagkinds, a:object.filetype) +    let s:tagkinds[a:object.filetype] = [] +  endif +  call add(s:tagkinds[a:object.filetype], a:object) +endfunction + +function! easytags#map_filetypes(vim_ft, ctags_ft) " {{{1 +  call add(s:vim_filetypes, a:vim_ft) +  call add(s:ctags_filetypes, a:ctags_ft) +endfunction + +function! easytags#to_vim_ft(ctags_ft) " {{{1 +  let type = tolower(a:ctags_ft) +  let index = index(s:ctags_filetypes, type) +  return index >= 0 ? s:vim_filetypes[index] : type +endfunction + +function! easytags#to_ctags_ft(vim_ft) " {{{1 +  let type = tolower(a:vim_ft) +  let index = index(s:vim_filetypes, type) +  return index >= 0 ? s:ctags_filetypes[index] : type +endfunction + +" Built-in file type & tag kind definitions. {{{1 + +if !exists('s:tagkinds') + +  let s:vim_filetypes = [] +  let s:ctags_filetypes = [] +  call easytags#map_filetypes('cpp', 'c++') +  call easytags#map_filetypes('cs', 'c#') +  call easytags#map_filetypes(exists('filetype_asp') ? filetype_asp : 'aspvbs', 'asp') + +  let s:tagkinds = {} + +  " Enable line continuation. +  let s:cpo_save = &cpo +  set cpo&vim + +  " Lua. {{{2 + +  call easytags#define_tagkind({ +        \ 'filetype': 'lua', +        \ 'hlgroup': 'luaFunc', +        \ 'filter': 'get(v:val, "kind") ==# "f"'}) + +  " C. {{{2 + +  call easytags#define_tagkind({ +        \ 'filetype': 'c', +        \ 'hlgroup': 'cType', +        \ 'filter': 'get(v:val, "kind") =~# "[cgstu]"'}) + +  call easytags#define_tagkind({ +        \ 'filetype': 'c', +        \ 'hlgroup': 'cPreProc', +        \ 'filter': 'get(v:val, "kind") ==# "d"'}) + +  call easytags#define_tagkind({ +        \ 'filetype': 'c', +        \ 'hlgroup': 'cFunction', +        \ 'filter': 'get(v:val, "kind") =~# "[fp]"'}) + +  highlight def link cFunction Function + +  " PHP. {{{2 + +  call easytags#define_tagkind({ +        \ 'filetype': 'php', +        \ 'hlgroup': 'phpFunctions', +        \ 'filter': 'get(v:val, "kind") ==# "f"'}) + +  call easytags#define_tagkind({ +        \ 'filetype': 'php', +        \ 'hlgroup': 'phpClasses', +        \ 'filter': 'get(v:val, "kind") ==# "c"'}) + +  " Vim script. {{{2 + +  call easytags#define_tagkind({ +        \ 'filetype': 'vim', +        \ 'hlgroup': 'vimAutoGroup', +        \ 'filter': 'get(v:val, "kind") ==# "a"'}) + +  highlight def link vimAutoGroup vimAutoEvent + +  call easytags#define_tagkind({ +        \ 'filetype': 'vim', +        \ 'hlgroup': 'vimCommand', +        \ 'filter': 'get(v:val, "kind") ==# "c"', +        \ 'pattern_prefix': '\(\(^\|\s\):\?\)\@<=', +        \ 'pattern_suffix': '\(!\?\(\s\|$\)\)\@='}) + +  " Exuberant Ctags doesn't mark script local functions in Vim scripts as +  " "static". When your tags file contains search patterns this plug-in can use +  " those search patterns to check which Vim script functions are defined +  " globally and which script local. + +  call easytags#define_tagkind({ +        \ 'filetype': 'vim', +        \ 'hlgroup': 'vimFuncName', +        \ 'filter': 'get(v:val, "kind") ==# "f" && get(v:val, "cmd") !~? ''<sid>\w\|\<s:\w''', +        \ 'pattern_prefix': '\C\%(\<s:\|<[sS][iI][dD]>\)\@<!\<'}) + +  call easytags#define_tagkind({ +        \ 'filetype': 'vim', +        \ 'hlgroup': 'vimScriptFuncName', +        \ 'filter': 'get(v:val, "kind") ==# "f" && get(v:val, "cmd") =~? ''<sid>\w\|\<s:\w''', +        \ 'pattern_prefix': '\C\%(\<s:\|<[sS][iI][dD]>\)'}) + +  highlight def link vimScriptFuncName vimFuncName + +  " Python. {{{2 + +  call easytags#define_tagkind({ +        \ 'filetype': 'python', +        \ 'hlgroup': 'pythonFunction', +        \ 'filter': 'get(v:val, "kind") ==# "f"', +        \ 'pattern_prefix': '\%(\<def\s\+\)\@<!\<'}) + +  call easytags#define_tagkind({ +        \ 'filetype': 'python', +        \ 'hlgroup': 'pythonMethod', +        \ 'filter': 'get(v:val, "kind") ==# "m"', +        \ 'pattern_prefix': '\.\@<='}) + +  highlight def link pythonMethodTag pythonFunction + +  " Restore "cpoptions". +  let &cpo = s:cpo_save +  unlet s:cpo_save + +endif + +" vim: ts=2 sw=2 et diff --git a/doc/README.footer b/doc/README.footer new file mode 100644 index 0000000..70723d9 --- /dev/null +++ b/doc/README.footer @@ -0,0 +1,2 @@ + </body> +</html> diff --git a/doc/README.header b/doc/README.header new file mode 100644 index 0000000..c2e063d --- /dev/null +++ b/doc/README.header @@ -0,0 +1,20 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" +"http://www.w3.org/TR/html4/strict.dtd"> +<html> + <head> +  <title>easytags.vim - Automated tag generation and syntax highlighting in Vim</title> +  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> +  <style type="text/css"> +   body { font-family: sans-serif; padding: 1em 30% 4em 1em; } +   a:link, a:visited { color: #000080; } +   a:hover, a:active { color: #8B0000; } +   pre, code, tt { font-family: Monaco, Consolas, monospace; } +   pre { margin-left: 2em; } +   code { background: #F6F6F6; padding: .1em .3em; } +   p { text-align: justify; line-height: 1.75em; } +   h1 { padding: 0 30% 0 0; } +   h2, h3 { border-bottom: 2px solid #F6F6F6; margin: 2em 0 -0.5em 0; padding-left: 0.5em; } +   hr { height: 0; border: none; border: 1px solid #F6F6F6; } +  </style> + </head> + <body> diff --git a/easytags.vim b/easytags.vim new file mode 100644 index 0000000..31bf9d0 --- /dev/null +++ b/easytags.vim @@ -0,0 +1,93 @@ +" Vim plug-in +" Maintainer: Peter Odding <peter@peterodding.com> +" Last Change: June 6, 2010 +" URL: http://peterodding.com/code/vim/easytags +" Requires: Exuberant Ctags (http://ctags.sf.net) +" License: MIT +" Version: 1.6 + +" Support for automatic update using the GLVS plug-in. +" GetLatestVimScripts: 3114 1 :AutoInstall: easytags.zip + +" Don't source the plug-in when its already been loaded or &compatible is set. +if &cp || exists('g:loaded_easytags') +  finish +endif + +" Configuration defaults. {{{1 + +if !exists('g:easytags_file') +  if has('win32') || has('win64') +    let g:easytags_file = '~/_vimtags' +  else +    let g:easytags_file = '~/.vimtags' +  endif +endif + +if !exists('g:easytags_resolve_links') +  let g:easytags_resolve_links = 0 +endif + +if !exists('g:easytags_always_enabled') +  let g:easytags_always_enabled = 0 +endif + +if !exists('g:easytags_on_cursorhold') +  let g:easytags_on_cursorhold = 1 +endif + +" Before sourcing the rest of the plug-in first check that the location of the +" "Exuberant Ctags" program has been configured or that the program exists in +" one of its default locations. + +if exists('g:easytags_cmd') && executable(g:easytags_cmd) +  let s:ctags_installed = 1 +else +  " On Ubuntu Linux, Exuberant Ctags is installed as `ctags'. On Debian Linux, +  " Exuberant Ctags is installed as `exuberant-ctags'. On Free-BSD, Exuberant +  " Ctags is installed as `exctags'. Finally there is `ctags.exe' on Windows. +  for s:command in ['ctags', 'exuberant-ctags', 'esctags', 'ctags.exe'] +    if executable(s:command) +      let g:easytags_cmd = s:command +      let s:ctags_installed = 1 +      break +    endif +  endfor +endif + +if !exists('s:ctags_installed') +  echomsg "easytags.vim: Exuberant Ctags unavailable! Plug-in not loaded." +  if executable('apt-get') +    echomsg "On Ubuntu & Debian Linux, you can install Exuberant Ctags" +    echomsg "by installing the package named `exuberant-ctags':" +    echomsg "  sudo apt-get install exuberant-ctags" +  else +    echomsg "Please download & install Exuberant Ctags from http://ctags.sf.net" +  endif +  finish +endif + +" The :UpdateTags and :HighlightTags commands. {{{1 + +command! -bar -bang UpdateTags call easytags#update_cmd(<q-bang> == '!') +command! -bar HighlightTags call easytags#highlight_cmd() + +" Automatic commands. {{{1 + +augroup PluginEasyTags +  autocmd! +  if g:easytags_always_enabled +    autocmd BufReadPost,BufWritePost * call easytags#autoload() +  endif +  if g:easytags_on_cursorhold +    autocmd CursorHold,CursorHoldI * call easytags#autoload() +  endif +  autocmd User PublishPre HighlightTags +augroup END + +" }}}1 + +" Make sure the plug-in is only loaded once. +let g:loaded_easytags = 1 + +" vim: ts=2 sw=2 et  | 
