├── 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 | 
4 |
5 | [](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 | 
18 |
19 | It also supports async instrumentation, since node is node.
20 |
21 | 
22 |
23 | 
24 |
25 | 
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 |
--------------------------------------------------------------------------------