├── .gitignore ├── LICENSE.txt ├── README.mkd ├── bin ├── jscc-cli └── jscc-cli-legacy ├── colors └── js_context_colors.vim ├── doc ├── JSContextColor.txt └── tags ├── ftplugin └── javascript.vim ├── package.json └── syntax └── javascript.vim /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tags 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 David Wilhelm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | #Vim JavaScript Context Coloring 2 | 3 | ##Description 4 | 5 | This is a plugin for the Vim text editor to highlight JavaScript code 6 | according to its scope. This was inspired by an idea by Douglas Crockford 7 | in his [presentation](http://www.youtube.com/watch?v=dkZFtimgAcM) at YUIConf 2012. 8 | (See from around the 16 minute mark onwards.) 9 | 10 | ##Other implementations 11 | See [this discussion on Google Plus](https://plus.google.com/118095276221607585885/posts/FzKnHk96m2C) 12 | for information on other implementations. 13 | 14 | ##Usage 15 | 16 | When the `JSContextColor` command is executed, the current buffer is colorized 17 | according to the scope of the code. By default this will happen when a JavaScript 18 | file is loaded, and whenever the file is changed. If you disable the plugin (see below), 19 | you could arrange to call the `JSContextColor` command in response to a keymap of 20 | your choice. Here is a summary of the commands available: 21 | 22 | ##Commands 23 | 24 | JSContextColor 25 | JSContextColorToggle 26 | JSContextColorUpdate 27 | 28 | ##Global Variables 29 | 30 | g:js_context_colors_enabled 31 | g:js_context_colors 32 | g:js_context_colors_insertmode 33 | g:js_context_colors_usemaps 34 | g:js_context_colors_colorize_comments 35 | g:js_context_colors_comment_higroup 36 | g:js_context_colors_debug 37 | g:js_context_colors_highlight_function_names 38 | g:js_context_colors_es5 39 | g:js_context_colors_block_scope 40 | g:js_context_colors_block_scope_with_let 41 | g:js_context_colors_jsx 42 | g:js_context_colors_fold 43 | g:js_context_colors_folding 44 | g:js_context_colors_allow_jsx_syntax 45 | 46 | ##Requirements 47 | 48 | * The plugin relies on [NodeJS](http://nodejs.org/) being installed, preferably > 0.10. 49 | 50 | * UNIX-like environment. The command line script which runs jslint in Node to obtain the coloring data 51 | requires a Unix-like environment to work. This means it's probably not going to work 52 | on Windows, unless inside Cygwin or something like that. 53 | 54 | * Vim version: 7.4 recommended, however it also works now on 7.3. Untested on earlier versions. 55 | 56 | ##Installation 57 | 58 | ### Using Pathogen. 59 | 60 | 0. Install [Pathogen](https://github.com/tpope/vim-pathogen) 61 | 62 | 1. Clone the repo from GitHub into your 'bundle' directory: 63 | 64 | git clone git@github.com:bigfish/vim-js-context-coloring.git 65 | 66 | 2. Run this command in the repo dir to install required node modules: 67 | 68 | npm install 69 | 70 | 3. Install docs (in Vim): 71 | 72 | :Helptags 73 | 74 | ### Using NeoBundle. 75 | 76 | 0. Install [NeoBundle](https://github.com/Shougo/neobundle.vim#quick-start) 77 | 78 | 1. Add the following to your `vimrc`: 79 | 80 | ```VimL 81 | NeoBundle 'bigfish/vim-js-context-coloring', { 82 | \ 'build' : { 83 | \ 'mac' : 'npm install --update', 84 | \ 'unix' : 'npm install --update', 85 | \ }, 86 | \ } 87 | ``` 88 | 89 | 2. Close & re-open Vim. 90 | 91 | ##Configuration 92 | 93 | By default the plugin is enabled for all .js files. The highlighting will be applied 94 | whenever a change is made to the text, whether in Normal or Insert mode. Note that it 95 | will only be done *after* Insert mode is exited (back to Normal mode) so it will not 96 | update as you type. 97 | 98 | ###Colors 99 | 100 | The default colors for JavaScript scope levels are: 101 | 102 | 1:white, 2:green, 3: yellow 4:blue, 5:red, 6:cyan, 7:grey. 103 | 104 | The highlighting is now handled by a colorscheme file, in standard vim colorscheme format. 105 | If you're upgrading from a previous version of the plugin (<= 0.6), you will need to 106 | reconfigure your colors by copying the 'jscc_colors.vim' colorscheme file from the 107 | plugin's /colors directory, to your VIMRUNTIME/colors directory (create if necessary). 108 | On unix this will be ~/.vim/colors. Make sure the name is still the same. 109 | Then you can make modifications to the highlight groups in the file as you wish. 110 | See ':help highlight-args' or look at other colorschemes for guidance. 111 | You may add additional levels if you wish. 112 | 113 | The special syntax group 'JSCC_UndeclaredGlobal' targets undeclared global 114 | variables, in order to distinguish them from global variables which were 115 | declared in the current file. If you don't want to distinguish these, just set 116 | this group to the same highlighting as JSCC_Level_0. 117 | 118 | You may use the [XtermColorTable](http://www.vim.org/scripts/script.php?script_id=3412) plugin to see what colors are available 119 | 120 | Note that the color changes will not take effect immediately. To update the colors, you 121 | can use the `:JSContextColorUpdate` command. 122 | 123 | ###256 Color Support 124 | 125 | ####Linux 126 | 127 | Ubuntu (and I suspect most modern distros?) do support 256 colors by default, but Vim does not know this 128 | as the $TERM environment is set to 'xterm' by default and Vim does not think that the terminal can support 129 | 256 colors and does not even try. This can be remedied by adding the following line to your .bashrc: 130 | 131 | [ -z "$TMUX" ] && export TERM=xterm-256color 132 | 133 | This will set the $TERM environment variable to `xterm-256colr` unless we are using tmux (which sets its own $TERM value and should allow the plugin to work). 134 | 135 | ####Mac OS X 136 | 137 | Terminal.app does not support 256 colors in Snow Leopard and older. If you have Snow Leopard you can use iTerm2 138 | to enable 256 color support. If you have a newer OSX you should be OK but the $TERM variable must still be 139 | set to 'xterm-256color'. Apparently this is default in Lion, and more recent versions, so that it should just work. 140 | 141 | ###Comments 142 | 143 | By default, comments are not colorized, but set to a middle grey color. If 144 | you want to colorize comments as well, do this: 145 | 146 | let g:js_context_colors_colorize_comments = 1 147 | 148 | If you want to use a custom highlight group for comments, define the group, 149 | and then assign it to the `g:js_context_colors_comment_higroup`, eg: 150 | 151 | highlight MyComment ctermfg=red 152 | let g:js_context_colors_comment_higroup = 'MyComment' 153 | 154 | Or to use the comment syntax highlighting from your colorscheme: 155 | 156 | let g:js_context_colors_comment_higroup = 'Comment' 157 | 158 | This will only take effect if the `g:js_context_colors_colorize_comments` option 159 | is not set to 1. 160 | 161 | ### Highlighting Function names 162 | 163 | Setting the `g:js_context_colors_highlight_function_names` option to 1 will 164 | highlight declared function names with the parent scope level color, to 165 | indicate that the name was exported to the container scope. 166 | 167 | ###ES6 Support 168 | 169 | The plugin now supports ES6 code. Arrow functions will be highlighted. 170 | If you also want to highlight block scopes, you can set the `g:js_context_colors_block_scope` 171 | option, but be aware this will also affect ES3/5 code if you open it. So you can 172 | use the `g:js_context_colors_block_scope_with_let` option, to only highlight 173 | blocks with let variables. 174 | 175 | ##ES5 only mode 176 | 177 | If you only want to support ES5 syntax, you can set the `g:js_context_colors_es5` option 178 | 179 | ### JSX support 180 | 181 | Set the `g:js_context_colors_jsx` option to 1 to support JSX (and ES6). If the file extension 182 | is .jsx this will be automatically turned on. 183 | 184 | If you want JXS syntax to be used, overriding the scope colors, set 185 | `g:js_context_colors_allow_jsx_syntax` to 1. The vim-jsx or equivalent syntax must be 186 | loaded *after* this plugin. 187 | 188 | ### Folding 189 | 190 | The foldmethod is set to syntax by default, so if you do 'zc' to close the current fold, 191 | it will fold the current function. ('zo' opens it again). If `g:js_context_colors_block_scope` 192 | is set, it will fold the current block. There is another option to automatically fold functions 193 | after a certain depth of nesting, which is 9 by default. The option that controls this is 194 | `g:js_context_colors_foldlevel`. Set to a lower number to fold sooner, or a higher to avoid folding 195 | if you don't want auto folding. To not set foldmethod to syntax, and disable autofolding, 196 | set the `g:js_context_colors_foldlevel` to 0. 197 | 198 | ###Error Message 199 | When the plugin fails to get a valid response from the parser script, it 200 | will show an error... it will also not highlight. This is most likely a syntax error, 201 | but it could also be a problem with the CLI script (perhaps it was not installed). 202 | To stop annoying users, however it is now suppressed by default. 203 | 204 | But it can be re-enabled with this option: 205 | 206 | let g:js_context_colors_show_error_message = 1 207 | 208 | ###Disabling 209 | 210 | The plugin is enabled by default. To disable it, set the `g:js_context_colors_enabled` variable 211 | to 0, in your .vimrc file. 212 | 213 | ###Mappings 214 | 215 | By default, h is mapped to the `JSContextColor` command. 216 | By default, t is mapped to the `JSContextColorToggle` command. 217 | 218 | These are local to the buffer. They can be disabled by setting the `g:js_context_colors_usemaps` variable to 0. 219 | 220 | ###NeoVim 221 | 222 | This plugin has a [Neovim](https://neovim.io/) specific version, on the 'neovim' branch. 223 | 224 | if you're new to neovim, [this](https://neovim.io/doc/user/nvim_from_vim.html) has helpful info on 225 | configuring nvim to use your existing vim config. 226 | 227 | The advantage of the nvim fork is that the scope-analysis is done in a different thread than (n)vim, 228 | so it does not block input. This makes vim more responsive while using this plugin, especially when editing larger js files. 229 | Also, since the node code is running continually in the background, it will become compiled and thus faster. 230 | 231 | ####Setup 232 | 233 | * Check out the neovim branch, and install node modules: 234 | 235 | cd vim-js-context-colors 236 | git fetch -a 237 | git checkout neovim 238 | cd rplugin/node 239 | npm install 240 | 241 | * The neovim version of my plugin depends on the [node-host nvim plugin](https://github.com/neovim/node-host) 242 | So this must be installed as well (eg. using Vundle:) 243 | 244 | if has('nvim') 245 | Plugin 'neovim/node-host' 246 | endif 247 | 248 | * You also need to do `npm install` inside the node-host repo as well.. 249 | 250 | * When you run nvim, you also need to do 251 | 252 | :UpdateRemotePlugins 253 | 254 | command, and then restart nvim. AFAIK, this only needs to be done the first time, or after updating the plugin. 255 | 256 | * You may sometimes notice the js code appears with default highlighting for a split second.. this is a side effect of 257 | the asynchronous nature of the plugin. It is more noticable on the Mac for some reason, than on Linux, and for the first file 258 | after starting the computer, as the node-host runtime has to startup. After that it shouldn't be noticable. 259 | 260 | 261 | ##License 262 | 263 | MIT -- see LICENSE file 264 | -------------------------------------------------------------------------------- /bin/jscc-cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /*jshint node:true, strict:false*/ 3 | 4 | var jsx = false; 5 | var block_scope = false; 6 | var block_scope_with_let = false; 7 | var highlight_function_names = false; 8 | 9 | //handle //CLI args (just flags, no values) 10 | // --jsx : support JSX syntax 11 | // --block-scope : highlight block scope (if es6 is on) 12 | // --block-scope-with-let : highlight block scope only if it contains let variables 13 | // --highlight-function-names : highlight names in function declarations 14 | 15 | if (process.argv.length > 2) { 16 | if (process.argv.indexOf("--jsx") !== -1) { 17 | jsx = true; 18 | } 19 | if (process.argv.indexOf("--block-scope") !== -1) { 20 | block_scope = true; 21 | } 22 | if (process.argv.indexOf("--block-scope-with-let") !== -1) { 23 | block_scope_with_let = true; 24 | } 25 | if (process.argv.indexOf("--highlight-function-names") !== -1) { 26 | highlight_function_names = true; 27 | } 28 | } 29 | 30 | //parser depends on if jsx support is required 31 | //acorn-jsx should work for es6 32 | //but escope seems to favor esprima in some cases 33 | if (jsx) { 34 | var acorn = require('acorn-jsx'); 35 | } else { 36 | var esprima = require('esprima'); 37 | } 38 | 39 | var escope = require('escope'); 40 | 41 | var input_js = ''; 42 | var scopes = []; 43 | 44 | //collect input from stdin 45 | process.stdin.resume(); 46 | process.stdin.setEncoding('utf8'); 47 | 48 | process.stdin.on('data', function(chunk) { 49 | input_js += chunk; 50 | }); 51 | 52 | var import_re = /^\s*import\s/m; 53 | var export_re = /^\s*export\s/m; 54 | 55 | function isModule(js) { 56 | return import_re.test(js) || export_re.test(js); 57 | } 58 | 59 | process.stdin.on('end', function() { 60 | 61 | var ast; 62 | var sourceType = isModule(input_js) ? 'module' : 'script'; 63 | 64 | if (jsx) { 65 | ast = acorn.parse(input_js, { 66 | ecmaVersion: 6, 67 | ranges: true, 68 | sourceType: sourceType, 69 | allowImportExportEverywhere: true, 70 | allowReturnOutsideFunction: true, 71 | plugins: { jsx: true } 72 | }); 73 | 74 | } else { 75 | ast = esprima.parse(input_js, { 76 | range: true, 77 | tolerant:true 78 | }); 79 | } 80 | 81 | var scopeManager = escope.analyze(ast, { 82 | optimistic: true, 83 | ignoreEval: true, 84 | ecmaVersion: 6, 85 | sourceType: sourceType, 86 | }); 87 | 88 | var toplevel = scopeManager.acquire(ast); 89 | 90 | var enclosed = {}; 91 | var declared_globals = []; 92 | var undeclared_globals = []; 93 | 94 | //define scope for toplevel 95 | toplevel.variables.forEach(function (variable) { 96 | declared_globals.push(variable.defs[0].name.name); 97 | enclosed[variable.defs[0].name.name] = 0; 98 | }); 99 | 100 | toplevel.through.forEach(function (ref) { 101 | if (ref.identifier.name && !enclosed[ref.identifier.name]) { 102 | enclosed[ref.identifier.name] = -1; 103 | undeclared_globals.push(ref.identifier.name); 104 | } 105 | }); 106 | scopes.push([0, toplevel.block.range[0], toplevel.block.range[1], enclosed]); 107 | 108 | function hasLet(scope) { 109 | var v, 110 | variable, 111 | vlen = scope.variables.length; 112 | 113 | for (v = 0; v < vlen; v++) { 114 | variable = scope.variables[v]; 115 | if (variable.defs.length && 116 | variable.defs[0].type === "Variable" && 117 | variable.defs[0].parent.kind === 'let') { 118 | return true; 119 | } 120 | 121 | } 122 | return false; 123 | 124 | } 125 | 126 | function setLevel(scope, level) { 127 | 128 | var enclosed = {}; 129 | 130 | scope.level = level; 131 | 132 | //level 0 references already done 133 | if (level) { 134 | 135 | //add function name to enclosed vars 136 | if (highlight_function_names && 137 | scope.type === 'function' && 138 | scope.block.type === 'FunctionDeclaration') { 139 | enclosed[scope.block.id.name] = level - 1; 140 | } 141 | 142 | scope.through.forEach(function (ref) { 143 | if (ref.resolved) { 144 | enclosed[ref.identifier.name] = ref.resolved.scope.level; 145 | } else { 146 | 147 | if (declared_globals.indexOf(ref.identifier.name) !== -1) { 148 | enclosed[ref.identifier.name] = 0; 149 | } else { 150 | //undeclared global level is -1 151 | enclosed[ref.identifier.name] = -1; 152 | } 153 | /*else if (undeclared_globals.indexOf(ref.identifier.name) !== -1) { 154 | enclosed[ref.identifier.name] = -1; 155 | }*/ 156 | } 157 | }); 158 | 159 | scopes.push([level, scope.block.range[0], scope.block.range[1], enclosed]); 160 | } 161 | 162 | //recurse into childScopes 163 | if (scope.childScopes.length) { 164 | scope.childScopes.forEach(function (s) { 165 | 166 | //only color function scopes unless use_block_scope is true 167 | if (block_scope || s.type === "function") { 168 | setLevel(s, level + 1); 169 | } else if (block_scope_with_let && hasLet(s)) { 170 | setLevel(s, level + 1); 171 | } else { 172 | setLevel(s, level); 173 | } 174 | }); 175 | } 176 | 177 | } 178 | 179 | 180 | setLevel(toplevel, 0) 181 | 182 | process.stdout.write(JSON.stringify({ 183 | scopes: scopes 184 | })); 185 | }); 186 | -------------------------------------------------------------------------------- /bin/jscc-cli-legacy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | //legacy cli for es5 only 3 | 4 | /*jshint node:true, strict:false*/ 5 | var input_js = ''; 6 | var UglifyJS = require("../node_modules/uglify-js/tools/node.js"); 7 | var scopes = []; 8 | 9 | process.stdin.resume(); 10 | process.stdin.setEncoding('utf8'); 11 | 12 | process.stdin.on('data', function(chunk) { 13 | input_js += chunk; 14 | }); 15 | 16 | process.stdin.on('end', function() { 17 | var toplevel = UglifyJS.parse(input_js); 18 | toplevel.figure_out_scope(); 19 | 20 | 21 | var walker = new UglifyJS.TreeWalker(function(node){ 22 | 23 | var level, def, scope; 24 | 25 | // AST_Accessor inherits AST_Scope but does not have start/end 26 | // these must be obtained from AST_ObjectSetter/Getter property node 27 | // which has the accessor function as its value. So to fix the issue 28 | // just set the start and end manually using the Getter/Setter's values 29 | 30 | if ( node instanceof UglifyJS.AST_ObjectGetter || node instanceof UglifyJS.AST_ObjectSetter) { 31 | 32 | //only start highlighting after get / set keyword 33 | node.value.start = { 34 | pos: node.start.pos + 4 35 | }; 36 | node.value.end = { 37 | endpos: node.end.endpos 38 | }; 39 | } 40 | 41 | if (node instanceof UglifyJS.AST_Scope) { 42 | 43 | //annotate Scope Nodes with level info 44 | if (node.level === undefined) { 45 | node.level = node.parent_scope ? node.parent_scope.level + 1 : 0; 46 | var enclosed = {}; 47 | node.enclosed.forEach(function(v) { 48 | enclosed[v.name] = v.scope.level 49 | }); 50 | 51 | scope = [node.level, node.start.pos, node.end.endpos, enclosed]; 52 | 53 | //add named function definitions 54 | if (node instanceof UglifyJS.AST_Defun) { 55 | scope.push(node.name.name);//] = node.parent_scope.level; 56 | } 57 | 58 | scopes.push(scope); 59 | } 60 | 61 | 62 | } 63 | }); 64 | toplevel.walk(walker); 65 | 66 | process.stdout.write(JSON.stringify({ 67 | scopes: scopes 68 | })); 69 | }); 70 | -------------------------------------------------------------------------------- /colors/js_context_colors.vim: -------------------------------------------------------------------------------- 1 | " Vim color file 2 | " 3 | " Author: David Wilhelm 4 | " 5 | " Note: Used by the JavaScript Context Colors plugin 6 | " to highlight function scopes differently by level 7 | " top level = 0 8 | " To override these colors, copy this colorscheme 9 | " to your ./vim/colors/ dir and change as desired 10 | 11 | "echom "JSCC: loading highlighting groups" 12 | 13 | hi JSCC_UndeclaredGlobal ctermfg=199 guifg=#FF0000 14 | 15 | hi JSCC_Level_0 ctermfg=76 guifg=#5fdf00 16 | hi JSCC_Level_1 ctermfg=2 guifg=#008000 17 | hi JSCC_Level_2 ctermfg=3 guifg=#808000 18 | hi JSCC_Level_3 ctermfg=4 guifg=#000080 19 | hi JSCC_Level_4 ctermfg=1 guifg=#800000 20 | hi JSCC_Level_5 ctermfg=6 guifg=#008080 21 | hi JSCC_Level_6 ctermfg=7 guifg=#c0c0c0 22 | 23 | hi Comment ctermfg=243 guifg=#767676 24 | 25 | if !g:js_context_colors_colorize_comments 26 | hi link javaScriptComment Comment 27 | hi link javaScriptLineComment Comment 28 | hi link javaScriptDocComment Comment 29 | hi link javaScriptCommentTodo Todo 30 | endif 31 | -------------------------------------------------------------------------------- /doc/JSContextColor.txt: -------------------------------------------------------------------------------- 1 | Highlight JavaScript according to function scope *JSContextColor* 2 | 3 | * . _______*______ . * . __ . * __ ______ . __ * . . 4 | . / / ___// ____/___ ____ / /____ _ __/ /_/ ____/___ / /___ _____ 5 | _ * / /\__ \/ / / __ \/ __ \/ __/ _ \| |/_/ __/ / / __ \/ / __ \/ ___/ 6 | / /_/ /___/ / /___/ /_/ / / / / /_/ __/> 10 | 11 | ============================================================================== 12 | Commands 13 | 14 | *:JSContextColor* 15 | :JSContextColor Colorize current buffer 16 | 17 | *:JSContextColorToggle* 18 | :JSContextColorToggle Toggle whether the plugin is enabled 19 | 20 | *:JSContextColorUpdate* 21 | :JSContextColorUpdate Update the color highlighting 22 | 23 | ============================================================================== 24 | 25 | 26 | Customizing Colors 27 | 28 | 29 | To do this, copy the 'jscc_colors.vim' colorscheme file from the plugin's /colors 30 | directory, to your VIMRUNTIME/colors directory (create if necessary). Make 31 | sure the name is still the same. Then you can make modifications to the highlight 32 | groups in the file as you wish. see ':help highlight-args' or look at other 33 | colorschemes for guidance. The XtermColorTable plugin is also helpful for seeing 34 | the available colors and their codes. 35 | 36 | The special syntax group 'JSCC_UndeclaredGlobal' targets undeclared global 37 | variables, in order to distinguish them from global variables which were 38 | declared in the current file. If you don't want to distinguish these, just set 39 | this group to the same highlighting as JSCC_Level_0. 40 | 41 | If you change the colors, you need to call :JSContextColorUpdate 42 | 43 | Options 44 | *g:js_context_colors_enabled* 45 | 46 | Whether highlighting is done automatically when .js files are loaded/changed. 47 | > 48 | let g:js_context_colors_enabled = 0 49 | < 50 | 51 | Note that commands above still work even when this is set to 0. If you enable 52 | the `g:js_context_colors_usemaps` option below, you can trigger them more easily. 53 | 54 | *g:js_context_colors_fold* 55 | 56 | Whether folding on javascript scopes (functions) is enabled. This is enabled 57 | by default. To disable: 58 | > 59 | let g:js_context_colors_fold = 0 60 | < 61 | 62 | *g:js_context_colors_foldlevel* 63 | 64 | The level where folds will be closed by default. Default is 9. To change: 65 | > 66 | let g:js_context_colors_foldlevel = 2 67 | 68 | This will auto-fold functions 2 levels deep. 69 | 70 | 71 | 72 | *g:js_context_colors_colorize_comments* 73 | 74 | Whether to colorize comments according to the scope color -- default is false. 75 | 76 | If you want this enable this behaviour, set the option to 1: 77 | 78 | > 79 | let g:js_context_colors_colorize_comments = 1 80 | 81 | This will highlight comments as the comment higroup (see next section). 82 | 83 | 84 | 85 | *g:js_context_colors_usemaps* 86 | 87 | Disables maps (see below) when set to 0: 88 | 89 | > 90 | let g:js_context_colors_usemaps = 0 91 | < 92 | 93 | *g:js_context_colors_show_error_message* 94 | 95 | When the plugin fails to get a valid response from the parser script, it 96 | will show an error... it will also not highlight. This is most likely a syntax error, 97 | but it could also be a problem with the CLI script (perhaps it was not installed). 98 | To stop annoying users, however it is now suppressed by default. 99 | 100 | But for debugging, it can be re-enabled with this option: 101 | 102 | > 103 | let g:js_context_colors_show_error_message = 1 104 | < 105 | 106 | *g:js_context_colors_debug* 107 | 108 | If set to 1, this enables verbose debug output, eg. highlighting data is 109 | displayed. 110 | 111 | *g:js_context_colors_block_scope* 112 | 113 | Highlight blocks as scope levels. Only really makes sense for ES6 code, but 114 | this will also affect ES5 code, so be aware of that. It will be ignored if 115 | the g:js_context_colors_es5 option is set (see below). 116 | 117 | *g:js_context_colors_block_scope_with_let* 118 | 119 | Highlight blocks as scope levels, only if a 'let' variable was included in it. 120 | This allows smarter highlighting across ES5 and ES6, as only ES6 code will 121 | have let variables. 122 | 123 | *g:js_context_colors_highlight_function_names* 124 | 125 | Highlight declared function names with the parent scope level color, to 126 | indicate that the name was exported to the container scope. Set to 1 to 127 | enable. 128 | 129 | *g:js_context_colors_jsx* 130 | 131 | Tolerates React/JSX syntax. This is off by default, set to 1 to enable. If the file extension 132 | is .jsx this will be automatically turned on. 133 | *g:js_context_colors_es5* 134 | 135 | Only support ES5 syntax. By default the plugin will now highlight ES6 syntax as well as 136 | ES5/ES3. This switches to the older CLI backend. 137 | 138 | *g:js_context_colors_allow_jsx_syntax* 139 | 140 | Allow JSX syntax (eg. vim-jsx) to appear over scope highlighting. This must be 141 | installed separately, and loaded *after* this plugin. 142 | > 143 | 144 | ============================================================================== 145 | Mapping 146 | 147 | By default, h is mapped to the JSContextColor command. 148 | By default, t is mapped to the JSContextColorToggle command. 149 | 150 | These can be changed by setting your own maps to the commands. This will 151 | remove the above mappings. 152 | 153 | vim:tw=78:sw=8:ts=8:sts=8:noet:ft=help:norl: 154 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | :JSContextColor JSContextColor.txt /*:JSContextColor* 2 | :JSContextColorToggle JSContextColor.txt /*:JSContextColorToggle* 3 | :JSContextColorUpdate JSContextColor.txt /*:JSContextColorUpdate* 4 | JSContextColor JSContextColor.txt /*JSContextColor* 5 | g:js_context_colors_block_scope JSContextColor.txt /*g:js_context_colors_block_scope* 6 | g:js_context_colors_block_scope_with_let JSContextColor.txt /*g:js_context_colors_block_scope_with_let* 7 | g:js_context_colors_colorize_comments JSContextColor.txt /*g:js_context_colors_colorize_comments* 8 | g:js_context_colors_debug JSContextColor.txt /*g:js_context_colors_debug* 9 | g:js_context_colors_enabled JSContextColor.txt /*g:js_context_colors_enabled* 10 | g:js_context_colors_es5 JSContextColor.txt /*g:js_context_colors_es5* 11 | g:js_context_colors_fold JSContextColor.txt /*g:js_context_colors_fold* 12 | g:js_context_colors_foldlevel JSContextColor.txt /*g:js_context_colors_foldlevel* 13 | g:js_context_colors_highlight_function_names JSContextColor.txt /*g:js_context_colors_highlight_function_names* 14 | g:js_context_colors_jsx JSContextColor.txt /*g:js_context_colors_jsx* 15 | g:js_context_colors_show_error_message JSContextColor.txt /*g:js_context_colors_show_error_message* 16 | g:js_context_colors_usemaps JSContextColor.txt /*g:js_context_colors_usemaps* 17 | -------------------------------------------------------------------------------- /ftplugin/javascript.vim: -------------------------------------------------------------------------------- 1 | "plugin to add javascript scope-coloring 2 | "Version: 0.0.8 3 | "Author: David Wilhelm 4 | " 5 | "Note: highlights function scopes in JavaScript 6 | " 7 | " Only do this when not done yet for this buffer 8 | if exists("b:did_jscc_ftplugin") 9 | finish 10 | endif 11 | 12 | setlocal iskeyword+=$ 13 | 14 | let b:did_jscc_ftplugin = 1 15 | let b:scope_groups = [] 16 | 17 | let s:cli_cmd = 'jscc-cli' 18 | 19 | " check options to send as CLI params 20 | if !exists('g:js_context_colors_jsx') 21 | let g:js_context_colors_jsx = 0 22 | endif 23 | 24 | "however, if current buffer file extension is .jsx 25 | "turn on jsx flag 26 | let b:file_ext = expand('%:e') 27 | if b:file_ext == "jsx" 28 | let g:js_context_colors_jsx = 1 29 | endif 30 | 31 | if g:js_context_colors_jsx 32 | let s:cli_cmd .= ' --jsx' 33 | endif 34 | 35 | if !exists('g:js_context_colors_block_scope') 36 | let g:js_context_colors_block_scope = 0 37 | endif 38 | 39 | if g:js_context_colors_block_scope 40 | let s:cli_cmd .= ' --block-scope' 41 | endif 42 | 43 | if !exists('g:js_context_colors_block_scope_with_let') 44 | let g:js_context_colors_block_scope_with_let = 0 45 | endif 46 | 47 | if !exists('g:js_context_colors_highlight_function_names') 48 | let g:js_context_colors_highlight_function_names = 0 49 | endif 50 | 51 | if g:js_context_colors_highlight_function_names 52 | let s:cli_cmd .= ' --highlight-function-names' 53 | endif 54 | 55 | if g:js_context_colors_block_scope_with_let 56 | let s:cli_cmd .= ' --block-scope-with-let' 57 | endif 58 | 59 | "revert to old cli if es5 option is given 60 | if !exists('g:js_context_colors_es5') 61 | let g:js_context_colors_es5 = 0 62 | endif 63 | 64 | if g:js_context_colors_es5 65 | let s:cli_cmd = 'jscc-cli-legacy' 66 | endif 67 | 68 | let s:jscc = expand(':p:h') . '/../bin/' . s:cli_cmd 69 | 70 | let s:region_count = 1 71 | 72 | syntax case match 73 | 74 | "set default options if no provided value 75 | if !exists('g:js_context_colors_enabled') 76 | let g:js_context_colors_enabled = 1 77 | endif 78 | 79 | if !exists('g:js_context_colors_usemaps') 80 | let g:js_context_colors_usemaps = 1 81 | endif 82 | 83 | if !exists('g:js_context_colors_colorize_comments') 84 | let g:js_context_colors_colorize_comments = 0 85 | endif 86 | 87 | if !exists('g:js_context_colors_fold') 88 | let g:js_context_colors_fold = 1 89 | endif 90 | 91 | if !exists('g:js_context_colors_foldlevel') 92 | let g:js_context_colors_foldlevel = 9 93 | endif 94 | 95 | 96 | if !exists('g:js_context_colors_show_error_message') 97 | let g:js_context_colors_show_error_message = 0 98 | endif 99 | 100 | if !exists('g:js_context_colors_debug') 101 | let g:js_context_colors_debug = 0 102 | endif 103 | 104 | if !exists('g:js_context_colors_allow_jsx_syntax') 105 | let g:js_context_colors_allow_jsx_syntax = 0 106 | endif 107 | let s:max_levels = 10 108 | 109 | "parse functions 110 | function! Strip(input_string) 111 | return substitute(a:input_string, '^\s*\(.\{-}\)\s*$', '\1', '') 112 | endfunction 113 | 114 | "following functions are used for debugging 115 | function! Warn(msg) 116 | echohl Error | echom a:msg 117 | echohl Normal 118 | endfunction 119 | 120 | function! IsPos(pos) 121 | if len(a:pos) != 2 122 | return 0 123 | endif 124 | if type(a:pos[0]) != type(0) 125 | return 0 126 | endif 127 | if type(a:pos[0]) < 0 128 | return 0 129 | endif 130 | if type(a:pos[1]) != type(0) 131 | return 0 132 | endif 133 | if type(a:pos[1]) < 0 134 | return 0 135 | endif 136 | 137 | return 1 138 | endfunction 139 | 140 | function! GetPosFromOffset(offset) 141 | "normalize byte count for Vim (first byte is 1 in Vim) 142 | let offset = a:offset + 1 143 | if offset < 0 144 | call Warn('offset cannot be less than 0: ' . string(offset)) 145 | endif 146 | let line = byte2line(offset) 147 | let line_start_offset = line2byte(line) 148 | "first col is 1 in Vim 149 | let col = (offset - line_start_offset) + 1 150 | let pos = [line, col] 151 | "if !IsPos(pos) 152 | "Warn('invalid pos result in GetPosFromOffset()' . string(pos)) 153 | "endif 154 | return pos 155 | endfunction 156 | 157 | let s:jscc_highlight_groups_defined = 0 158 | 159 | function! JSCC_DefineSyntaxGroups() 160 | 161 | "define JavaScript syntax groups -- all syntax is cleared when 162 | "colorizing is done, so they must be redefined 163 | syntax keyword javaScriptCommentTodo TODO FIXME XXX TBD contained 164 | syntax region javaScriptLineComment start=+\/\/+ end=+$+ keepend contains=javaScriptCommentTodo 165 | syntax region javaScriptLineComment start=+^\s*\/\/+ skip=+\n\s*\/\/+ end=+$+ keepend contains=javaScriptCommentTodo fold 166 | syntax region javaScriptComment start="/\*" end="\*/" contains=javaScriptCommentTodo fold 167 | 168 | syntax cluster jsComment contains=javaScriptComment,javaScriptLineComment 169 | 170 | "allow highlighting of vars inside strings,jsx regions 171 | "TODO: fix issue where quote characters inside regex literals breaks highlighting 172 | 173 | "for lev in range(s:max_levels) 174 | "exe 'syntax region javaScriptStringD_'. lev .' start=+"+ skip=+\\\\\|\\$"+ end=+"+ keepend' 175 | "exe "syntax region javaScriptStringS_". lev ." start=+'+ skip=+\\\\\|\\$'+ end=+'+ keepend" 176 | "exe "syntax region javaScriptTemplate_". lev ." start=+`+ skip=+\\\\\|\\$'\"+ end=+`+ keepend" 177 | 178 | "if g:js_context_colors_jsx 179 | " Highlight JSX regions as XML; recursively match. 180 | "exe "syn region jsxRegion_" . lev . " contains=jsxRegion,javaScriptStringD_". lev .",javaScriptStringS_" . lev ." start=+<\\@+ end=++ end=+/>+ keepend extend" 181 | "endif 182 | 183 | "exe 'hi link javaScriptStringS_' . lev . ' JSCC_Level_' . lev 184 | "exe 'hi link javaScriptStringD_' . lev . ' JSCC_Level_' . lev 185 | "exe 'hi link javaScriptTemplate_' . lev . ' JSCC_Level_' . lev 186 | 187 | "if g:js_context_colors_jsx 188 | "exe 'hi link jsxRegion_' . lev . ' JSCC_Level_' . lev 189 | "endif 190 | 191 | "endfor 192 | 193 | endfunction 194 | 195 | "define highlight groups dynamically 196 | function! JSCC_DefineHighlightGroups() 197 | 198 | colors js_context_colors 199 | 200 | let s:jscc_highlight_groups_defined = 1 201 | endfunction 202 | 203 | function! JSCC_ClearScopeSyntax() 204 | "clear previous scope syntax groups 205 | if exists('b:scope_groups') && len(b:scope_groups) 206 | for grp in b:scope_groups 207 | exe "syntax clear " . join(b:scope_groups, " ") 208 | endfor 209 | endif 210 | let b:scope_groups = [] 211 | endfunction 212 | 213 | function! JSCC_Colorize() 214 | 215 | "bail if not a js filetype 216 | if stridx(&ft, 'javascript') != 0 217 | return 218 | endif 219 | 220 | call JSCC_ClearScopeSyntax() 221 | 222 | let s:region_count = 0 223 | 224 | let s:scope_level_clusters = {} 225 | 226 | if !g:js_context_colors_colorize_comments 227 | call JSCC_DefineSyntaxGroups() 228 | endif 229 | 230 | if !s:jscc_highlight_groups_defined 231 | call JSCC_DefineHighlightGroups() 232 | endif 233 | 234 | let buflines = getline(1, '$') 235 | 236 | "replace hashbangs (in node CLI scripts) 237 | let linenum = 0 238 | for bline in buflines 239 | if match(bline, '#!') == 0 240 | "replace #! with // to prevent parse errors 241 | "while not throwing off byte count 242 | let buflines[linenum] = '//' . strpart(bline, 2) 243 | break 244 | endif 245 | let linenum += 1 246 | endfor 247 | "fix offset errors caused by windows line endings 248 | "since 'buflines' does NOT return the line endings 249 | "we need to replace them for unix/mac file formats 250 | "and for windows we replace them with a space and \n 251 | "since \r does not work in node on linux, just replacing 252 | "with a space will at least correct the offsets 253 | if &ff == 'unix' || &ff == 'mac' 254 | let buftext = join(buflines, "\n") 255 | elseif &ff == 'dos' 256 | let buftext = join(buflines, " \n") 257 | else 258 | echom 'unknown file format' . &ff 259 | let buftext = join(buflines, "\n") 260 | endif 261 | 262 | "noop if empty string 263 | if Strip(buftext) == '' 264 | return 265 | endif 266 | 267 | "ignore errors from shell command to prevent distracting user 268 | "syntax errors should be caught by a lint program 269 | try 270 | let colordata_result = system(s:jscc, buftext) 271 | 272 | let colordata = eval(colordata_result) 273 | 274 | let scopes = colordata.scopes 275 | "let symbols = colordata.symbols 276 | 277 | for scope in scopes 278 | 279 | "use offset from end to normalize 3 element and 2 element ranges 280 | let start_pos = GetPosFromOffset(scope[1]) 281 | let end_pos = GetPosFromOffset(scope[2]) 282 | let group_name = 'Level' . scope[0] 283 | let level = scope[0] 284 | 285 | let enclosed = scope[3] 286 | let enclosed_groups = [] 287 | 288 | "create unique scope syntax group name 289 | let s:region_count = s:region_count + 1 290 | let scope_group = group_name . s:region_count 291 | 292 | "create a scope level cluster or add scope to existing one 293 | let scope_cluster = 'ScopeLevelCluster_' . level 294 | 295 | if has_key(s:scope_level_clusters, scope_cluster) 296 | exe 'syntax cluster ' . scope_cluster .' add=' . scope_group 297 | else 298 | exe 'syntax cluster ScopeLevelCluster_' . level . ' contains=' . scope_group 299 | let s:scope_level_clusters[scope_cluster] = 1 300 | endif 301 | 302 | "handle vars 303 | for var in keys(enclosed) 304 | 305 | let var_level = enclosed[var] 306 | 307 | if var_level < level 308 | 309 | if var_level == -1 310 | let var_syntax_group = 'JSCC_UndeclaredGlobal' 311 | else 312 | let var_syntax_group = 'JSCC_Level_' . var_level . '_' . tr(var, '$', 'S') 313 | endif 314 | 315 | exe "syn match ". var_syntax_group . ' /\_[^.$[:alnum:]_]\zs' . var . "\\>\\(\\s*\\:\\)\\@!/ display contained containedin=" . scope_group 316 | 317 | "also match ${var} inside template strings 318 | exe "syn match ". var_syntax_group . ' /${\zs' . var . "\\(}\\)\\@=\\(\\s*\\:\\)\\@!/ display contained containedin=javaScriptTemplate_" . level 319 | 320 | "also matcth {{var}} inside strings, eg handlebars 321 | exe "syn match ". var_syntax_group . ' /{{\zs' . var . "\\(}}\\)\\@=\\(\\s*\\:\\)\\@!/ display contained containedin=javaScriptStringD_" . level . ",javaScriptStringS_" . level 322 | 323 | "match {var} in jsx 324 | if g:js_context_colors_jsx 325 | exe "syn match ". var_syntax_group . ' /\<' . var . "\\>\\(\\s*\\:\\)\\@!/ display contained containedin=jsxRegion_" . level 326 | endif 327 | 328 | if var_level != -1 329 | exe 'hi link ' . var_syntax_group . ' JSCC_Level_' . var_level 330 | endif 331 | 332 | call add(enclosed_groups, var_syntax_group) 333 | endif 334 | 335 | endfor 336 | 337 | let contains = "contains=@jsComment," . (g:js_context_colors_allow_jsx_syntax ? "jsxRegion," : "") 338 | \. "javaScriptStringS_" . level . ",javaScriptStringD_" . level . ",javaScriptTemplate_" . level . ",javaScriptProp,@ScopeLevelCluster_" . (level + 1) 339 | 340 | if len(enclosed_groups) 341 | let contains .= ',' . join(enclosed_groups, ',') 342 | endif 343 | 344 | let cmd = "syn region ". scope_group . " start='\\%" . start_pos[0] ."l\\%". start_pos[1] . 345 | \"c' end='\\%" . end_pos[0] . "l\\%" . end_pos[1] . 346 | \"c' " . contains . " fold" 347 | exe cmd 348 | 349 | exe 'hi link ' . scope_group . ' ' . 'JSCC_Level_' . level 350 | 351 | call add(b:scope_groups, scope_group) 352 | 353 | endfor 354 | 355 | catch 356 | 357 | if g:js_context_colors_show_error_message || g:js_context_colors_debug 358 | echom "Syntax Error [JSContextColors]" 359 | endif 360 | 361 | if g:js_context_colors_debug 362 | echom colordata_result 363 | endif 364 | 365 | endtry 366 | 367 | if g:js_context_colors_debug 368 | echom colordata_result 369 | endif 370 | "ensure syntax highlighting is fully applied 371 | syntax sync fromstart 372 | 373 | endfunction 374 | 375 | function! JSCC_Enable() 376 | 377 | if !s:jscc_highlight_groups_defined 378 | call JSCC_DefineHighlightGroups() 379 | endif 380 | 381 | if g:js_context_colors_fold 382 | setlocal foldmethod=syntax 383 | exe "setlocal foldlevel=" . g:js_context_colors_foldlevel 384 | endif 385 | 386 | try 387 | augroup JSContextColorAug 388 | "remove if added previously, but only in this buffer 389 | au! InsertLeave,TextChanged 390 | au! InsertLeave,TextChanged :JSContextColor 391 | augroup END 392 | 393 | "if < vim 7.4 TextChanged events are not 394 | "available and will result in error E216 395 | catch /^Vim\%((\a\+)\)\=:E216/ 396 | 397 | "use different events to trigger update in Vim < 7.4 398 | augroup JSContextColorAug 399 | au! InsertLeave 400 | au! InsertLeave :JSContextColor 401 | augroup END 402 | 403 | endtry 404 | 405 | syntax clear 406 | 407 | :JSContextColor 408 | 409 | endfunction 410 | 411 | function! JSCC_Disable() 412 | "clear autocommands 413 | try 414 | augroup JSContextColorAug 415 | au! InsertLeave,TextChanged 416 | augroup END 417 | 418 | catch /^Vim\%((\a\+)\)\=:E216/ 419 | 420 | augroup JSContextColorAug 421 | au! InsertLeave 422 | augroup END 423 | endtry 424 | 425 | call JSCC_ClearScopeSyntax() 426 | syntax enable 427 | 428 | let s:jscc_highlight_groups_defined = 0 429 | endfunction 430 | 431 | function! JSCC_Toggle() 432 | if g:js_context_colors_enabled 433 | let g:js_context_colors_enabled = 0 434 | call JSCC_Disable() 435 | else 436 | let g:js_context_colors_enabled = 1 437 | call JSCC_Enable() 438 | endif 439 | endfunction 440 | 441 | "define user commands 442 | "command! -range=% -nargs=0 JSContextColor ,:call JSCC_Colorize() 443 | command! JSContextColor call JSCC_Colorize() 444 | 445 | command! JSContextColorToggle call JSCC_Toggle() 446 | 447 | command! JSContextColorUpdate call JSCC_DefineHighlightGroups() 448 | 449 | "always create color highlight groups in case of direct calls to :JSContextColor 450 | :JSContextColorUpdate 451 | 452 | if g:js_context_colors_usemaps 453 | if !hasmapto('JSContextColor') 454 | "mnemonic (h)ighlight 455 | nnoremap h :JSContextColor 456 | endif 457 | 458 | if !hasmapto('JSContextColorToggle') 459 | "mnemonic (t)oggle 460 | nnoremap t :JSContextColorToggle 461 | endif 462 | endif 463 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim-js-context-coloring", 3 | "version": "0.0.4", 4 | "description": "Javascript scope highlighting", 5 | "main": "after/ftplugin/javascript.vim", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/bigfish/vim-js-context-coloring.git" 9 | }, 10 | "keywords": [ 11 | "vim", 12 | "javascript" 13 | ], 14 | "author": "dewilhelm@gmail.com", 15 | "license": "MIT", 16 | "readmeFilename": "README.mkd", 17 | "gitHead": "617951e1806cf83d3f324687e0a2a0bb28eca47b", 18 | "dependencies": { 19 | "uglify-js": "2.7.3", 20 | "escope": "3.6.0", 21 | "esprima": "3.1.0", 22 | "acorn-jsx": "3.0.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /syntax/javascript.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: JavaScript 3 | " Maintainer: David Wilhelm 4 | " Last Change: 2014 July 12 5 | 6 | " The purpose of this syntax file is to prevent 7 | " default javascript syntax from being loaded 8 | " which is unnecessary in the context of this plugin 9 | 10 | " The actual syntax definitions are done dynamically 11 | " in the ftplugin/javascript.vim file. 12 | 13 | if stridx(&filetype, 'javascript') == 0 && exists('g:js_context_colors_enabled') && g:js_context_colors_enabled 14 | 15 | call JSCC_Enable() 16 | 17 | "set syntax to javascript so another syntax for javascript isn't loaded 18 | let b:current_syntax=&filetype 19 | 20 | endif 21 | 22 | --------------------------------------------------------------------------------