├── images ├── fs.gif ├── logo.png ├── sin.gif ├── apples.gif └── magic2.gif ├── .travis.yml ├── shared └── streams.js ├── static ├── css │ ├── css.js │ ├── monokai.css │ ├── site.css │ ├── vibrant-ink.css │ └── codemirror.css ├── index.html ├── index.js └── js │ └── javascript.js ├── test.js ├── rpl.js ├── package.json ├── README.md └── index.js /images/fs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyzidiamond/rpl/HEAD/images/fs.gif -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyzidiamond/rpl/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/sin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyzidiamond/rpl/HEAD/images/sin.gif -------------------------------------------------------------------------------- /images/apples.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyzidiamond/rpl/HEAD/images/apples.gif -------------------------------------------------------------------------------- /images/magic2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyzidiamond/rpl/HEAD/images/magic2.gif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | before_install: 6 | - npm install -g npm@~1.4.6 7 | -------------------------------------------------------------------------------- /shared/streams.js: -------------------------------------------------------------------------------- 1 | var through = require('through'); 2 | 3 | module.exports.fromJSON = function() { 4 | return through(function(data) { 5 | this.queue(JSON.parse(data)); 6 | }); 7 | }; 8 | 9 | module.exports.toJSON = function() { 10 | return through(function(data) { 11 | this.queue(JSON.stringify(data)); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /static/css/css.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var insertCss = require('insert-css'); 3 | insertCss(fs.readFileSync(__dirname + '/vibrant-ink.css', 'utf8')); 4 | insertCss(fs.readFileSync(__dirname + '/codemirror.css', 'utf8')); 5 | insertCss(fs.readFileSync(__dirname + '/site.css', 'utf8')); 6 | insertCss(fs.readFileSync(__dirname + '/../../node_modules/mapbox.js/theme/style.css', 'utf8')); 7 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'), 2 | shoe = require('sockjs-stream'), 3 | RPL = require('./index.js'); 4 | 5 | test('rpl - listen & close', function(t) { 6 | var rpl = new RPL(); 7 | rpl.listen(1984, function(err, res) { 8 | var stream = shoe('ws://localhost:1984/eval'); 9 | stream.write(JSON.stringify({ value: '//=1' })); 10 | stream.on('data', function(data) { 11 | t.equal(JSON.parse(data)['1:0'][0].name, '1', 'response.name'); 12 | rpl.close(function(err, res) { 13 | t.pass('closed'); 14 | t.end(); 15 | }); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /rpl.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('minimist')(process.argv.slice(2)), 4 | opener = require('opener'), 5 | chromeApp = require('chrome-app'); 6 | var RPL = require('./'); 7 | 8 | var rpl = new RPL(argv._[0]); 9 | 10 | rpl.listen(1984, onlisten); 11 | 12 | function onlisten(err, res) { 13 | var address = 'http://' + 14 | rpl.server.address().address + ':' + 15 | rpl.server.address().port; 16 | console.log('rpl running at %s', address); 17 | if (argv.b) { 18 | chromeApp(address, 'rpl'); 19 | } 20 | if (argv.o) { 21 | opener(address); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rpl 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rpl", 3 | "version": "0.8.0", 4 | "description": "repl", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node rpl.js & watchify -d static/index.js -o static/bundle.js", 8 | "test": "tape test.js" 9 | }, 10 | "bin": { 11 | "rpl": "rpl.js" 12 | }, 13 | "browserify": { 14 | "transform": [ 15 | "brfs" 16 | ] 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git@github.com:tmcw/rpl.git" 21 | }, 22 | "author": "Tom MacWright", 23 | "license": "ISC", 24 | "dependencies": { 25 | "chrome-app": "0.1.0", 26 | "is-geojson": "^1.0.1", 27 | "json-stringify-safe": "~5.0.0", 28 | "mapbox.js": "^2.1.4", 29 | "minimist": "~1.1.0", 30 | "opener": "~1.4.0", 31 | "rickshaw": "^1.4.6", 32 | "shoe": "0.0.15", 33 | "st": "~0.5.2", 34 | "stream-combiner": "^0.2.1", 35 | "terrarium-stream": "^1.4.0", 36 | "through": "~2.3.6" 37 | }, 38 | "devDependencies": { 39 | "brfs": "~1.2.0", 40 | "browserify": "~6.0.2", 41 | "codemirror": "https://github.com/tmcw/CodeMirror/archive/module-export.tar.gz", 42 | "insert-css": "~0.2.0", 43 | "sockjs-stream": "^1.0.2", 44 | "tape": "^3.0.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /static/css/monokai.css: -------------------------------------------------------------------------------- 1 | /* Based on Sublime Text's Monokai theme */ 2 | 3 | .cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;} 4 | .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} 5 | .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} 6 | .cm-s-monokai .CodeMirror-linenumber {color: #d0d0d0;} 7 | .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} 8 | 9 | .cm-s-monokai span.cm-comment {color: #75715e;} 10 | .cm-s-monokai span.cm-atom {color: #ae81ff;} 11 | .cm-s-monokai span.cm-number {color: #ae81ff;} 12 | 13 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;} 14 | .cm-s-monokai span.cm-keyword {color: #f92672;} 15 | .cm-s-monokai span.cm-string {color: #e6db74;} 16 | 17 | .cm-s-monokai span.cm-variable {color: #a6e22e;} 18 | .cm-s-monokai span.cm-variable-2 {color: #9effff;} 19 | .cm-s-monokai span.cm-def {color: #fd971f;} 20 | .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;} 21 | .cm-s-monokai span.cm-bracket {color: #f8f8f2;} 22 | .cm-s-monokai span.cm-tag {color: #f92672;} 23 | .cm-s-monokai span.cm-link {color: #ae81ff;} 24 | 25 | .cm-s-monokai .CodeMirror-matchingbracket { 26 | text-decoration: underline; 27 | color: white !important; 28 | } 29 | -------------------------------------------------------------------------------- /static/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; 3 | padding:0; 4 | font:13px/20px sans-serif; 5 | } 6 | 7 | .control-line { 8 | background:#222; 9 | color:#eee; 10 | height:25px; 11 | line-height:25px; 12 | position:absolute; 13 | top:0; 14 | right:0; 15 | left:0; 16 | } 17 | 18 | #error { 19 | float:right; 20 | padding-right:5px; 21 | color:#eaa; 22 | } 23 | 24 | .editor { 25 | position:fixed; 26 | top:0; 27 | left:0; 28 | right:0; 29 | bottom:0; 30 | color:#fff; 31 | } 32 | 33 | .data { 34 | background:#000; 35 | border-top:1px solid #222; 36 | border-bottom:2px solid #222; 37 | color:#fff; 38 | position:relative; 39 | padding:5px 5px 5px 5px; 40 | margin:5px 0; 41 | } 42 | 43 | .data-name { 44 | position:absolute; 45 | padding:0 10px; 46 | top:0; 47 | right:0; 48 | line-height:25px; 49 | color:white; 50 | background:#222; 51 | z-index:9999; 52 | } 53 | 54 | code.time { 55 | color:#777; 56 | margin-right:5px; 57 | } 58 | 59 | .CodeMirror { 60 | position:absolute; 61 | top:25px; 62 | right:0; 63 | bottom:0; 64 | left:0; 65 | overflow:auto; 66 | height:auto; 67 | } 68 | 69 | .CodeMirror-linenumbers { 70 | background:#000; 71 | } 72 | 73 | .time-control { 74 | color:#aee; 75 | margin-left:10px; 76 | } 77 | 78 | .time-control a { 79 | color:#fff; 80 | text-decoration:none; 81 | } 82 | -------------------------------------------------------------------------------- /static/css/vibrant-ink.css: -------------------------------------------------------------------------------- 1 | /* Taken from the popular Visual Studio Vibrant Ink Schema */ 2 | 3 | .cm-s-vibrant-ink.CodeMirror { background: black; color: white; } 4 | .cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; } 5 | 6 | .cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } 7 | .cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } 8 | .cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white !important; } 9 | 10 | .cm-s-vibrant-ink .cm-keyword { color: #CC7832; } 11 | .cm-s-vibrant-ink .cm-atom { color: #FC0; } 12 | .cm-s-vibrant-ink .cm-number { color: #FFEE98; } 13 | .cm-s-vibrant-ink .cm-def { color: #8DA6CE; } 14 | .cm-s-vibrant-ink span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #FFC66D } 15 | .cm-s-vibrant-ink span.cm-variable-3, .cm-s-cobalt span.cm-def { color: #FFC66D } 16 | .cm-s-vibrant-ink .cm-operator { color: #888; } 17 | .cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } 18 | .cm-s-vibrant-ink .cm-string { color: #A5C25C } 19 | .cm-s-vibrant-ink .cm-string-2 { color: red } 20 | .cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } 21 | .cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } 22 | .cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } 23 | .cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } 24 | .cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } 25 | .cm-s-vibrant-ink .cm-header { color: #FF6400; } 26 | .cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } 27 | .cm-s-vibrant-ink .cm-link { color: blue; } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### See [rpl-www](https://github.com/tmcw/rpl-www) for a sibling project that's usable in a browser as a JavaScript library. 2 | 3 | ![rpl](images/logo.png) 4 | 5 | [![build status](https://secure.travis-ci.org/tmcw/rpl.png)](http://travis-ci.org/tmcw/rpl) 6 | 7 | `rpl` for the future and past. An alternative to the `node` default 8 | REPL (what you access when you just call `node` and can type in lines of code). 9 | 10 | ## Install 11 | 12 | npm install -g rpl 13 | 14 | The main trick is that this supports time travel. You can instrument code 15 | calls by using special comments, and edit previous code, changing future values. 16 | 17 | ![](images/apples.gif) 18 | 19 | It also supports async instrumentation, since node is node. 20 | 21 | ![](images/fs.gif) 22 | 23 | ![](images/sin.gif) 24 | 25 | ![](images/magic2.gif) 26 | 27 | ```js 28 | require('fs').readFile('/etc/hosts', 'utf8', function(err, res) { 29 | //=res 30 | }); 31 | ``` 32 | 33 | ## the binary 34 | 35 | ``` 36 | rpl [-b] [-o] [FILENAME] 37 | ^ ^ ^ 38 | | | | 39 | | | | prefill the cli with the contents of a file 40 | | | 41 | | | -o open your browser to the page 42 | | 43 | | -b open a chrome app standalone window 44 | ``` 45 | 46 | ## getting started 47 | 48 | `rpl` is a node module you install globally. When you run `rpl`, it starts 49 | up a server at `http://localhost:3000/`, so you'll need to open your browser 50 | to that page. From there you just type, and when you want to inspect a value, 51 | add a comment to your source code like: 52 | 53 | ```js 54 | //=variableName 55 | ``` 56 | 57 | You can also type expressions there, like: 58 | 59 | ```js 60 | //=Date() 61 | ``` 62 | 63 | You can use `require()` just like you would elsewhere, and, like the node 64 | REPL, `require()` calls become relative to the current working directory. 65 | 66 | ## see also 67 | 68 | `rpl` is the sibling of [mistakes.io](http://mistakes.io/), something 69 | that does something similar but in browsers instead of node and implicitly 70 | instead of explicitly. 71 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'), 4 | http = require('http'), 5 | streams = require('./shared/streams.js'), 6 | through = require('through'), 7 | st = require('st'), 8 | terrariumStream = require('terrarium-stream').Node, 9 | stringify = require('json-stringify-safe'), 10 | fs = require('fs'), 11 | shoe = require('shoe'); 12 | 13 | // from https://github.com/joyent/node/blob/master/lib/repl.js 14 | module.filename = path.resolve('rpl'); 15 | module.paths = require('module')._nodeModulePaths(module.filename); 16 | 17 | function writeHead(res, contentType) { 18 | res.writeHead(200, { 19 | 'Content-Type': contentType, 20 | 'Cache-Control': 'no-cache' 21 | }); 22 | } 23 | 24 | function RPL(filename) { 25 | this.filename = filename; 26 | 27 | if (this.filename) { 28 | if (!fs.existsSync(this.filename)) { 29 | console.log('Creating new file %s', this.filename); 30 | } else { 31 | this.defaultValue = fs.readFileSync(this.filename, 'utf8'); 32 | } 33 | } 34 | 35 | this.server = http.createServer(st({ 36 | path: __dirname + '/static', 37 | url: '/', 38 | cache: false, 39 | index: 'index.html', 40 | dot: true 41 | })); 42 | } 43 | 44 | RPL.prototype.close = function() { 45 | this.server.close.apply(this.server, arguments); 46 | }; 47 | 48 | RPL.prototype.listen = function() { 49 | this.server.listen.apply(this.server, arguments); 50 | 51 | var onstream = function(stream) { 52 | 53 | // if you've started this up with a file argument, 54 | // send that file to the browser 55 | if (this.defaultValue) { 56 | stream.write(JSON.stringify({ defaultValue: this.defaultValue })); 57 | } 58 | 59 | stream.pipe(streams.fromJSON()) 60 | .pipe(terrariumStream()) 61 | .pipe(streams.toJSON()) 62 | .pipe(stream); 63 | 64 | }.bind(this); 65 | 66 | // shoe manages our connection to the browser and lets 67 | // us send messages back and forth with streams. under the hood 68 | // it's all websockets on modern browsers. 69 | var sock = shoe(onstream); 70 | 71 | sock.install(this.server, '/eval'); 72 | 73 | this.sock = sock; 74 | }; 75 | 76 | // horrible errors within the vm can bubble up in unexpected ways. 77 | // we keep that from crashing the process by basically ignoring them 78 | // here. 79 | process.on('uncaughtException', function (err) { 80 | console.log('Caught exception: ' + err); 81 | }); 82 | 83 | module.exports = RPL; 84 | -------------------------------------------------------------------------------- /static/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: "M+ 1mn", monospace; 6 | height: 300px; 7 | } 8 | .CodeMirror-scroll { 9 | /* Set scrolling behaviour here */ 10 | overflow: auto; 11 | } 12 | 13 | /* PADDING */ 14 | 15 | .CodeMirror-lines { 16 | padding: 4px 0; /* Vertical padding around content */ 17 | } 18 | .CodeMirror pre { 19 | padding: 0 4px; /* Horizontal padding of content */ 20 | } 21 | 22 | .CodeMirror-scrollbar-filler { 23 | background-color: white; /* The little square between H and V scrollbars */ 24 | } 25 | 26 | /* GUTTER */ 27 | 28 | .CodeMirror-gutters { 29 | border-right: 1px solid #ddd; 30 | background-color: #f7f7f7; 31 | } 32 | .CodeMirror-linenumbers {} 33 | .CodeMirror-linenumber { 34 | padding: 0 3px 0 5px; 35 | min-width: 20px; 36 | text-align: right; 37 | color: #999; 38 | } 39 | 40 | /* CURSOR */ 41 | 42 | .CodeMirror div.CodeMirror-cursor { 43 | border-left: 1px solid black; 44 | } 45 | /* Shown when moving in bi-directional text */ 46 | .CodeMirror div.CodeMirror-secondarycursor { 47 | border-left: 1px solid silver; 48 | } 49 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 50 | width: auto; 51 | border: 0; 52 | background: transparent; 53 | background: rgba(0, 200, 0, .4); 54 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); 55 | } 56 | /* Kludge to turn off filter in ie9+, which also accepts rgba */ 57 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) { 58 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 59 | } 60 | /* Can style cursor different in overwrite (non-insert) mode */ 61 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 62 | 63 | /* DEFAULT THEME */ 64 | 65 | .cm-s-default .cm-keyword {color: #708;} 66 | .cm-s-default .cm-atom {color: #219;} 67 | .cm-s-default .cm-number {color: #164;} 68 | .cm-s-default .cm-def {color: #00f;} 69 | .cm-s-default .cm-variable {color: black;} 70 | .cm-s-default .cm-variable-2 {color: #05a;} 71 | .cm-s-default .cm-variable-3 {color: #085;} 72 | .cm-s-default .cm-property {color: black;} 73 | .cm-s-default .cm-operator {color: black;} 74 | .cm-s-default .cm-comment {color: #a50;} 75 | .cm-s-default .cm-string {color: #a11;} 76 | .cm-s-default .cm-string-2 {color: #f50;} 77 | .cm-s-default .cm-meta {color: #555;} 78 | .cm-s-default .cm-error {color: #f00;} 79 | .cm-s-default .cm-qualifier {color: #555;} 80 | .cm-s-default .cm-builtin {color: #30a;} 81 | .cm-s-default .cm-bracket {color: #997;} 82 | .cm-s-default .cm-tag {color: #170;} 83 | .cm-s-default .cm-attribute {color: #00c;} 84 | .cm-s-default .cm-header {color: blue;} 85 | .cm-s-default .cm-quote {color: #090;} 86 | .cm-s-default .cm-hr {color: #999;} 87 | .cm-s-default .cm-link {color: #00c;} 88 | 89 | .cm-negative {color: #d44;} 90 | .cm-positive {color: #292;} 91 | .cm-header, .cm-strong {font-weight: bold;} 92 | .cm-em {font-style: italic;} 93 | .cm-link {text-decoration: underline;} 94 | 95 | .cm-invalidchar {color: #f00;} 96 | 97 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 98 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 99 | 100 | /* STOP */ 101 | 102 | /* The rest of this file contains styles related to the mechanics of 103 | the editor. You probably shouldn't touch them. */ 104 | 105 | .CodeMirror { 106 | line-height: 1; 107 | position: relative; 108 | overflow: hidden; 109 | } 110 | 111 | .CodeMirror-scroll { 112 | /* 30px is the magic margin used to hide the element's real scrollbars */ 113 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ 114 | margin-bottom: -30px; margin-right: -30px; 115 | padding-bottom: 30px; padding-right: 30px; 116 | height: 100%; 117 | outline: none; /* Prevent dragging from highlighting the element */ 118 | position: relative; 119 | } 120 | .CodeMirror-sizer { 121 | position: relative; 122 | } 123 | 124 | /* The fake, visible scrollbars. Used to force redraw during scrolling 125 | before actuall scrolling happens, thus preventing shaking and 126 | flickering artifacts. */ 127 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 128 | position: absolute; 129 | z-index: 6; 130 | display: none; 131 | } 132 | .CodeMirror-vscrollbar { 133 | right: 0; top: 0; 134 | overflow-x: hidden; 135 | overflow-y: scroll; 136 | } 137 | .CodeMirror-hscrollbar { 138 | bottom: 0; left: 0; 139 | overflow-y: hidden; 140 | overflow-x: scroll; 141 | } 142 | .CodeMirror-scrollbar-filler { 143 | right: 0; bottom: 0; 144 | z-index: 6; 145 | } 146 | 147 | .CodeMirror-gutters { 148 | position: absolute; left: 0; top: 0; 149 | height: 100%; 150 | padding-bottom: 30px; 151 | z-index: 3; 152 | } 153 | .CodeMirror-gutter { 154 | height: 100%; 155 | display: inline-block; 156 | /* Hack to make IE7 behave */ 157 | *zoom:1; 158 | *display:inline; 159 | } 160 | .CodeMirror-gutter-elt { 161 | position: absolute; 162 | cursor: default; 163 | z-index: 4; 164 | } 165 | 166 | .CodeMirror-lines { 167 | cursor: text; 168 | } 169 | .CodeMirror pre { 170 | /* Reset some styles that the rest of the page might have set */ 171 | -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; 172 | border-width: 0; 173 | background: transparent; 174 | font-family: inherit; 175 | font-size: inherit; 176 | margin: 0; 177 | white-space: pre; 178 | word-wrap: normal; 179 | line-height: inherit; 180 | color: inherit; 181 | z-index: 2; 182 | position: relative; 183 | overflow: visible; 184 | } 185 | .CodeMirror-wrap pre { 186 | word-wrap: break-word; 187 | white-space: pre-wrap; 188 | word-break: normal; 189 | } 190 | .CodeMirror-linebackground { 191 | position: absolute; 192 | left: 0; right: 0; top: 0; bottom: 0; 193 | z-index: 0; 194 | } 195 | 196 | .CodeMirror-linewidget { 197 | position: relative; 198 | z-index: 2; 199 | overflow: auto; 200 | } 201 | 202 | .CodeMirror-widget { 203 | display: inline-block; 204 | } 205 | 206 | .CodeMirror-wrap .CodeMirror-scroll { 207 | overflow-x: hidden; 208 | } 209 | 210 | .CodeMirror-measure { 211 | position: absolute; 212 | width: 100%; height: 0px; 213 | overflow: hidden; 214 | visibility: hidden; 215 | } 216 | .CodeMirror-measure pre { position: static; } 217 | 218 | .CodeMirror div.CodeMirror-cursor { 219 | position: absolute; 220 | visibility: hidden; 221 | border-right: none; 222 | width: 0; 223 | } 224 | .CodeMirror-focused div.CodeMirror-cursor { 225 | visibility: visible; 226 | } 227 | 228 | .CodeMirror-selected { background: #d9d9d9; } 229 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 230 | 231 | .cm-searching { 232 | background: #ffa; 233 | background: rgba(255, 255, 0, .4); 234 | } 235 | 236 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 237 | .CodeMirror span { *vertical-align: text-bottom; } 238 | 239 | @media print { 240 | /* Hide the cursor when printing */ 241 | .CodeMirror div.CodeMirror-cursor { 242 | visibility: hidden; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /static/index.js: -------------------------------------------------------------------------------- 1 | require('./css/css.js'); 2 | 3 | var Combine = require('stream-combiner'); 4 | var through = require('through'); 5 | var shoe = require('shoe'); 6 | var Rickshaw = require('rickshaw'); 7 | var isGeoJSON = require('is-geojson'); 8 | var terrariumStream = require('terrarium-stream').Browser; 9 | var streams = require('../shared/streams.js'); 10 | function $(_) { return document.getElementById(_); } 11 | function ce(_, c) { 12 | var elem = document.createElement(_); 13 | elem.className = c || ''; 14 | return elem; 15 | } 16 | var error = $('error'); 17 | require('mapbox.js'); 18 | 19 | L.mapbox.accessToken = 'pk.eyJ1IjoidG1jdyIsImEiOiJIZmRUQjRBIn0.lRARalfaGHnPdRcc-7QZYQ'; 20 | 21 | var CodeMirror = require('codemirror'); 22 | require('./js/javascript')(CodeMirror); 23 | 24 | var backends = { 25 | node: function() { 26 | return Combine(streams.toJSON(), shoe('/eval'), streams.fromJSON()); 27 | }, 28 | browser: function() { return terrariumStream(); } 29 | }; 30 | 31 | var backend = null; 32 | var backendType = null; 33 | var widgets = []; 34 | 35 | var evalPause = false, globalIndent = false, delayedClear = null; 36 | $('evaluate').onchange = function(e) { evalPause = !e.target.checked; }; 37 | $('backend').onchange = function(e) { setBackend(e.target.value); }; 38 | 39 | var editor = CodeMirror.fromTextArea($('editor'), { 40 | indentUnit: 2, 41 | mode: 'text/javascript', 42 | lineNumbers: true, 43 | autofocus: true, 44 | extraKeys: { 45 | 'Ctrl-S': save, 46 | 'Cmd-S': save 47 | } 48 | }); 49 | 50 | function save() { 51 | backend.write({ value: editor.getValue(), command: 'save' }); 52 | return false; 53 | } 54 | 55 | editor.setOption('theme', 'vibrant-ink'); 56 | 57 | editor.on('change', function() { 58 | if (evalPause) return; 59 | clearTimeout(delayedClear); 60 | backend.write({ value: editor.getValue() }); 61 | }); 62 | 63 | setBackend('browser'); 64 | 65 | function setBackend(type) { 66 | if (backend) backend.destroy(); 67 | backend = backends[type](); 68 | backend.pipe(through(read)); 69 | backend.on('err', onerr); 70 | backendType = type; 71 | } 72 | 73 | function makeWidget(values) { 74 | var idx = 0; 75 | var value; 76 | var parsed; 77 | var count; 78 | var mode = 'json'; 79 | 80 | var msg = ce('div'); 81 | var pre = msg.appendChild(ce('pre')); 82 | var n = msg.appendChild(ce('div', 'data-name')); 83 | n.className = 'data-name'; 84 | var preTime = n.appendChild(ce('code', 'time')); 85 | var name = n.appendChild(ce('span', 'data-var')); 86 | name.innerHTML = values[idx].name; 87 | var select = n.appendChild(ce('select')); 88 | ['json', 'chart', 'map'].forEach(function(type) { 89 | var opt = select.appendChild(ce('option')); 90 | opt.value = opt.innerHTML = type; 91 | }); 92 | select.onchange = function(e) { 93 | mode = e.target.value; 94 | fillPre(); 95 | }; 96 | 97 | msg.className = 'data'; 98 | 99 | function fillPre() { 100 | try { 101 | parsed = value.val !== undefined ? value.val : value.stringified; 102 | 103 | if (parsed.ELEMENT_NODE) { 104 | widgetTypes.element(pre, parsed); 105 | } else if (mode === 'json') { 106 | widgetTypes.json(pre, parsed); 107 | } else if (mode === 'chart') { 108 | widgetTypes.chart(pre, parsed); 109 | } else if (mode === 'map') { 110 | widgetTypes.map(pre, parsed); 111 | } 112 | 113 | if (value.when > 0) { 114 | preTime.innerHTML = value.when + 'ms'; 115 | } else { 116 | preTime.innerHTML = ''; 117 | } 118 | } catch(e) { } 119 | } 120 | 121 | function setStep(_) { 122 | _ = Math.min(values.length - 1, Math.max(0, _)); 123 | value = values[_]; 124 | fillPre(); 125 | if (count) count.innerHTML = (_ + 1) + '/' + values.length; 126 | idx = _; 127 | } 128 | 129 | function nav(dir) { 130 | return function() { 131 | if (values[idx + dir]) setStep(idx + dir); 132 | return false; 133 | }; 134 | } 135 | 136 | function showNav() { 137 | if (values.length > 1) { 138 | if (n.getElementsByClassName('time-control').length) { 139 | n.getElementsByClassName('time-control')[0].parentNode.removeChild(n.getElementsByClassName('time-control')[0]); 140 | } 141 | var timeControl = n.appendChild(ce('span', 'time-control')); 142 | timeControl.className = 'time-control'; 143 | var backward = timeControl.appendChild(ce('a')); 144 | backward.innerHTML = '←'; 145 | backward.href = '#'; 146 | count = timeControl.appendChild(ce('span')); 147 | var forward = timeControl.appendChild(ce('a')); 148 | forward.innerHTML = '→'; 149 | forward.href = '#'; 150 | forward.addEventListener('click', nav(1)); 151 | backward.addEventListener('click', nav(-1)); 152 | } 153 | } 154 | 155 | showNav(); 156 | setStep(0); 157 | 158 | return { 159 | element: msg, 160 | update: function(_) { 161 | values = _; 162 | showNav(); 163 | fillPre(); 164 | setStep(idx); 165 | } 166 | }; 167 | } 168 | 169 | var widgetTypes = { 170 | json: function(container, value) { 171 | var element = container.firstChild; 172 | 173 | if (element && element.mode !== 'json') container.innerHTML = ''; 174 | 175 | if (element && element.mode == 'json') { 176 | update(); 177 | } else { 178 | setup(); update(); 179 | } 180 | 181 | function setup() { 182 | element = container.appendChild(ce('pre')); 183 | element.mode = 'json'; 184 | } 185 | function update() { 186 | element.innerHTML = JSON.stringify(value, null, 2); 187 | } 188 | }, 189 | element: function(container, value) { 190 | container.innerHTML = ''; 191 | container.appendChild(value); 192 | }, 193 | map: function(container, value) { 194 | var element = container.firstChild; 195 | 196 | if (element && element.mode !== 'map') container.innerHTML = ''; 197 | 198 | if (element && element.mode == 'map') { 199 | update(); 200 | } else { 201 | setup(); 202 | update(); 203 | } 204 | 205 | function setup() { 206 | element = container.appendChild(document.createElement('div')); 207 | element.mode = 'map'; 208 | element.style.height = '300px'; 209 | element.features = L.mapbox.featureLayer(); 210 | element.map = L.mapbox.map(element, 'tmcw.map-7s15q36b', { 211 | zoomControl: false }) 212 | .addLayer(element.features); 213 | } 214 | 215 | function update() { 216 | element.features.setGeoJSON(value); 217 | } 218 | }, 219 | chart: function(container, value) { 220 | var element = container.firstChild; 221 | 222 | if (element && element.mode !== 'chart') container.innerHTML = ''; 223 | 224 | if (element && element.mode == 'chart') { 225 | update(); 226 | } else { 227 | setup(); 228 | update(); 229 | } 230 | 231 | function setup() { 232 | element = container.appendChild(document.createElement('div')); 233 | element.chart = new Rickshaw.Graph({ 234 | element: element, 235 | width: 960, 236 | height: 300, 237 | renderer: 'line', 238 | series: [{ 239 | color: "#fff", 240 | data: value.map(function(d, i) { 241 | return { x: i, y: d }; 242 | }), 243 | name: 'variable' 244 | }] 245 | }); 246 | var hoverDetail = new Rickshaw.Graph.HoverDetail({ 247 | graph: element.chart 248 | }); 249 | element.chart.render(); 250 | element.mode = 'chart'; 251 | } 252 | 253 | function update() { 254 | element.chart.series[0].data = value.map(function(d, i) { 255 | return { x: i, y: d }; 256 | }); 257 | element.chart.update(); 258 | } 259 | } 260 | }; 261 | 262 | function joinWidgets(newData) { 263 | 264 | // remove old widgets 265 | widgets = widgets.filter(function(widget) { 266 | if (!newData[widget.id]) { 267 | editor.removeLineWidget(widget); 268 | return false; 269 | } else { 270 | return true; 271 | } 272 | }); 273 | 274 | var widgetsById = widgets.reduce(function(memo, w) { 275 | memo[w.id] = w; 276 | return memo; 277 | }, {}); 278 | 279 | for (var id in newData) { 280 | if (widgetsById[id]) { 281 | // update existing widgets 282 | widgetsById[id].update(newData[id]); 283 | } else { 284 | // create new widgets 285 | widgets.push(addWidget(newData[id], id)); 286 | } 287 | } 288 | 289 | function addWidget(val, id) { 290 | var line = val[val.length - 1].line; 291 | var w = makeWidget(val); 292 | var widget = editor.addLineWidget( 293 | line, 294 | w.element, { 295 | coverGutter: false, 296 | noHScroll: true 297 | }); 298 | widget.id = id; 299 | widget.update = w.update; 300 | return widget; 301 | } 302 | } 303 | 304 | function read(d) { 305 | if (evalPause) return; 306 | 307 | if (d.defaultValue) { 308 | editor.setValue(d.defaultValue); 309 | return; 310 | } 311 | 312 | clearTimeout(delayedClear); 313 | 314 | if (d.error) { 315 | error.style.display = 'block'; 316 | error.innerHTML = d.error; 317 | delayedClear = setTimeout(joinWidgets, 1000); 318 | } else { 319 | error.style.display = 'none'; 320 | joinWidgets(d); 321 | } 322 | } 323 | 324 | function onerr(str) { 325 | error.style.display = 'block'; 326 | error.innerHTML = str; 327 | delayedClear = setTimeout(joinWidgets, 1000); 328 | } 329 | 330 | function values(d) { return Object.keys(d).map(function(k) { return d[k]; }); } 331 | -------------------------------------------------------------------------------- /static/js/javascript.js: -------------------------------------------------------------------------------- 1 | module.exports = function(CodeMirror) { 2 | // TODO actually recognize syntax of TypeScript constructs 3 | 4 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 5 | var indentUnit = config.indentUnit; 6 | var jsonMode = parserConfig.json; 7 | var isTS = parserConfig.typescript; 8 | 9 | // Tokenizer 10 | 11 | var keywords = function(){ 12 | function kw(type) {return {type: type, style: "keyword"};} 13 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 14 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 15 | 16 | var jsKeywords = { 17 | "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 18 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, 19 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 20 | "function": kw("function"), "catch": kw("catch"), 21 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 22 | "in": operator, "typeof": operator, "instanceof": operator, 23 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 24 | }; 25 | 26 | // Extend the 'normal' keywords with the TypeScript language extensions 27 | if (isTS) { 28 | var type = {type: "variable", style: "variable-3"}; 29 | var tsKeywords = { 30 | // object-like things 31 | "interface": kw("interface"), 32 | "class": kw("class"), 33 | "extends": kw("extends"), 34 | "constructor": kw("constructor"), 35 | 36 | // scope modifiers 37 | "public": kw("public"), 38 | "private": kw("private"), 39 | "protected": kw("protected"), 40 | "static": kw("static"), 41 | 42 | "super": kw("super"), 43 | 44 | // types 45 | "string": type, "number": type, "bool": type, "any": type 46 | }; 47 | 48 | for (var attr in tsKeywords) { 49 | jsKeywords[attr] = tsKeywords[attr]; 50 | } 51 | } 52 | 53 | return jsKeywords; 54 | }(); 55 | 56 | var isOperatorChar = /[+\-*&%=<>!?|~^]/; 57 | 58 | function chain(stream, state, f) { 59 | state.tokenize = f; 60 | return f(stream, state); 61 | } 62 | 63 | function nextUntilUnescaped(stream, end) { 64 | var escaped = false, next; 65 | while ((next = stream.next()) != null) { 66 | if (next == end && !escaped) 67 | return false; 68 | escaped = !escaped && next == "\\"; 69 | } 70 | return escaped; 71 | } 72 | 73 | // Used as scratch variables to communicate multiple values without 74 | // consing up tons of objects. 75 | var type, content; 76 | function ret(tp, style, cont) { 77 | type = tp; content = cont; 78 | return style; 79 | } 80 | 81 | function jsTokenBase(stream, state) { 82 | var ch = stream.next(); 83 | if (ch == '"' || ch == "'") 84 | return chain(stream, state, jsTokenString(ch)); 85 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 86 | return ret(ch); 87 | else if (ch == "0" && stream.eat(/x/i)) { 88 | stream.eatWhile(/[\da-f]/i); 89 | return ret("number", "number"); 90 | } 91 | else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { 92 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 93 | return ret("number", "number"); 94 | } 95 | else if (ch == "/") { 96 | if (stream.eat("*")) { 97 | return chain(stream, state, jsTokenComment); 98 | } 99 | else if (stream.eat("/")) { 100 | stream.skipToEnd(); 101 | return ret("comment", "comment"); 102 | } 103 | else if (state.lastType == "operator" || state.lastType == "keyword c" || 104 | /^[\[{}\(,;:]$/.test(state.lastType)) { 105 | nextUntilUnescaped(stream, "/"); 106 | stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla 107 | return ret("regexp", "string-2"); 108 | } 109 | else { 110 | stream.eatWhile(isOperatorChar); 111 | return ret("operator", null, stream.current()); 112 | } 113 | } 114 | else if (ch == "#") { 115 | stream.skipToEnd(); 116 | return ret("error", "error"); 117 | } 118 | else if (isOperatorChar.test(ch)) { 119 | stream.eatWhile(isOperatorChar); 120 | return ret("operator", null, stream.current()); 121 | } 122 | else { 123 | stream.eatWhile(/[\w\$_]/); 124 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 125 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 126 | ret("variable", "variable", word); 127 | } 128 | } 129 | 130 | function jsTokenString(quote) { 131 | return function(stream, state) { 132 | if (!nextUntilUnescaped(stream, quote)) 133 | state.tokenize = jsTokenBase; 134 | return ret("string", "string"); 135 | }; 136 | } 137 | 138 | function jsTokenComment(stream, state) { 139 | var maybeEnd = false, ch; 140 | while (ch = stream.next()) { 141 | if (ch == "/" && maybeEnd) { 142 | state.tokenize = jsTokenBase; 143 | break; 144 | } 145 | maybeEnd = (ch == "*"); 146 | } 147 | return ret("comment", "comment"); 148 | } 149 | 150 | // Parser 151 | 152 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; 153 | 154 | function JSLexical(indented, column, type, align, prev, info) { 155 | this.indented = indented; 156 | this.column = column; 157 | this.type = type; 158 | this.prev = prev; 159 | this.info = info; 160 | if (align != null) this.align = align; 161 | } 162 | 163 | function inScope(state, varname) { 164 | for (var v = state.localVars; v; v = v.next) 165 | if (v.name == varname) return true; 166 | } 167 | 168 | function parseJS(state, style, type, content, stream) { 169 | var cc = state.cc; 170 | // Communicate our context to the combinators. 171 | // (Less wasteful than consing up a hundred closures on every call.) 172 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; 173 | 174 | if (!state.lexical.hasOwnProperty("align")) 175 | state.lexical.align = true; 176 | 177 | while(true) { 178 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 179 | if (combinator(type, content)) { 180 | while(cc.length && cc[cc.length - 1].lex) 181 | cc.pop()(); 182 | if (cx.marked) return cx.marked; 183 | if (type == "variable" && inScope(state, content)) return "variable-2"; 184 | return style; 185 | } 186 | } 187 | } 188 | 189 | // Combinator utils 190 | 191 | var cx = {state: null, column: null, marked: null, cc: null}; 192 | function pass() { 193 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 194 | } 195 | function cont() { 196 | pass.apply(null, arguments); 197 | return true; 198 | } 199 | function register(varname) { 200 | function inList(list) { 201 | for (var v = list; v; v = v.next) 202 | if (v.name == varname) return true; 203 | return false; 204 | } 205 | var state = cx.state; 206 | if (state.context) { 207 | cx.marked = "def"; 208 | if (inList(state.localVars)) return; 209 | state.localVars = {name: varname, next: state.localVars}; 210 | } else { 211 | if (inList(state.globalVars)) return; 212 | state.globalVars = {name: varname, next: state.globalVars}; 213 | } 214 | } 215 | 216 | // Combinators 217 | 218 | var defaultVars = {name: "this", next: {name: "arguments"}}; 219 | function pushcontext() { 220 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 221 | cx.state.localVars = defaultVars; 222 | } 223 | function popcontext() { 224 | cx.state.localVars = cx.state.context.vars; 225 | cx.state.context = cx.state.context.prev; 226 | } 227 | function pushlex(type, info) { 228 | var result = function() { 229 | var state = cx.state; 230 | state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); 231 | }; 232 | result.lex = true; 233 | return result; 234 | } 235 | function poplex() { 236 | var state = cx.state; 237 | if (state.lexical.prev) { 238 | if (state.lexical.type == ")") 239 | state.indented = state.lexical.indented; 240 | state.lexical = state.lexical.prev; 241 | } 242 | } 243 | poplex.lex = true; 244 | 245 | function expect(wanted) { 246 | return function(type) { 247 | if (type == wanted) return cont(); 248 | else if (wanted == ";") return pass(); 249 | else return cont(arguments.callee); 250 | }; 251 | } 252 | 253 | function statement(type) { 254 | if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); 255 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 256 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 257 | if (type == "{") return cont(pushlex("}"), block, poplex); 258 | if (type == ";") return cont(); 259 | if (type == "function") return cont(functiondef); 260 | if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), 261 | poplex, statement, poplex); 262 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 263 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 264 | block, poplex, poplex); 265 | if (type == "case") return cont(expression, expect(":")); 266 | if (type == "default") return cont(expect(":")); 267 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 268 | statement, poplex, popcontext); 269 | return pass(pushlex("stat"), expression, expect(";"), poplex); 270 | } 271 | function expression(type) { 272 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); 273 | if (type == "function") return cont(functiondef); 274 | if (type == "keyword c") return cont(maybeexpression); 275 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); 276 | if (type == "operator") return cont(expression); 277 | if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 278 | if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 279 | return cont(); 280 | } 281 | function maybeexpression(type) { 282 | if (type.match(/[;\}\)\],]/)) return pass(); 283 | return pass(expression); 284 | } 285 | 286 | function maybeoperator(type, value) { 287 | if (type == "operator") { 288 | if (/\+\+|--/.test(value)) return cont(maybeoperator); 289 | if (value == "?") return cont(expression, expect(":"), expression); 290 | return cont(expression); 291 | } 292 | if (type == ";") return; 293 | if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); 294 | if (type == ".") return cont(property, maybeoperator); 295 | if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 296 | } 297 | function maybelabel(type) { 298 | if (type == ":") return cont(poplex, statement); 299 | return pass(maybeoperator, expect(";"), poplex); 300 | } 301 | function property(type) { 302 | if (type == "variable") {cx.marked = "property"; return cont();} 303 | } 304 | function objprop(type) { 305 | if (type == "variable") cx.marked = "property"; 306 | else if (type == "number" || type == "string") cx.marked = type + " property"; 307 | if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); 308 | } 309 | function commasep(what, end) { 310 | function proceed(type) { 311 | if (type == ",") return cont(what, proceed); 312 | if (type == end) return cont(); 313 | return cont(expect(end)); 314 | } 315 | return function(type) { 316 | if (type == end) return cont(); 317 | else return pass(what, proceed); 318 | }; 319 | } 320 | function block(type) { 321 | if (type == "}") return cont(); 322 | return pass(statement, block); 323 | } 324 | function maybetype(type) { 325 | if (type == ":") return cont(typedef); 326 | return pass(); 327 | } 328 | function typedef(type) { 329 | if (type == "variable"){cx.marked = "variable-3"; return cont();} 330 | return pass(); 331 | } 332 | function vardef1(type, value) { 333 | if (type == "variable") { 334 | register(value); 335 | return isTS ? cont(maybetype, vardef2) : cont(vardef2); 336 | } 337 | return pass(); 338 | } 339 | function vardef2(type, value) { 340 | if (value == "=") return cont(expression, vardef2); 341 | if (type == ",") return cont(vardef1); 342 | } 343 | function forspec1(type) { 344 | if (type == "var") return cont(vardef1, expect(";"), forspec2); 345 | if (type == ";") return cont(forspec2); 346 | if (type == "variable") return cont(formaybein); 347 | return cont(forspec2); 348 | } 349 | function formaybein(_type, value) { 350 | if (value == "in") return cont(expression); 351 | return cont(maybeoperator, forspec2); 352 | } 353 | function forspec2(type, value) { 354 | if (type == ";") return cont(forspec3); 355 | if (value == "in") return cont(expression); 356 | return cont(expression, expect(";"), forspec3); 357 | } 358 | function forspec3(type) { 359 | if (type != ")") cont(expression); 360 | } 361 | function functiondef(type, value) { 362 | if (type == "variable") {register(value); return cont(functiondef);} 363 | if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); 364 | } 365 | function funarg(type, value) { 366 | if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} 367 | } 368 | 369 | // Interface 370 | 371 | return { 372 | startState: function(basecolumn) { 373 | return { 374 | tokenize: jsTokenBase, 375 | lastType: null, 376 | cc: [], 377 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 378 | localVars: parserConfig.localVars, 379 | globalVars: parserConfig.globalVars, 380 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 381 | indented: 0 382 | }; 383 | }, 384 | 385 | token: function(stream, state) { 386 | if (stream.sol()) { 387 | if (!state.lexical.hasOwnProperty("align")) 388 | state.lexical.align = false; 389 | state.indented = stream.indentation(); 390 | } 391 | if (stream.eatSpace()) return null; 392 | var style = state.tokenize(stream, state); 393 | if (type == "comment") return style; 394 | state.lastType = type; 395 | return parseJS(state, style, type, content, stream); 396 | }, 397 | 398 | indent: function(state, textAfter) { 399 | if (state.tokenize == jsTokenComment) return CodeMirror.Pass; 400 | if (state.tokenize != jsTokenBase) return 0; 401 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 402 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 403 | var type = lexical.type, closing = firstChar == type; 404 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0); 405 | else if (type == "form" && firstChar == "{") return lexical.indented; 406 | else if (type == "form") return lexical.indented + indentUnit; 407 | else if (type == "stat") 408 | return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0); 409 | else if (lexical.info == "switch" && !closing) 410 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 411 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 412 | else return lexical.indented + (closing ? 0 : indentUnit); 413 | }, 414 | 415 | electricChars: ":{}", 416 | 417 | jsonMode: jsonMode 418 | }; 419 | }); 420 | 421 | CodeMirror.defineMIME("text/javascript", "javascript"); 422 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 423 | CodeMirror.defineMIME("application/javascript", "javascript"); 424 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 425 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 426 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 427 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 428 | } 429 | --------------------------------------------------------------------------------