commit 4985aad17d0371a8c77e2e0f86e5cee5ff8477d1 Author: Buddy Sandidge Date: Wed Feb 19 22:11:26 2014 -0800 Squashed 'vim/bundle/coffee-script/' content from commit 18c68524a git-subtree-dir: vim/bundle/coffee-script git-subtree-split: 18c68524ab8a043a566bbe227ea8f81ab922d092 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ff7b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.*.sw[a-z] +.*.un~ +doc/tags + diff --git a/Copying.md b/Copying.md new file mode 100644 index 0000000..51cf8d1 --- /dev/null +++ b/Copying.md @@ -0,0 +1,15 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2010 to 2012 Mick Koch + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e6ef409 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +REF = HEAD +VERSION = $(shell git describe --always $(REF)) + +ARCHIVE = vim-coffee-script-$(VERSION).zip +ARCHIVE_DIRS = after autoload compiler doc ftdetect ftplugin indent syntax + +# Don't do anything by default. +all: + +# Make vim.org zipball. +archive: + git archive $(REF) -o $(ARCHIVE) -- $(ARCHIVE_DIRS) + +# Remove zipball. +clean: + -rm -f $(ARCHIVE) + +# Build the list of syntaxes for @coffeeAll. +coffeeAll: + @grep -E 'syn (match|region)' syntax/coffee.vim |\ + grep -v 'contained' |\ + awk '{print $$3}' |\ + uniq + +.PHONY: all archive clean hash coffeeAll diff --git a/News.md b/News.md new file mode 100644 index 0000000..9706796 --- /dev/null +++ b/News.md @@ -0,0 +1,18 @@ +### Version 002 (December 5, 2011) + +Added binary numbers (0b0101) and fixed some bugs (#9, #62, #63, #65). + +### Version 001 (October 18, 2011) + +Removed deprecated `coffee_folding` option, added `coffee_compile_vert` option, +split out compiler, fixed indentation and syntax bugs, and added Haml support +and omnicompletion. + + - The coffee compiler is now a proper vim compiler that can be loaded with + `:compiler coffee`. + - The `coffee_compile_vert` option can now be set to split the CoffeeCompile + buffer vertically by default. + - CoffeeScript is now highlighted inside the `:coffeescript` filter in Haml. + - Omnicompletion (`:help compl-omni`) now uses JavaScript's dictionary to + complete words. + - We now have a fancy version number. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..d557848 --- /dev/null +++ b/Readme.md @@ -0,0 +1,599 @@ +This project adds [CoffeeScript] support to vim. It covers syntax, indenting, +compiling, and more. + +![Screenshot](http://i.imgur.com/j1BhpZQ.png) + +[CoffeeScript]: http://coffeescript.org/ + +## Table of Contents + +- Installation + - [Requirements](#requirements) + - [Install using Pathogen](#install-using-pathogen) + - [Install using Vundle](#install-using-vundle) + - [Install from a Zip File](#install-from-a-zip-file) +- Coffee Commands + - [Compile to JavaScript](#compile-to-javascript) + - [Compile CoffeeScript Snippets](#coffeecompile-compile-coffeescript-snippets) + - [Live Preview Compiling](#coffeewatch-live-preview-compiling) + - [Run CoffeeScript Snippets](#coffeerun-run-coffeescript-snippets) + - [Lint your CoffeeScript](#coffeelint-lint-your-coffeescript) +- Extras + - [Literate CoffeeScript](#literate-coffeescript) + - [CoffeeScript in HTML](#coffeescript-in-html) + - [CoffeeScript in Haml](#coffeescript-in-haml) +- Configuration + - [Custom Autocmds](#custom-autocmds) + - [Configuration Variables](#configuration-variables) + - [Configure Syntax Highlighting](#configure-syntax-highlighting) + - [Tune Vim for CoffeeScript](#tune-vim-for-coffeescript) + +## Requirements + + - vim 7.4 or later + - coffee 1.2.0 or later + +## Install using Pathogen + +This project uses rolling releases based on git commits, so pathogen is a +natural fit for it. If you're already using pathogen, you can skip to step 4. + +1. Install [pathogen.vim] into `~/.vim/autoload/` (see [pathogen's + readme][install-pathogen] for more information.) + +[pathogen.vim]: http://www.vim.org/scripts/script.php?script_id=2332 +[install-pathogen]: https://github.com/tpope/vim-pathogen#installation + +2. Enable pathogen in your vimrc. Here's a bare-minimum vimrc that enables + all the features of `vim-coffee-script`: + + ```vim + call pathogen#infect() + syntax enable + filetype plugin indent on + ``` + + If you already have a vimrc built up, just make sure it contains these calls, + in this order. + +3. Create the directory `~/.vim/bundle/`: + + mkdir ~/.vim/bundle + +4. Clone the `vim-coffee-script` repo into `~/.vim/bundle/`: + + git clone https://github.com/kchmck/vim-coffee-script.git ~/.vim/bundle/vim-coffee-script/ + +Updating takes two steps: + +1. Change into `~/.vim/bundle/vim-coffee-script/`: + + cd ~/.vim/bundle/vim-coffee-script + +2. Pull in the latest changes: + + git pull + +## Install using Vundle + +1. [Install Vundle] into `~/.vim/bundle/`. + +[Install Vundle]: https://github.com/gmarik/vundle#quick-start + +2. Configure your vimrc for Vundle. Here's a bare-minimum vimrc that enables all + the features of `vim-coffee-script`: + + + ```vim + set nocompatible + filetype off + + set rtp+=~/.vim/bundle/vundle/ + call vundle#rc() + + Bundle 'kchmck/vim-coffee-script' + + syntax enable + filetype plugin indent on + ``` + + If you're adding Vundle to a built-up vimrc, just make sure all these calls + are in there and that they occur in this order. + +3. Open vim and run `:BundleInstall`. + +To update, open vim and run `:BundleInstall!` (notice the bang!) + +## Install from a Zip File + +1. Download the latest zip file from [vim.org][zip]. + +2. Extract the archive into `~/.vim/`: + + unzip -od ~/.vim/ ARCHIVE.zip + + This should create the files `~/.vim/autoload/coffee.vim`, + `~/.vim/compiler/coffee.vim`, etc. + +You can update the plugin using the same steps. + +[zip]: http://www.vim.org/scripts/script.php?script_id=3590 + +## Compile to JavaScript + +A `coffee` wrapper for use with `:make` is enabled automatically for coffee +files if no other compiler is loaded. To enable it manually, run + + :compiler coffee + +The `:make` command is then configured to use the `coffee` compiler and +recognize its errors. I've included a quick reference here but be sure to check +out [`:help :make`][make] for a full reference of the command. + + ![make](http://i.imgur.com/scUXmxR.png) + + ![make Result](http://i.imgur.com/eGIjEdn.png) + +[make]: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:make_makeprg + +Consider the full signature of a `:make` call as + + :[silent] make[!] [COFFEE-OPTIONS]... + +By default `:make` shows all compiler output and jumps to the first line +reported as an error. Compiler output can be hidden with a leading `:silent`: + + :silent make + +Line-jumping can be turned off by adding a bang: + + :make! + +`COFFEE-OPTIONS` given to `:make` are passed along to `coffee` (see also +[`coffee_make_options`](#coffee_make_options)): + + :make --bare --output /some/dir + +See the [full table of options](http://coffeescript.org/#usage) for a +list of all the options that `coffee` recognizes. + +*Configuration*: [`coffee_compiler`](#coffee_compiler), +[`coffee_make_options`](#coffee_make_options) + +#### The quickfix window + +Compiler errors are added to the [quickfix] list by `:make`, but the quickfix +window isn't automatically shown. The [`:cwindow`][cwindow] command will pop up +the quickfix window if there are any errors: + + :make + :cwindow + +This is usually the desired behavior, so you may want to add an autocmd to your +vimrc to do this automatically: + + autocmd QuickFixCmdPost * nested cwindow | redraw! + +The `redraw!` command is needed to fix a redrawing quirk in terminal vim, but +can removed for gVim. + +[quickfix]: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix +[cwindow]: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#:cwindow + +#### Recompile on write + +To recompile a file when it's written, add a `BufWritePost` autocmd to your +vimrc: + + autocmd BufWritePost *.coffee silent make! + +#### Cake and Cakefiles + +A `cake` compiler is also available with the call + + :compiler cake + +You can then use `:make` as above to run your Cakefile and capture any `coffee` +errors: + + :silent make build + +It runs within the current directory, so make sure you're in the directory of +your Cakefile before calling it. + +*Configuration*: [`coffee_cake`](#coffee_cake), +[`coffee_cake_options`](#coffee_cake_options) + +## CoffeeCompile: Compile CoffeeScript Snippets + +CoffeeCompile shows how the current file or a snippet of CoffeeScript is +compiled to JavaScript. + + :[RANGE] CoffeeCompile [vert[ical]] [WINDOW-SIZE] + +Calling `:CoffeeCompile` without a range compiles the whole file: + + ![CoffeeCompile](http://i.imgur.com/0zFG0l0.png) + + ![CoffeeCompile Result](http://i.imgur.com/bpiAxaa.png) + +Calling it with a range, like in visual mode, compiles only the selected snippet +of CoffeeScript: + + ![CoffeeCompile Snippet](http://i.imgur.com/x3OT3Ay.png) + + ![Compiled Snippet](http://i.imgur.com/J02j4T8.png) + +Each file gets its own CoffeeCompile buffer, and the same buffer is used for all +future calls of `:CoffeeCompile` on that file. It can be quickly closed by +hitting `q` in normal mode. + +Using `vert` opens the CoffeeCompile buffer vertically instead of horizontally +(see also [`coffee_compile_vert`](#coffee_compile_vert)): + + :CoffeeCompile vert + +By default the CoffeeCompile buffer splits the source buffer in half, but this +can be overridden by passing in a `WINDOW-SIZE`: + + :CoffeeCompile 4 + +*Configuration*: [`coffee_compiler`](#coffee_compiler`), +[`coffee_compile_vert`](#coffee_compile_vert) + +#### Quick syntax checking + +If compiling a snippet results in a compiler error, CoffeeCompile adds that +error to the [quickfix] list. + +[quickfix]: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix + + ![Syntax Checking](http://i.imgur.com/RC8accF.png) + + ![Syntax Checking Result](http://i.imgur.com/gi1ON75.png) + +You can use this to quickly check the syntax of a snippet. + +## CoffeeWatch: Live Preview Compiling + +CoffeeWatch emulates using the Try CoffeeScript preview box on the [CoffeeScript +homepage][CoffeeScript]. + + ![CoffeeWatch](http://i.imgur.com/TRHdIMG.png) + + ![CoffeeWatch Result](http://i.imgur.com/rJbOeeS.png) + +CoffeeWatch takes the same options as CoffeeCompile: + + :CoffeeWatch [vert[ical]] [WINDOW-SIZE] + +After a source buffer is watched, leaving insert mode or saving the file fires +off a recompile of the CoffeeScript: + + ![Insert Mode](http://i.imgur.com/SBVcf4k.png) + + ![Recompile](http://i.imgur.com/pbPMog7.png) + +You can force recompilation by calling `:CoffeeWatch`. + +To get synchronized scrolling of the source buffer and CoffeeWatch buffer, set +[`'scrollbind'`](http://vimdoc.sourceforge.net/htmldoc/options.html#'scrollbind') +on each: + + :setl scrollbind + +*Configuration*: [`coffee_compiler`](#coffee_compiler), +[`coffee_watch_vert`](#coffee_watch_vert) + +## CoffeeRun: Run CoffeeScript Snippets + +CoffeeRun compiles the current file or selected snippet and runs the resulting +JavaScript. + + ![CoffeeRun](http://i.imgur.com/YSkHUuQ.png) + + ![CoffeeRun Output](http://i.imgur.com/wZQbggN.png) + +The command has two forms: + + :CoffeeRun [PROGRAM-OPTIONS]... + +This form applies when no `RANGE` is given or when the given range is `1,$` +(first line to last line). It allows passing `PROGRAM-OPTIONS` to your compiled +program. The filename is passed directly to `coffee` so you must save the file +for your changes to take effect. + + :RANGE CoffeeRun [COFFEE-OPTIONS]... + +This form applies with all other ranges. It compiles and runs the lines within +the given `RANGE` and any extra `COFFEE-OPTIONS` are passed to `coffee`. + +*Configuration*: [`coffee_compiler`](#coffee_compiler), +[`coffee_run_vert`](#coffee_run_vert) + +## CoffeeLint: Lint your CoffeeScript + +CoffeeLint runs [coffeelint](http://www.coffeelint.org/) (version 0.5.7 or later +required) on the current file and adds any issues to the [quickfix] list. + + ![CoffeeLint](http://i.imgur.com/UN8Nr5N.png) + + ![CoffeeLint Result](http://i.imgur.com/9hSIj3W.png) + + :[RANGE] CoffeeLint[!] [COFFEELINT-OPTIONS]... [ | cwindow] + +If a `RANGE` is given, only those lines are piped to `coffeelint`. Options given +in `COFFEELINT-OPTIONS` are passed to `coffeelint` (see also +[`coffee_lint_options`](#coffee_lint_options)): + + :CoffeeLint -f lint.json + +It behaves very similar to `:make`, described [above](#compile-to-javascript). + + :CoffeeLint! | cwindow + +*Configuration*: [`coffee_linter`](#coffee_linter), +[`coffee_lint_options`](#coffee_lint_options) + +## Literate CoffeeScript + +Literate CoffeeScript syntax and indent support is provided by +[vim-literate-coffeescript]. The `Coffee` commands detect when they're running +on a litcoffee file and pass the `--literate` flag to their respective tools, +but at this time the commands are not automatically loaded when a litcoffee file +is opened. + +[vim-literate-coffeescript]: https://github.com/mintplant/vim-literate-coffeescript + +To load them, run + + runtime ftplugin/coffee.vim + +while inside a litcoffee buffer. To do this automatically, add + + autocmd FileType litcoffee runtime ftplugin/coffee.vim + +to your vimrc. + +## CoffeeScript in HTML + +CoffeeScript is highlighted and indented within + +```html + +``` + +blocks in html files. + +## CoffeeScript in Haml + +CoffeeScript is highlighted within the `:coffeescript` filter in haml files: + +```haml +:coffeescript + console.log "hullo" +``` + +At this time, coffee indenting doesn't work in these blocks. + +## Custom Autocmds + +You can [define commands][autocmd-explain] to be ran automatically on these +custom events. + +In all cases, the name of the command running the event (`CoffeeCompile`, +`CoffeeWatch`, or `CoffeeRun`) is matched by the [`{pat}`][autocmd] argument. +You can match all commands with a `*` or only specific commands by separating +them with a comma: `CoffeeCompile,CoffeeWatch`. + +[autocmd-explain]: http://vimdoc.sourceforge.net/htmldoc/usr_40.html#40.3 +[autocmd]: http://vimdoc.sourceforge.net/htmldoc/autocmd.html#:autocmd + +#### CoffeeBufNew + +CoffeeBufNew is ran when a new scratch buffer is created. It's called from the +new buffer, so it can be used to do additional set up. + +```vim +augroup CoffeeBufNew + autocmd User * set wrap +augroup END +``` + +*Used By*: CoffeeCompile, CoffeeWatch, CoffeeRun + +#### CoffeeBufUpdate + +CoffeeBufUpdate is ran when a scratch buffer is updated with output from +`coffee`. It's called from the scratch buffer, so it can be used to alter the +compiled output. + +```vim +" Switch back to the source buffer after updating. +augroup CoffeeBufUpdate + autocmd User CoffeeCompile,CoffeeRun exec bufwinnr(b:coffee_src_buf) 'wincmd w' +augroup END +``` + +For example, to strip off the "Generated by" comment on the first line, put this +in your vimrc: + +```vim +function! s:RemoveGeneratedBy() + " If there was an error compiling, there's no comment to remove. + if v:shell_error + return + endif + + " Save cursor position. + let pos = getpos('.') + + " Remove first line. + set modifiable + 1 delete _ + set nomodifiable + + " Restore cursor position. + call setpos('.', pos) +endfunction + +augroup CoffeeBufUpdate + autocmd User CoffeeCompile,CoffeeWatch call s:RemoveGeneratedBy() +augroup END +``` + +*Used By*: CoffeeCompile, CoffeeWatch, CoffeeRun + +## Configuration Variables + +This is the full list of configuration variables available, with example +settings and default values. Use these in your vimrc to control the default +behavior. + +#### coffee\_indent\_keep\_current + +By default, the indent function matches the indent of the previous line if it +doesn't find a reason to indent or outdent. To change this behavior so it +instead keeps the [current indent of the cursor][98], use + + let coffee_indent_keep_current = 1 + +[98]: https://github.com/kchmck/vim-coffee-script/pull/98 + +*Default*: `unlet coffee_indent_keep_current` + +Note that if you change this after a coffee file has been loaded, you'll have to +reload the indent script for the change to take effect: + + unlet b:did_indent | runtime indent/coffee.vim + +#### coffee\_compiler + +Path to the `coffee` executable used by the `Coffee` commands: + + let coffee_compiler = '/usr/bin/coffee' + +*Default*: `'coffee'` (search `$PATH` for executable) + +#### coffee\_make\_options + +Options to pass to `coffee` with `:make`: + + let coffee_make_options = '--bare' + +*Default*: `''` (nothing) + +Note that `coffee_make_options` is embedded into `'makeprg'`, so `:compiler +coffee` must be ran after changing `coffee_make_options` for the changes to take +effect. + +#### coffee\_cake + +Path to the `cake` executable: + + let coffee_cake = '/opt/bin/cake' + +*Default*: `'cake'` (search `$PATH` for executable) + +#### coffee\_cake\_options + +Options to pass to `cake` with `:make`: + + let coffee_cake_options = 'build' + +*Default*: `''` (nothing) + +#### coffee\_linter + +Path to the `coffeelint` executable: + + let coffee_linter = '/opt/bin/coffeelint' + +*Default*: `'coffeelint'` (search `$PATH` for executable) + +#### coffee\_lint\_options + +Options to pass to `coffeelint`: + + let coffee_lint_options = '-f lint.json' + +*Default*: `''` (nothing) + +#### coffee\_compile\_vert + +Open the CoffeeCompile buffer with a vertical split instead of a horizontal +one: + + let coffee_compile_vert = 1 + +*Default*: `unlet coffee_compile_vert` + +#### coffee\_watch\_vert + +Open the CoffeeWatch buffer with a vertical split instead of a horizontal +one: + + let coffee_watch_vert = 1 + +*Default*: `unlet coffee_watch_vert` + +#### coffee\_run\_vert + +Open the CoffeeRun buffer with a vertical split instead of a horizontal +one: + + let coffee_run_vert = 1 + +*Default*: `unlet coffee_run_vert` + +## Configure Syntax Highlighting + +Add these lines to your vimrc to disable the relevant syntax group. + +#### Disable trailing whitespace error + +Trailing whitespace is highlighted as an error by default. This can be disabled +with: + + hi link coffeeSpaceError NONE + +#### Disable trailing semicolon error + +Trailing semicolons are considered an error (for help transitioning from +JavaScript.) This can be disabled with: + + hi link coffeeSemicolonError NONE + +#### Disable reserved words error + +Reserved words like `function` and `var` are highlighted as an error where +they're not allowed in CoffeeScript. This can be disabled with: + + hi link coffeeReservedError NONE + +## Tune Vim for CoffeeScript + +Changing these core settings can make vim more CoffeeScript friendly. + +#### Fold by indentation + +Folding by indentation works well for CoffeeScript functions and classes: + + ![Folding](http://i.imgur.com/gDgUBdO.png) + +To fold by indentation in CoffeeScript files, add this line to your vimrc: + + autocmd BufNewFile,BufReadPost *.coffee setl foldmethod=indent nofoldenable + +With this, folding is disabled by default but can be quickly toggled per-file +by hitting `zi`. To enable folding by default, remove `nofoldenable`: + + autocmd BufNewFile,BufReadPost *.coffee setl foldmethod=indent + +#### Two-space indentation + +To get standard two-space indentation in CoffeeScript files, add this line to +your vimrc: + + autocmd BufNewFile,BufReadPost *.coffee setl shiftwidth=2 expandtab diff --git a/Thanks.md b/Thanks.md new file mode 100644 index 0000000..8ddcf23 --- /dev/null +++ b/Thanks.md @@ -0,0 +1,44 @@ +Thanks to all bug reporters, and special thanks to those who have contributed +code: + + Brian Egan (brianegan): + Initial compiling support + + Ches Martin (ches): + Initial vim docs + + Chris Hoffman (cehoffman): + Add new keywoards from, to, and do + Highlight the - in negative integers + Add here regex highlighting, increase fold level for here docs + + David Wilhelm (bigfish): + CoffeeRun command + + Jay Adkisson (jayferd): + Support for eco templates + + Karl Guertin (grayrest) + Cakefiles are coffeescript + + Maciej Konieczny (narfdotpl): + Fix funny typo + + Matt Sacks (mattsa): + Javascript omni-completion + coffee_compile_vert option + + Nick Stenning (nickstenning): + Fold by indentation for coffeescript + + Simon Lipp (sloonz): + Trailing spaces are not error on lines containing only spaces + + Stéphan Kochen (stephank): + Initial HTML CoffeeScript highlighting + + Sven Felix Oberquelle (Svelix): + Haml CoffeeScript highlighting + + Wei Dai (clvv): + Fix the use of Vim built-in make command. diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..3d4ffaa --- /dev/null +++ b/Todo.md @@ -0,0 +1 @@ +- Don't highlight bad operator combinations diff --git a/after/indent/html.vim b/after/indent/html.vim new file mode 100644 index 0000000..e637708 --- /dev/null +++ b/after/indent/html.vim @@ -0,0 +1,33 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +" Load the coffee and html indent functions. +silent! unlet b:did_indent +runtime indent/coffee.vim +let s:coffeeIndentExpr = &l:indentexpr + +" Load html last so it can overwrite coffee settings. +silent! unlet b:did_indent +runtime indent/html.vim +let s:htmlIndentExpr = &l:indentexpr + +" Inject our wrapper indent function. +setlocal indentexpr=GetCoffeeHtmlIndent(v:lnum) + +function! GetCoffeeHtmlIndent(curlinenum) + " See if we're inside a coffeescript block. + let scriptlnum = searchpair('', 'bWn') + let prevlnum = prevnonblank(a:curlinenum) + + " If we're in the script block and the previous line isn't the script tag + " itself, use coffee indenting. + if scriptlnum && scriptlnum != prevlnum + exec 'return ' s:coffeeIndentExpr + endif + + " Otherwise use html indenting. + exec 'return ' s:htmlIndentExpr +endfunction diff --git a/after/syntax/haml.vim b/after/syntax/haml.vim new file mode 100644 index 0000000..4c517eb --- /dev/null +++ b/after/syntax/haml.vim @@ -0,0 +1,13 @@ +" Language: CoffeeScript +" Maintainer: Sven Felix Oberquelle +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +" Inherit coffee from html so coffeeComment isn't redefined and given higher +" priority than hamlInterpolation. +syn cluster hamlCoffeescript contains=@htmlCoffeeScript +syn region hamlCoffeescriptFilter matchgroup=hamlFilter +\ start="^\z(\s*\):coffee\z(script\)\?\s*$" +\ end="^\%(\z1 \| *$\)\@!" +\ contains=@hamlCoffeeScript,hamlInterpolation +\ keepend diff --git a/after/syntax/html.vim b/after/syntax/html.vim new file mode 100644 index 0000000..9e2eb3a --- /dev/null +++ b/after/syntax/html.vim @@ -0,0 +1,11 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +" Syntax highlighting for text/coffeescript script tags +syn include @htmlCoffeeScript syntax/coffee.vim +syn region coffeeScript start=##me=s-1 keepend +\ contains=@htmlCoffeeScript,htmlScriptTag,@htmlPreproc +\ containedin=htmlHead diff --git a/autoload/coffee.vim b/autoload/coffee.vim new file mode 100644 index 0000000..04d5efb --- /dev/null +++ b/autoload/coffee.vim @@ -0,0 +1,54 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +" Set up some common global/buffer variables. +function! coffee#CoffeeSetUpVariables() + " Path to coffee executable + if !exists('g:coffee_compiler') + let g:coffee_compiler = 'coffee' + endif + + " Options passed to coffee with make + if !exists('g:coffee_make_options') + let g:coffee_make_options = '' + endif + + " Path to cake executable + if !exists('g:coffee_cake') + let g:coffee_cake = 'cake' + endif + + " Extra options passed to cake + if !exists('g:coffee_cake_options') + let g:coffee_cake_options = '' + endif + + " Path to coffeelint executable + if !exists('g:coffee_linter') + let g:coffee_linter = 'coffeelint' + endif + + " Options passed to CoffeeLint + if !exists('g:coffee_lint_options') + let g:coffee_lint_options = '' + endif + + " Pass the litcoffee flag to tools in this buffer if a litcoffee file is open. + " Let the variable be overwritten so it can be updated if a different filetype + " is set. + if &filetype == 'litcoffee' + let b:coffee_litcoffee = '--literate' + else + let b:coffee_litcoffee = '' + endif +endfunction + +function! coffee#CoffeeSetUpErrorFormat() + CompilerSet errorformat=Error:\ In\ %f\\,\ %m\ on\ line\ %l, + \Error:\ In\ %f\\,\ Parse\ error\ on\ line\ %l:\ %m, + \SyntaxError:\ In\ %f\\,\ %m, + \%f:%l:%c:\ error:\ %m, + \%-G%.%# +endfunction diff --git a/compiler/cake.vim b/compiler/cake.vim new file mode 100644 index 0000000..0a3c703 --- /dev/null +++ b/compiler/cake.vim @@ -0,0 +1,15 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +if exists('current_compiler') + finish +endif + +let current_compiler = 'cake' +call coffee#CoffeeSetUpVariables() + +exec 'CompilerSet makeprg=' . escape(g:coffee_cake . ' ' . +\ g:coffee_cake_options . ' $*', ' ') +call coffee#CoffeeSetUpErrorFormat() diff --git a/compiler/coffee.vim b/compiler/coffee.vim new file mode 100644 index 0000000..9a91d35 --- /dev/null +++ b/compiler/coffee.vim @@ -0,0 +1,82 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +" All this is needed to support compiling filenames with spaces, quotes, and +" such. The filename is escaped and embedded into the `makeprg` setting. +" +" Because of this, `makeprg` must be updated on every file rename. And because +" of that, `CompilerSet` can't be used because it doesn't exist when the +" rename autocmd is ran. So, we have to do some checks to see whether `compiler` +" was called locally or globally, and respect that in the rest of the script. + +if exists('current_compiler') + finish +endif + +let current_compiler = 'coffee' +call coffee#CoffeeSetUpVariables() + +" Pattern to check if coffee is the compiler +let s:pat = '^' . current_compiler + +" Get a `makeprg` for the current filename. +function! s:GetMakePrg() + return g:coffee_compiler . + \ ' -c' . + \ ' ' . b:coffee_litcoffee . + \ ' ' . g:coffee_make_options . + \ ' $*' . + \ ' ' . fnameescape(expand('%')) +endfunction + +" Set `makeprg` and return 1 if coffee is still the compiler, else return 0. +function! s:SetMakePrg() + if &l:makeprg =~ s:pat + let &l:makeprg = s:GetMakePrg() + elseif &g:makeprg =~ s:pat + let &g:makeprg = s:GetMakePrg() + else + return 0 + endif + + return 1 +endfunction + +" Set a dummy compiler so we can check whether to set locally or globally. +exec 'CompilerSet makeprg=' . current_compiler +" Then actually set the compiler. +call s:SetMakePrg() +call coffee#CoffeeSetUpErrorFormat() + +function! s:CoffeeMakeDeprecated(bang, args) + echoerr 'CoffeeMake is deprecated! Please use :make instead, its behavior ' . + \ 'is identical.' + sleep 5 + exec 'make' . a:bang a:args +endfunction + +" Compile the current file. +command! -bang -bar -nargs=* CoffeeMake +\ call s:CoffeeMakeDeprecated(, ) + +" Set `makeprg` on rename since we embed the filename in the setting. +augroup CoffeeUpdateMakePrg + autocmd! + + " Update `makeprg` if coffee is still the compiler, else stop running this + " function. + function! s:UpdateMakePrg() + if !s:SetMakePrg() + autocmd! CoffeeUpdateMakePrg + endif + endfunction + + " Set autocmd locally if compiler was set locally. + if &l:makeprg =~ s:pat + autocmd BufWritePre,BufFilePost call s:UpdateMakePrg() + else + autocmd BufWritePre,BufFilePost call s:UpdateMakePrg() + endif +augroup END diff --git a/doc/coffee-script.txt b/doc/coffee-script.txt new file mode 100644 index 0000000..1b43cf3 --- /dev/null +++ b/doc/coffee-script.txt @@ -0,0 +1,4 @@ +Please see the project readme for up-to-date docs: +https://github.com/kchmck/vim-coffee-script + + vim:tw=78:ts=8:ft=help:norl: diff --git a/ftdetect/coffee.vim b/ftdetect/coffee.vim new file mode 100644 index 0000000..5056929 --- /dev/null +++ b/ftdetect/coffee.vim @@ -0,0 +1,17 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +autocmd BufNewFile,BufRead *.coffee set filetype=coffee +autocmd BufNewFile,BufRead *Cakefile set filetype=coffee +autocmd BufNewFile,BufRead *.coffeekup,*.ck set filetype=coffee +autocmd BufNewFile,BufRead *._coffee set filetype=coffee + +function! s:DetectCoffee() + if getline(1) =~ '^#!.*\' + set filetype=coffee + endif +endfunction + +autocmd BufNewFile,BufRead * call s:DetectCoffee() diff --git a/ftplugin/coffee.vim b/ftplugin/coffee.vim new file mode 100644 index 0000000..c44fe97 --- /dev/null +++ b/ftplugin/coffee.vim @@ -0,0 +1,404 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +if exists('b:did_ftplugin') + finish +endif + +let b:did_ftplugin = 1 +call coffee#CoffeeSetUpVariables() + +setlocal formatoptions-=t formatoptions+=croql +setlocal comments=:# commentstring=#\ %s +setlocal omnifunc=javascriptcomplete#CompleteJS + +" Create custom augroups. +augroup CoffeeBufUpdate | augroup END +augroup CoffeeBufNew | augroup END + +" Enable coffee compiler if a compiler isn't set already. +if !len(&l:makeprg) + compiler coffee +endif + +" Switch to the window for buf. +function! s:SwitchWindow(buf) + exec bufwinnr(a:buf) 'wincmd w' +endfunction + +" Create a new scratch buffer and return the bufnr of it. After the function +" returns, vim remains in the scratch buffer so more set up can be done. +function! s:ScratchBufBuild(src, vert, size) + if a:size <= 0 + if a:vert + let size = winwidth(bufwinnr(a:src)) / 2 + else + let size = winheight(bufwinnr(a:src)) / 2 + endif + endif + + if a:vert + vertical belowright new + exec 'vertical resize' size + else + belowright new + exec 'resize' size + endif + + setlocal bufhidden=wipe buftype=nofile nobuflisted noswapfile nomodifiable + nnoremap q :hide + + return bufnr('%') +endfunction + +" Replace buffer contents with text and delete the last empty line. +function! s:ScratchBufUpdate(buf, text) + " Move to the scratch buffer. + call s:SwitchWindow(a:buf) + + " Double check we're in the scratch buffer before overwriting. + if bufnr('%') != a:buf + throw 'unable to change to scratch buffer' + endif + + setlocal modifiable + silent exec '% delete _' + silent put! =a:text + silent exec '$ delete _' + setlocal nomodifiable +endfunction + +" Parse the output of coffee into a qflist entry for src buffer. +function! s:ParseCoffeeError(output, src, startline) + " Coffee error is always on first line? + let match = matchlist(a:output, + \ '^\(\f\+\|\[stdin\]\):\(\d\):\(\d\): error: \(.\{-}\)' . "\n") + + if !len(match) + return + endif + + " Consider the line number from coffee as relative and add it to the beginning + " line number of the range the command was called on, then subtract one for + " zero-based relativity. + call setqflist([{'bufnr': a:src, 'lnum': a:startline + str2nr(match[2]) - 1, + \ 'type': 'E', 'col': str2nr(match[3]), 'text': match[4]}], 'r') +endfunction + +" Reset source buffer variables. +function! s:CoffeeCompileResetVars() + " Variables defined in source buffer: + " b:coffee_compile_buf: bufnr of output buffer + " Variables defined in output buffer: + " b:coffee_src_buf: bufnr of source buffer + " b:coffee_compile_pos: previous cursor position in output buffer + + let b:coffee_compile_buf = -1 +endfunction + +function! s:CoffeeWatchResetVars() + " Variables defined in source buffer: + " b:coffee_watch_buf: bufnr of output buffer + " Variables defined in output buffer: + " b:coffee_src_buf: bufnr of source buffer + " b:coffee_watch_pos: previous cursor position in output buffer + + let b:coffee_watch_buf = -1 +endfunction + +function! s:CoffeeRunResetVars() + " Variables defined in CoffeeRun source buffer: + " b:coffee_run_buf: bufnr of output buffer + " Variables defined in CoffeeRun output buffer: + " b:coffee_src_buf: bufnr of source buffer + " b:coffee_run_pos: previous cursor position in output buffer + + let b:coffee_run_buf = -1 +endfunction + +" Clean things up in the source buffers. +function! s:CoffeeCompileClose() + " Switch to the source buffer if not already in it. + silent! call s:SwitchWindow(b:coffee_src_buf) + call s:CoffeeCompileResetVars() +endfunction + +function! s:CoffeeWatchClose() + silent! call s:SwitchWindow(b:coffee_src_buf) + silent! autocmd! CoffeeAuWatch * + call s:CoffeeWatchResetVars() +endfunction + +function! s:CoffeeRunClose() + silent! call s:SwitchWindow(b:coffee_src_buf) + call s:CoffeeRunResetVars() +endfunction + +" Compile the lines between startline and endline and put the result into buf. +function! s:CoffeeCompileToBuf(buf, startline, endline) + let src = bufnr('%') + let input = join(getline(a:startline, a:endline), "\n") + + " Coffee doesn't like empty input. + if !len(input) + " Function should still return within output buffer. + call s:SwitchWindow(a:buf) + return + endif + + " Pipe lines into coffee. + let output = system(g:coffee_compiler . + \ ' -scb' . + \ ' ' . b:coffee_litcoffee . + \ ' 2>&1', input) + + " Paste output into output buffer. + call s:ScratchBufUpdate(a:buf, output) + + " Highlight as JavaScript if there were no compile errors. + if v:shell_error + call s:ParseCoffeeError(output, src, a:startline) + setlocal filetype= + else + " Clear the quickfix list. + call setqflist([], 'r') + setlocal filetype=javascript + endif +endfunction + +" Peek at compiled CoffeeScript in a scratch buffer. We handle ranges like this +" to prevent the cursor from being moved (and its position saved) before the +" function is called. +function! s:CoffeeCompile(startline, endline, args) + if a:args =~ '\' + echoerr 'CoffeeCompile watch is deprecated! Please use CoffeeWatch instead' + sleep 5 + call s:CoffeeWatch(a:args) + return + endif + + " Switch to the source buffer if not already in it. + silent! call s:SwitchWindow(b:coffee_src_buf) + + " Bail if not in source buffer. + if !exists('b:coffee_compile_buf') + return + endif + + " Build the output buffer if it doesn't exist. + if bufwinnr(b:coffee_compile_buf) == -1 + let src = bufnr('%') + + let vert = exists('g:coffee_compile_vert') || a:args =~ '\' + let size = str2nr(matchstr(a:args, '\<\d\+\>')) + + " Build the output buffer and save the source bufnr. + let buf = s:ScratchBufBuild(src, vert, size) + let b:coffee_src_buf = src + + " Set the buffer name. + exec 'silent! file [CoffeeCompile ' . src . ']' + + " Clean up the source buffer when the output buffer is closed. + autocmd BufWipeout call s:CoffeeCompileClose() + " Save the cursor when leaving the output buffer. + autocmd BufLeave let b:coffee_compile_pos = getpos('.') + + " Run user-defined commands on new buffer. + silent doautocmd CoffeeBufNew User CoffeeCompile + + " Switch back to the source buffer and save the output bufnr. This also + " triggers BufLeave above. + call s:SwitchWindow(src) + let b:coffee_compile_buf = buf + endif + + " Fill the scratch buffer. + call s:CoffeeCompileToBuf(b:coffee_compile_buf, a:startline, a:endline) + " Reset cursor to previous position. + call setpos('.', b:coffee_compile_pos) + + " Run any user-defined commands on the scratch buffer. + silent doautocmd CoffeeBufUpdate User CoffeeCompile +endfunction + +" Update the scratch buffer and switch back to the source buffer. +function! s:CoffeeWatchUpdate() + call s:CoffeeCompileToBuf(b:coffee_watch_buf, 1, '$') + call setpos('.', b:coffee_watch_pos) + silent doautocmd CoffeeBufUpdate User CoffeeWatch + call s:SwitchWindow(b:coffee_src_buf) +endfunction + +" Continually compile a source buffer. +function! s:CoffeeWatch(args) + silent! call s:SwitchWindow(b:coffee_src_buf) + + if !exists('b:coffee_watch_buf') + return + endif + + if bufwinnr(b:coffee_watch_buf) == -1 + let src = bufnr('%') + + let vert = exists('g:coffee_watch_vert') || a:args =~ '\' + let size = str2nr(matchstr(a:args, '\<\d\+\>')) + + let buf = s:ScratchBufBuild(src, vert, size) + let b:coffee_src_buf = src + + exec 'silent! file [CoffeeWatch ' . src . ']' + + autocmd BufWipeout call s:CoffeeWatchClose() + autocmd BufLeave let b:coffee_watch_pos = getpos('.') + + silent doautocmd CoffeeBufNew User CoffeeWatch + + call s:SwitchWindow(src) + let b:coffee_watch_buf = buf + endif + + " Make sure only one watch autocmd is defined on this buffer. + silent! autocmd! CoffeeAuWatch * + + augroup CoffeeAuWatch + autocmd InsertLeave call s:CoffeeWatchUpdate() + autocmd BufWritePost call s:CoffeeWatchUpdate() + augroup END + + call s:CoffeeWatchUpdate() +endfunction + +" Run a snippet of CoffeeScript between startline and endline. +function! s:CoffeeRun(startline, endline, args) + silent! call s:SwitchWindow(b:coffee_src_buf) + + if !exists('b:coffee_run_buf') + return + endif + + if bufwinnr(b:coffee_run_buf) == -1 + let src = bufnr('%') + + let buf = s:ScratchBufBuild(src, exists('g:coffee_run_vert'), 0) + let b:coffee_src_buf = src + + exec 'silent! file [CoffeeRun ' . src . ']' + + autocmd BufWipeout call s:CoffeeRunClose() + autocmd BufLeave let b:coffee_run_pos = getpos('.') + + silent doautocmd CoffeeBufNew User CoffeeRun + + call s:SwitchWindow(src) + let b:coffee_run_buf = buf + endif + + if a:startline == 1 && a:endline == line('$') + let output = system(g:coffee_compiler . + \ ' ' . b:coffee_litcoffee . + \ ' ' . fnameescape(expand('%')) . + \ ' ' . a:args) + else + let input = join(getline(a:startline, a:endline), "\n") + + if !len(input) + return + endif + + let output = system(g:coffee_compiler . + \ ' -s' . + \ ' ' . b:coffee_litcoffee . + \ ' ' . a:args, input) + endif + + call s:ScratchBufUpdate(b:coffee_run_buf, output) + call setpos('.', b:coffee_run_pos) + + silent doautocmd CoffeeBufUpdate User CoffeeRun +endfunction + +" Run coffeelint on a file, and add any errors between startline and endline +" to the quickfix list. +function! s:CoffeeLint(startline, endline, bang, args) + let input = join(getline(a:startline, a:endline), "\n") + + if !len(input) + return + endif + + let output = system(g:coffee_linter . + \ ' -s --csv' . + \ ' ' . b:coffee_litcoffee . + \ ' ' . g:coffee_lint_options . + \ ' ' . a:args . + \ ' 2>&1', input) + + " Convert output into an array and strip off the csv header. + let lines = split(output, "\n")[1:] + let buf = bufnr('%') + let qflist = [] + + for line in lines + let match = matchlist(line, '^stdin,\(\d\+\),\d*,\(error\|warn\),\(.\+\)$') + + " Ignore unmatched lines. + if !len(match) + continue + endif + + " The 'type' will result in either 'E' or 'W'. + call add(qflist, {'bufnr': buf, 'lnum': a:startline + str2nr(match[1]) - 1, + \ 'type': toupper(match[2][0]), 'text': match[3]}) + endfor + + " Replace the quicklist with our items. + call setqflist(qflist, 'r') + + " If not given a bang, jump to first error. + if !len(a:bang) + silent! cc 1 + endif +endfunction + +" Complete arguments for Coffee* commands. +function! s:CoffeeComplete(cmd, cmdline, cursor) + let args = ['vertical'] + + " If no partial command, return all possibilities. + if !len(a:cmd) + return args + endif + + let pat = '^' . a:cmd + + for arg in args + if arg =~ pat + return [arg] + endif + endfor +endfunction + +" Set initial state variables if they don't exist +if !exists('b:coffee_compile_buf') + call s:CoffeeCompileResetVars() +endif + +if !exists('b:coffee_watch_buf') + call s:CoffeeWatchResetVars() +endif + +if !exists('b:coffee_run_buf') + call s:CoffeeRunResetVars() +endif + +command! -range=% -bar -nargs=* -complete=customlist,s:CoffeeComplete +\ CoffeeCompile call s:CoffeeCompile(, , ) +command! -bar -nargs=* -complete=customlist,s:CoffeeComplete +\ CoffeeWatch call s:CoffeeWatch() +command! -range=% -bar -nargs=* CoffeeRun +\ call s:CoffeeRun(, , ) +command! -range=% -bang -bar -nargs=* CoffeeLint +\ call s:CoffeeLint(, , , ) diff --git a/indent/coffee.vim b/indent/coffee.vim new file mode 100644 index 0000000..7bd82e3 --- /dev/null +++ b/indent/coffee.vim @@ -0,0 +1,428 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +if exists('b:did_indent') + finish +endif + +let b:did_indent = 1 + +setlocal autoindent +setlocal indentexpr=GetCoffeeIndent(v:lnum) +" Make sure GetCoffeeIndent is run when these are typed so they can be +" indented or outdented. +setlocal indentkeys+=0],0),0.,=else,=when,=catch,=finally + +" If no indenting or outdenting is needed, either keep the indent of the cursor +" (use autoindent) or match the indent of the previous line. +if exists('g:coffee_indent_keep_current') + let s:DEFAULT_LEVEL = '-1' +else + let s:DEFAULT_LEVEL = 'indent(prevnlnum)' +endif + +" Only define the function once. +if exists('*GetCoffeeIndent') + finish +endif + +" Keywords that begin a block +let s:BEGIN_BLOCK_KEYWORD = '\C^\%(if\|unless\|else\|for\|while\|until\|' +\ . 'loop\|switch\|when\|try\|catch\|finally\|' +\ . 'class\)\>\%(\s*:\)\@!' + +" An expression that uses the result of a statement +let s:COMPOUND_EXPRESSION = '\C\%([^-]-\|[^+]+\|[^/]/\|[:=*%&|^<>]\)\s*' +\ . '\%(if\|unless\|for\|while\|until\|loop\|switch\|' +\ . 'try\|class\)\>' + +" Combine the two above +let s:BEGIN_BLOCK = s:BEGIN_BLOCK_KEYWORD . '\|' . s:COMPOUND_EXPRESSION + +" Operators that begin a block but also count as a continuation +let s:BEGIN_BLOCK_OP = '[([{:=]$' + +" Begins a function block +let s:FUNCTION = '[-=]>$' + +" Operators that continue a line onto the next line +let s:CONTINUATION_OP = '\C\%(\<\%(is\|isnt\|and\|or\)\>\|' +\ . '[^-]-\|[^+]+\|[^-=]>\|[^.]\.\|[<*/%&|^,]\)$' + +" Ancestor operators that prevent continuation indenting +let s:CONTINUATION = s:CONTINUATION_OP . '\|' . s:BEGIN_BLOCK_OP + +" A closing bracket by itself on a line followed by a continuation +let s:BRACKET_CONTINUATION = '^\s*[}\])]\s*' . s:CONTINUATION_OP + +" A continuation dot access +let s:DOT_ACCESS = '^\.' + +" Keywords that break out of a block +let s:BREAK_BLOCK_OP = '\C^\%(return\|break\|continue\|throw\)\>' + +" A condition attached to the end of a statement +let s:POSTFIX_CONDITION = '\C\S\s\+\zs\<\%(if\|unless\|when\|while\|until\)\>' + +" A then contained in brackets +let s:CONTAINED_THEN = '\C[(\[].\{-}\.\{-\}[)\]]' + +" An else with a condition attached +let s:ELSE_COND = '\C^\s*else\s\+\<\%(if\|unless\)\>' + +" A single-line else statement (without a condition attached) +let s:SINGLE_LINE_ELSE = '\C^else\s\+\%(\<\%(if\|unless\)\>\)\@!' + +" Pairs of starting and ending keywords, with an initial pattern to match +let s:KEYWORD_PAIRS = [ +\ ['\C^else\>', '\C\<\%(if\|unless\|when\|else\s\+\%(if\|unless\)\)\>', +\ '\C\'], +\ ['\C^catch\>', '\C\', '\C\'], +\ ['\C^finally\>', '\C\', '\C\'] +\] + +" Pairs of starting and ending brackets +let s:BRACKET_PAIRS = {']': '\[', '}': '{', ')': '('} + +" Max lines to look back for a match +let s:MAX_LOOKBACK = 50 + +" Syntax names for strings +let s:SYNTAX_STRING = 'coffee\%(String\|AssignString\|Embed\|Regex\|Heregex\|' +\ . 'Heredoc\)' + +" Syntax names for comments +let s:SYNTAX_COMMENT = 'coffee\%(Comment\|BlockComment\|HeregexComment\)' + +" Syntax names for strings and comments +let s:SYNTAX_STRING_COMMENT = s:SYNTAX_STRING . '\|' . s:SYNTAX_COMMENT + +" Compatibility code for shiftwidth() as recommended by the docs, but modified +" so there isn't as much of a penalty if shiftwidth() exists. +if exists('*shiftwidth') + let s:ShiftWidth = function('shiftwidth') +else + function! s:ShiftWidth() + return &shiftwidth + endfunction +endif + +" Get the linked syntax name of a character. +function! s:SyntaxName(lnum, col) + return synIDattr(synID(a:lnum, a:col, 1), 'name') +endfunction + +" Check if a character is in a comment. +function! s:IsComment(lnum, col) + return s:SyntaxName(a:lnum, a:col) =~ s:SYNTAX_COMMENT +endfunction + +" Check if a character is in a string. +function! s:IsString(lnum, col) + return s:SyntaxName(a:lnum, a:col) =~ s:SYNTAX_STRING +endfunction + +" Check if a character is in a comment or string. +function! s:IsCommentOrString(lnum, col) + return s:SyntaxName(a:lnum, a:col) =~ s:SYNTAX_STRING_COMMENT +endfunction + +" Search a line for a regex until one is found outside a string or comment. +function! s:SearchCode(lnum, regex) + " Start at the first column and look for an initial match (including at the + " cursor.) + call cursor(a:lnum, 1) + let pos = search(a:regex, 'c', a:lnum) + + while pos + if !s:IsCommentOrString(a:lnum, col('.')) + return 1 + endif + + " Move to the match and continue searching (don't accept matches at the + " cursor.) + let pos = search(a:regex, '', a:lnum) + endwhile + + return 0 +endfunction + +" Search for the nearest previous line that isn't a comment. +function! s:GetPrevNormalLine(startlnum) + let curlnum = a:startlnum + + while curlnum + let curlnum = prevnonblank(curlnum - 1) + + " Return the line if the first non-whitespace character isn't a comment. + if !s:IsComment(curlnum, indent(curlnum) + 1) + return curlnum + endif + endwhile + + return 0 +endfunction + +function! s:SearchPair(startlnum, lookback, skip, open, close) + " Go to the first column so a:close will be matched even if it's at the + " beginning of the line. + call cursor(a:startlnum, 1) + return searchpair(a:open, '', a:close, 'bnW', a:skip, max([1, a:lookback])) +endfunction + +" Skip if a match +" - is in a string or comment +" - is a single-line statement that isn't immediately +" adjacent +" - has a postfix condition and isn't an else statement or compound +" expression +function! s:ShouldSkip(startlnum, lnum, col) + return s:IsCommentOrString(a:lnum, a:col) || + \ s:SearchCode(a:lnum, '\C\') && a:startlnum - a:lnum > 1 || + \ s:SearchCode(a:lnum, s:POSTFIX_CONDITION) && + \ getline(a:lnum) !~ s:ELSE_COND && + \ !s:SearchCode(a:lnum, s:COMPOUND_EXPRESSION) +endfunction + +" Search for the nearest and farthest match for a keyword pair. +function! s:SearchMatchingKeyword(startlnum, open, close) + let skip = 's:ShouldSkip(' . a:startlnum . ", line('.'), line('.'))" + + " Search for the nearest match. + let nearestlnum = s:SearchPair(a:startlnum, a:startlnum - s:MAX_LOOKBACK, + \ skip, a:open, a:close) + + if !nearestlnum + return [] + endif + + " Find the nearest previous line with indent less than or equal to startlnum. + let ind = indent(a:startlnum) + let lookback = s:GetPrevNormalLine(a:startlnum) + + while lookback && indent(lookback) > ind + let lookback = s:GetPrevNormalLine(lookback) + endwhile + + " Search for the farthest match. If there are no other matches, then the + " nearest match is also the farthest one. + let matchlnum = nearestlnum + + while matchlnum + let lnum = matchlnum + let matchlnum = s:SearchPair(matchlnum, lookback, skip, a:open, a:close) + endwhile + + return [nearestlnum, lnum] +endfunction + +" Strip a line of a trailing comment and surrounding whitespace. +function! s:GetTrimmedLine(lnum) + " Try to find a comment starting at the first column. + call cursor(a:lnum, 1) + let pos = search('#', 'c', a:lnum) + + " Keep searching until a comment is found or search returns 0. + while pos + if s:IsComment(a:lnum, col('.')) + break + endif + + let pos = search('#', '', a:lnum) + endwhile + + if !pos + " No comment was found so use the whole line. + let line = getline(a:lnum) + else + " Subtract 1 to get to the column before the comment and another 1 for + " column indexing -> zero-based indexing. + let line = getline(a:lnum)[:col('.') - 2] + endif + + return substitute(substitute(line, '^\s\+', '', ''), + \ '\s\+$', '', '') +endfunction + +" Get the indent policy when no special rules are used. +function! s:GetDefaultPolicy(curlnum) + " Check whether equalprg is being ran on existing lines. + if strlen(getline(a:curlnum)) == indent(a:curlnum) + " If not indenting an existing line, use the default policy. + return s:DEFAULT_LEVEL + else + " Otherwise let autoindent determine what to do with an existing line. + return '-1' + endif +endfunction + +function! GetCoffeeIndent(curlnum) + " Get the previous non-blank line (may be a comment.) + let prevlnum = prevnonblank(a:curlnum - 1) + + " Bail if there's no code before. + if !prevlnum + return -1 + endif + + " Bail if inside a multiline string. + if s:IsString(a:curlnum, 1) + let prevnlnum = prevlnum + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + + " Get the code part of the current line. + let curline = s:GetTrimmedLine(a:curlnum) + " Get the previous non-comment line. + let prevnlnum = s:GetPrevNormalLine(a:curlnum) + + " Check if the current line is the closing bracket in a bracket pair. + if has_key(s:BRACKET_PAIRS, curline[0]) + " Search for a matching opening bracket. + let matchlnum = s:SearchPair(a:curlnum, a:curlnum - s:MAX_LOOKBACK, + \ "s:IsCommentOrString(line('.'), col('.'))", + \ s:BRACKET_PAIRS[curline[0]], curline[0]) + + if matchlnum + " Match the indent of the opening bracket. + return indent(matchlnum) + else + " No opening bracket found (bad syntax), so bail. + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + endif + + " Check if the current line is the closing keyword in a keyword pair. + for pair in s:KEYWORD_PAIRS + if curline =~ pair[0] + " Find the nearest and farthest matches within the same indent level. + let matches = s:SearchMatchingKeyword(a:curlnum, pair[1], pair[2]) + + if len(matches) + " Don't force indenting/outdenting as long as line is already lined up + " with a valid match + return max([min([indent(a:curlnum), indent(matches[0])]), + \ indent(matches[1])]) + else + " No starting keyword found (bad syntax), so bail. + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + endif + endfor + + " Check if the current line is a `when` and not the first in a switch block. + if curline =~ '\C^when\>' && !s:SearchCode(prevnlnum, '\C\') + " Look back for a `when`. + while prevnlnum + if getline(prevnlnum) =~ '\C^\s*when\>' + " Indent to match the found `when`, but don't force indenting (for when + " indenting nested switch blocks.) + return min([indent(a:curlnum), indent(prevnlnum)]) + endif + + let prevnlnum = s:GetPrevNormalLine(prevnlnum) + endwhile + + " No matching `when` found (bad syntax), so bail. + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + + " If the previous line is a comment, use its indentation, but don't force + " indenting. + if prevlnum != prevnlnum + return min([indent(a:curlnum), indent(prevlnum)]) + endif + + let prevline = s:GetTrimmedLine(prevnlnum) + + " Always indent after these operators. + if prevline =~ s:BEGIN_BLOCK_OP + return indent(prevnlnum) + s:ShiftWidth() + endif + + " Indent if the previous line starts a function block, but don't force + " indenting if the line is non-blank (for empty function bodies.) + if prevline =~ s:FUNCTION + if strlen(getline(a:curlnum)) > indent(a:curlnum) + return min([indent(prevnlnum) + s:ShiftWidth(), indent(a:curlnum)]) + else + return indent(prevnlnum) + s:ShiftWidth() + endif + endif + + " Check if continuation indenting is needed. If the line ends in a slash, make + " sure it isn't a regex. + if prevline =~ s:CONTINUATION_OP && + \ !(prevline =~ '/$' && s:IsString(prevnlnum, col([prevnlnum, '$']) - 1)) + " Don't indent if the continuation follows a closing bracket. + if prevline =~ s:BRACKET_CONTINUATION + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + + let prevprevnlnum = s:GetPrevNormalLine(prevnlnum) + + " Don't indent if not the first continuation. + if prevprevnlnum && s:GetTrimmedLine(prevprevnlnum) =~ s:CONTINUATION + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + + " Continuation indenting seems to vary between programmers, so if the line + " is non-blank, don't override the indentation + if strlen(getline(a:curlnum)) > indent(a:curlnum) + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + + " Otherwise indent a level. + return indent(prevnlnum) + s:ShiftWidth() + endif + + " Check if the previous line starts with a keyword that begins a block. + if prevline =~ s:BEGIN_BLOCK + " Indent if the current line doesn't start with `then` and the previous line + " isn't a single-line statement. + if curline !~ '\C^\' && !s:SearchCode(prevnlnum, '\C\') && + \ prevline !~ s:SINGLE_LINE_ELSE + return indent(prevnlnum) + s:ShiftWidth() + else + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + endif + + " Indent a dot access if it's the first. + if curline =~ s:DOT_ACCESS + if prevline !~ s:DOT_ACCESS + return indent(prevnlnum) + s:ShiftWidth() + else + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + endif + + " Outdent if a keyword breaks out of a block as long as it doesn't have a + " postfix condition (and the postfix condition isn't a single-line statement.) + if prevline =~ s:BREAK_BLOCK_OP + if !s:SearchCode(prevnlnum, s:POSTFIX_CONDITION) || + \ s:SearchCode(prevnlnum, '\C\') && + \ !s:SearchCode(prevnlnum, s:CONTAINED_THEN) + " Don't force indenting. + return min([indent(a:curlnum), indent(prevnlnum) - s:ShiftWidth()]) + else + exec 'return' s:GetDefaultPolicy(a:curlnum) + endif + endif + + " Check if inside brackets. + let matchlnum = s:SearchPair(a:curlnum, a:curlnum - s:MAX_LOOKBACK, + \ "s:IsCommentOrString(line('.'), col('.'))", + \ '\[\|(\|{', '\]\|)\|}') + + " If inside brackets, indent relative to the brackets, but don't outdent an + " already indented line. + if matchlnum + return max([indent(a:curlnum), indent(matchlnum) + s:ShiftWidth()]) + endif + + " No special rules applied, so use the default policy. + exec 'return' s:GetDefaultPolicy(a:curlnum) +endfunction diff --git a/syntax/coffee.vim b/syntax/coffee.vim new file mode 100755 index 0000000..7f8df73 --- /dev/null +++ b/syntax/coffee.vim @@ -0,0 +1,221 @@ +" Language: CoffeeScript +" Maintainer: Mick Koch +" URL: http://github.com/kchmck/vim-coffee-script +" License: WTFPL + +" Bail if our syntax is already loaded. +if exists('b:current_syntax') && b:current_syntax == 'coffee' + finish +endif + +" Include JavaScript for coffeeEmbed. +syn include @coffeeJS syntax/javascript.vim +silent! unlet b:current_syntax + +" Highlight long strings. +syntax sync fromstart + +" These are `matches` instead of `keywords` because vim's highlighting +" priority for keywords is higher than matches. This causes keywords to be +" highlighted inside matches, even if a match says it shouldn't contain them -- +" like with coffeeAssign and coffeeDot. +syn match coffeeStatement /\<\%(return\|break\|continue\|throw\)\>/ display +hi def link coffeeStatement Statement + +syn match coffeeRepeat /\<\%(for\|while\|until\|loop\)\>/ display +hi def link coffeeRepeat Repeat + +syn match coffeeConditional /\<\%(if\|else\|unless\|switch\|when\|then\)\>/ +\ display +hi def link coffeeConditional Conditional + +syn match coffeeException /\<\%(try\|catch\|finally\)\>/ display +hi def link coffeeException Exception + +syn match coffeeKeyword /\<\%(new\|in\|of\|by\|and\|or\|not\|is\|isnt\|class\|extends\|super\|do\)\>/ +\ display +" The `own` keyword is only a keyword after `for`. +syn match coffeeKeyword /\/ contained containedin=coffeeRepeat +\ display +hi def link coffeeKeyword Keyword + +syn match coffeeOperator /\<\%(instanceof\|typeof\|delete\)\>/ display +hi def link coffeeOperator Operator + +" The first case matches symbol operators only if they have an operand before. +syn match coffeeExtendedOp /\%(\S\s*\)\@<=[+\-*/%&|\^=!<>?.]\{-1,}\|[-=]>\|--\|++\|:/ +\ display +syn match coffeeExtendedOp /\<\%(and\|or\)=/ display +hi def link coffeeExtendedOp coffeeOperator + +" This is separate from `coffeeExtendedOp` to help differentiate commas from +" dots. +syn match coffeeSpecialOp /[,;]/ display +hi def link coffeeSpecialOp SpecialChar + +syn match coffeeBoolean /\<\%(true\|on\|yes\|false\|off\|no\)\>/ display +hi def link coffeeBoolean Boolean + +syn match coffeeGlobal /\<\%(null\|undefined\)\>/ display +hi def link coffeeGlobal Type + +" A special variable +syn match coffeeSpecialVar /\<\%(this\|prototype\|arguments\)\>/ display +hi def link coffeeSpecialVar Special + +" An @-variable +syn match coffeeSpecialIdent /@\%(\%(\I\|\$\)\%(\i\|\$\)*\)\?/ display +hi def link coffeeSpecialIdent Identifier + +" A class-like name that starts with a capital letter +syn match coffeeObject /\<\u\w*\>/ display +hi def link coffeeObject Structure + +" A constant-like name in SCREAMING_CAPS +syn match coffeeConstant /\<\u[A-Z0-9_]\+\>/ display +hi def link coffeeConstant Constant + +" A variable name +syn cluster coffeeIdentifier contains=coffeeSpecialVar,coffeeSpecialIdent, +\ coffeeObject,coffeeConstant + +" A non-interpolated string +syn cluster coffeeBasicString contains=@Spell,coffeeEscape +" An interpolated string +syn cluster coffeeInterpString contains=@coffeeBasicString,coffeeInterp + +" Regular strings +syn region coffeeString start=/"/ skip=/\\\\\|\\"/ end=/"/ +\ contains=@coffeeInterpString +syn region coffeeString start=/'/ skip=/\\\\\|\\'/ end=/'/ +\ contains=@coffeeBasicString +hi def link coffeeString String + +" A integer, including a leading plus or minus +syn match coffeeNumber /\%(\i\|\$\)\@/ display +syn match coffeeNumber /\<0[bB][01]\+\>/ display +syn match coffeeNumber /\<0[oO][0-7]\+\>/ display +syn match coffeeNumber /\<\%(Infinity\|NaN\)\>/ display +hi def link coffeeNumber Number + +" A floating-point number, including a leading plus or minus +syn match coffeeFloat /\%(\i\|\$\)\@/ +\ display +hi def link coffeeReservedError Error + +" A normal object assignment +syn match coffeeObjAssign /@\?\%(\I\|\$\)\%(\i\|\$\)*\s*\ze::\@!/ contains=@coffeeIdentifier display +hi def link coffeeObjAssign Identifier + +syn keyword coffeeTodo TODO FIXME XXX contained +hi def link coffeeTodo Todo + +syn match coffeeComment /#.*/ contains=@Spell,coffeeTodo +hi def link coffeeComment Comment + +syn region coffeeBlockComment start=/####\@!/ end=/###/ +\ contains=@Spell,coffeeTodo +hi def link coffeeBlockComment coffeeComment + +" A comment in a heregex +syn region coffeeHeregexComment start=/#/ end=/\ze\/\/\/\|$/ contained +\ contains=@Spell,coffeeTodo +hi def link coffeeHeregexComment coffeeComment + +" Embedded JavaScript +syn region coffeeEmbed matchgroup=coffeeEmbedDelim +\ start=/`/ skip=/\\\\\|\\`/ end=/`/ keepend +\ contains=@coffeeJS +hi def link coffeeEmbedDelim Delimiter + +syn region coffeeInterp matchgroup=coffeeInterpDelim start=/#{/ end=/}/ contained +\ contains=@coffeeAll +hi def link coffeeInterpDelim PreProc + +" A string escape sequence +syn match coffeeEscape /\\\d\d\d\|\\x\x\{2\}\|\\u\x\{4\}\|\\./ contained display +hi def link coffeeEscape SpecialChar + +" A regex -- must not follow a parenthesis, number, or identifier, and must not +" be followed by a number +syn region coffeeRegex start=#\%(\%()\|\%(\i\|\$\)\@> #{ == { { { } } } == } << " +" >> #{ == { abc: { def: 42 } } == } << " diff --git a/test/test-ops.coffee b/test/test-ops.coffee new file mode 100644 index 0000000..54be8db --- /dev/null +++ b/test/test-ops.coffee @@ -0,0 +1,90 @@ +# Various operators +abc instanceof def +typeof abc +delete abc +abc::def + +abc + def +abc - def +abc * def +abc / def +abc % def +abc & def +abc | def +abc ^ def +abc >> def +abc << def +abc >>> def +abc ? def +abc && def +abc and def +abc || def +abc or def + +abc += def +abc -= def +abc *= def +abc /= def +abc %= def +abc &= def +abc |= def +abc ^= def +abc >>= def +abc <<= def +abc >>>= def +abc ?= def +abc &&= def +abc ||= def + +abc and= def +abc or= def + +abc.def.ghi +abc?.def?.ghi + +abc < def +abc > def +abc = def +abc == def +abc != def +abc <= def +abc >= def + +abc++ +abc-- +++abc +--abc + +# Nested operators +abc[def] = ghi +abc[def[ghi: jkl]] = 42 +@abc[def] = ghi + +abc["#{def = 42}"] = 42 +abc["#{def.ghi = 42}"] = 42 +abc["#{def[ghi] = 42}"] = 42 +abc["#{def['ghi']}"] = 42 + +# Object assignments +abc = + def: 123 + DEF: 123 + @def: 123 + Def: 123 + 'def': 123 + 42: 123 + +# Operators shouldn't be highlighted +vector= +wand= + +abc+++ +abc--- +abc ** def +abc &&& def +abc ^^ def +abc ===== def +abc <==== def +abc >==== def +abc +== def +abc =^= def diff --git a/test/test-reserved.coffee b/test/test-reserved.coffee new file mode 100644 index 0000000..b841760 --- /dev/null +++ b/test/test-reserved.coffee @@ -0,0 +1,27 @@ +# Should be an error +function = 42 +var = 42 + +# Shouldn't be an error +abc.with = 42 +function: 42 +var: 42 + +# Keywords shouldn't be highlighted +abc.function +abc.do +abc.break +abc.true + +abc::function +abc::do +abc::break +abc::true + +abc:: function +abc. function + +# Numbers should be highlighted +def.42 +def .42 +def::42 diff --git a/test/test.haml b/test/test.haml new file mode 100644 index 0000000..ae19fba --- /dev/null +++ b/test/test.haml @@ -0,0 +1,3 @@ +:coffeescript + class Hello + # test diff --git a/test/test.html b/test/test.html new file mode 100644 index 0000000..3479145 --- /dev/null +++ b/test/test.html @@ -0,0 +1,7 @@ + + +