├── README.md
├── assets
└── index.css
├── index.html
├── index.js
├── lib
├── analyzer.js
├── cm-js.js
└── editor.js
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # essim
2 |
3 | a mostly-for-funsies live JS visualizer. there are bugs.
4 |
5 | [check out the example here](http://didact.us/essim)
6 |
7 | ## License
8 |
9 | MIT
10 |
--------------------------------------------------------------------------------
/assets/index.css:
--------------------------------------------------------------------------------
1 | /* swiped wholesale from hughsk's wonderful work on glslbin! */
2 | html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:0 0}body{line-height:1}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:0 0}ins{text-decoration:none}ins,mark{background-color:#ff9;color:#000}mark{font-style:italic;font-weight:700}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,select{vertical-align:middle}
3 | .CodeMirror{font-family:'Fantasque Sans Mono',monospace;height:300px;color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror div.CodeMirror-cursor{border-left:1px solid #000}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.CodeMirror.cm-fat-cursor div.CodeMirror-cursor{width:auto;border:0;background:#7e7}.CodeMirror.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1)infinite;-moz-animation:blink 1.06s steps(1)infinite;animation:blink 1.06s steps(1)infinite}@-moz-keyframes blink{0%{background:#7e7}50%{background:0 0}100%{background:#7e7}}@-webkit-keyframes blink{0%{background:#7e7}50%{background:0 0}100%{background:#7e7}}@keyframes blink{0%{background:#7e7}50%{background:0 0}100%{background:#7e7}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-error,.cm-invalidchar{color:red}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:none}.CodeMirror-scroll,.CodeMirror-sizer{position:relative;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-sizer{border-right:30px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;-moz-box-sizing:content-box;box-sizing:content-box;display:inline-block;margin-bottom:-30px;*zoom:1;*display:inline}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;height:100%}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper{-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:none}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-measure pre{position:static}.CodeMirror div.CodeMirror-cursor{position:absolute;border-right:none;width:0}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror ::selection{background:#d7d4f0}.CodeMirror ::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.CodeMirror span{*vertical-align:text-bottom}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}
4 |
5 | /*
6 | Original theme by: Zeno Rocha
7 | Brackets theme adaptation by: Felipe KM
8 | Statusbar, Toolbar, Sidebar css added by: Rafael Artoo Derolez
9 | */
10 |
11 | .cm-s-dracula .CodeMirror-scroll{
12 | margin-right: 5px;
13 | border-color: #202020;
14 | }
15 | .cm-s-dracula .CodeMirror-vscrollbar{
16 | overflow-y: hidden;
17 | }
18 | .cm-s-dracula ::-webkit-scrollbar {
19 | width: 10px;
20 | }
21 | .cm-s-dracula ::-webkit-scrollbar-track {
22 | background: transparent;
23 | }
24 | .cm-s-dracula ::-webkit-scrollbar-thumb {
25 | background: #383a48;
26 | border-radius: 5px;
27 | }
28 | .cm-s-dracula ::-webkit-scrollbar-thumb:hover{
29 | background: #444756;
30 | }
31 | .cm-s-dracula ::-webkit-scrollbar-thumb:active{
32 | background: #5d6073;
33 | }
34 |
35 | .cm-s-dracula.CodeMirror {
36 | background: #34363B;
37 | color: #EEE;
38 | font-size: 14px;
39 | line-height: 1.5em;
40 | }
41 |
42 | .cm-s-dracula div.CodeMirror-cursor {
43 | border-left: 1px solid #f8f8f0;
44 | z-index: 3;
45 | }
46 |
47 | .CodeMirror-selected {
48 | background: #3d3d3d;
49 | opacity: .23;
50 | }
51 |
52 | .cm-s-dracula span.cm-keyword {color: #66C4FF;}
53 | .cm-s-dracula span.cm-atom {color: #FFE169;}
54 | .cm-s-dracula span.cm-number {color: #FFE169;}
55 | .cm-s-dracula span.cm-def {color: #ffb86c;}
56 | .cm-s-dracula span.cm-variable {color: #EEE;}
57 | .cm-s-dracula span.cm-variable-2 {color: #FFE169;}
58 | .cm-s-dracula span.cm-property {color: #8be9fd;}
59 | .cm-s-dracula span.cm-operator {color: #66C4FF;}
60 | .cm-s-dracula span.cm-comment {color: #A9B0C2;}
61 | .cm-s-dracula span.cm-string {color: #FFE169;}
62 | .cm-s-dracula span.cm-string-2 {color: #FFE169;}
63 | .cm-s-dracula span.cm-meta {color: #61FF90;}
64 | .cm-s-dracula span.cm-qualifier, .CodeMirror span.cm-builtin {color: #61FF90;}
65 | .cm-s-dracula span.cm-bracket {color: #f9faf4;}
66 | .cm-s-dracula span.cm-tag {color: #66C4FF !important;}
67 | .cm-s-dracula span.cm-attribute {color: #61FF90;}
68 | .cm-s-dracula span.cm-matchhighlight {background-color: rgba(68,71,90.23);}
69 |
70 | .cm-s-dracula span.CodeMirror-matchingbracket {
71 | color: #61FF90;
72 | font-weight: bold;
73 | }
74 |
75 | .cm-s-dracula span.CodeMirror-matchingtag {
76 | color: #61FF90 !important;
77 | text-decoration:underline;
78 | background:none;
79 | }
80 |
81 |
82 | .cm-s-dracula span.CodeMirror-searching {
83 | background-color: none;
84 | background: none;
85 | box-shadow: 0 0 0 1px #fff;
86 | }
87 |
88 | .cm-s-dracula .CodeMirror-gutters {
89 | background: #34363B;
90 | border-right: 0.5rem solid #34363B;
91 | }
92 |
93 | .cm-s-dracula .CodeMirror-linenumber {
94 | color: #5B6173;
95 | }
96 |
97 | /**
98 | * Editor Styles
99 | */
100 | .editor {
101 | position: fixed;
102 | top: 0; right: 0; left: 50%;
103 | bottom: 0;
104 | }
105 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const analyze = require('./lib/analyzer.js')
2 | const initializeEditor = require('./lib/editor.js')
3 | const graphlibDot = require('graphlib-dot')
4 | const d3dagre = require('dagre-d3')
5 | const through = require('through')
6 | const d3 = require('d3')
7 |
8 | initializeEditor(document.body)
9 | .pipe(through(oncode))
10 |
11 | const renderFlow = d3dagre.render()
12 | const renderObject = d3dagre.render()
13 | const svg = d3.select('body').append('svg')
14 | const playPause = document.createElement('button')
15 | let playing = false
16 | let disabled = false
17 | let lastGoodCode = null
18 | let objectGraphStream = null
19 |
20 | playPause.setAttribute('id', 'play-pause')
21 | playPause.textContent = ' '
22 | playPause.addEventListener('click', onclickbtn)
23 | document.body.appendChild(playPause)
24 |
25 | const zoomLayer =
26 | svg
27 | .attr('width', '50%')
28 | .attr('height', window.innerHeight)
29 | .append('g')
30 | .call(d3.behavior.zoom().scaleExtent([0.125, 1.25]).on('zoom', onzoom))
31 |
32 | zoomLayer
33 | .append('rect')
34 | .attr('width', '100%')
35 | .attr('height', '100%')
36 | .attr('fill', '#666676')
37 |
38 | const flowGraph =
39 | zoomLayer
40 | .append('g')
41 | .attr('id', 'flow-graph')
42 |
43 | const objectGraph =
44 | zoomLayer
45 | .append('g')
46 | .attr('id', 'object-graph')
47 |
48 | window.onresize = function() {
49 | svg.attr('height', window.innerHeight)
50 | }
51 |
52 | function oncode(code) {
53 | if (objectGraphStream) {
54 | objectGraphStream.stop()
55 | objectGraphStream = null
56 | }
57 | analyze.createCFG(code, function(err, cfg) {
58 | if (err) {
59 | document.body.classList.add('play-disabled')
60 | disabled = true
61 | return
62 | }
63 | disabled = false
64 | document.body.classList.remove('play-disabled')
65 | lastGoodCode = code
66 | const results = graphlibDot.read(cfg.toDot())
67 | if (!results.graph().hasOwnProperty('marginx') &&
68 | !results.graph().hasOwnProperty('marginy')) {
69 | results.graph().marginx = 20
70 | results.graph().marginy = 20
71 | }
72 |
73 | results.graph().transition = function(selection) {
74 | return selection.transition().duration(500)
75 | }
76 |
77 | flowGraph.call(renderFlow, results)
78 | })
79 | }
80 |
81 | function onzoom() {
82 | flowGraph.attr(
83 | 'transform',
84 | `translate(${d3.event.translate})scale(${d3.event.scale})`
85 | )
86 | objectGraph.attr(
87 | 'transform',
88 | `translate(${d3.event.translate})scale(${d3.event.scale})`
89 | )
90 | }
91 |
92 | function onclickbtn(ev) {
93 | ev.preventDefault()
94 | if (disabled) {
95 | return
96 | }
97 | playing = !playing
98 | if (!playing) {
99 | objectGraph.selectAll('*').remove()
100 | document.body.classList.remove('playing')
101 | return
102 | } else {
103 | document.body.classList.add('playing')
104 | }
105 |
106 | objectGraphStream = analyze.createObjectGraph(lastGoodCode)
107 | objectGraph.selectAll('*').remove()
108 |
109 | objectGraphStream.on('data', function({vertices, edges, builtins, root}) {
110 | const output = ['digraph {']
111 | let ID = 1;
112 | const mapping = new Map()
113 | for (const vertex of vertices) {
114 | const id = ID++
115 | mapping.set(vertex, id)
116 | output.push(id + ` [label=${
117 | JSON.stringify(
118 | vertex.stack || vertex.toparent ? 'Stack Item' :
119 | vertex.root ? 'Root' :
120 | vertex.classInfo ? vertex.classInfo() :
121 | vertex.getName ? 'name: ' + vertex.getName() : '???'
122 | )
123 | }]`)
124 | }
125 | for (const edge of edges) {
126 | if (!mapping.get(edge[0]) || !mapping.get(edge[1])) {
127 | continue
128 | }
129 |
130 | output.push(`${mapping.get(edge[0])} -> ${mapping.get(edge[1])} [label=${
131 | JSON.stringify(edge[2])
132 | }]`)
133 | }
134 | output.push('}')
135 | const results = graphlibDot.read(output.join('\n'))
136 | if (!results.graph().hasOwnProperty('marginx') &&
137 | !results.graph().hasOwnProperty('marginy')) {
138 | results.graph().marginx = 20
139 | results.graph().marginy = 20
140 | }
141 |
142 | results.graph().transition = function(selection) {
143 | return selection.transition().duration(500)
144 | }
145 |
146 | objectGraph.call(renderObject, results)
147 |
148 | })
149 | }
150 |
--------------------------------------------------------------------------------
/lib/analyzer.js:
--------------------------------------------------------------------------------
1 | module.exports = {createCFG, createObjectGraph}
2 |
3 | const escontrol = require('escontrol')
4 | const through = require('through')
5 | const espree = require('espree')
6 | const d3 = require('d3')
7 |
8 | const parseOpts = {
9 | loc: true,
10 | ecmaFeatures: {
11 | arrowFunctions: true,
12 | blockBindings: true,
13 | destructuring: true,
14 | regexYFlag: true,
15 | regexUFlag: true,
16 | templateStrings: true,
17 | binaryLiterals: true,
18 | octalLiterals: true,
19 | unicodeCodePointEscapes: true,
20 | defaultParams: true,
21 | restParams: true,
22 | forOf: true,
23 | objectLiteralComputedProperties: true,
24 | objectLiteralShorthandMethods: true,
25 | objectLiteralShorthandProperties: true,
26 | objectLiteralDuplicateProperties: true,
27 | generators: true,
28 | spread: true,
29 | classes: true,
30 | modules: false,
31 | jsx: false,
32 | globalReturn: true
33 | }
34 | }
35 |
36 | function createCFG(code, ready) {
37 | let cfg = null
38 | try {
39 | cfg = escontrol(espree.parse(code, parseOpts))
40 | } catch(err) {
41 | return ready(err)
42 | }
43 |
44 | return iterate()
45 |
46 | function iterate() {
47 | try {
48 | let times = 0
49 | while (cfg.advance()) {
50 | if (++times > 1000) {
51 | return setTimeout(iterate)
52 | }
53 | }
54 | return ready(null, cfg)
55 | } catch(err) {
56 | return ready(err)
57 | }
58 | }
59 | }
60 |
61 | function createObjectGraph(code) {
62 | let cfg = null
63 | const stream = through()
64 | const vertices = new Set()
65 | const edges = new Set()
66 | const fromVia = new WeakMap()
67 | let cancelled = false
68 | let stack = {stack: true}
69 | let root = {root: true}
70 | let isBuiltin = true
71 | let sawChange = true
72 | let sawGCEvent = false
73 |
74 | fromVia.set(root, new Map())
75 | fromVia.set(stack, new Map())
76 | const builtins = new Set()
77 | try {
78 | cfg = escontrol(espree.parse(code, parseOpts), {
79 | onvalue : onvalue,
80 | onpushvalue : onpushvalue,
81 | onpopvalue : onpopvalue,
82 | onlink : onlink,
83 | onunlink : onunlink,
84 | oncalled : oncalled
85 | })
86 | } catch(err) {
87 | setTimeout(_ => stream.emit('error', err))
88 | }
89 | isBuiltin = false
90 | vertices.add(stack)
91 | vertices.add(root)
92 |
93 | setTimeout(iterate)
94 |
95 | stream.stop = stop
96 | cfg.builtins().getprop('[[ArrayProto]]').value().classInfo = _ => '[[ArrayProto]]'
97 | cfg.builtins().getprop('[[ObjectProto]]').value().classInfo = _ => '[[ObjectProto]]'
98 | cfg.builtins().getprop('[[StringProto]]').value().classInfo = _ => '[[StringProto]]'
99 | cfg.builtins().getprop('[[RegExpProto]]').value().classInfo = _ => '[[RegExpProto]]'
100 | cfg.builtins().getprop('[[NumberProto]]').value().classInfo = _ => '[[NumberProto]]'
101 | cfg.builtins().getprop('[[FunctionProto]]').value().classInfo = _ => '[[FunctionProto]]'
102 | cfg.global().classInfo = _ => 'Global Scope'
103 | return stream
104 |
105 | function iterate() {
106 | if (cancelled) return
107 | sawChange = false
108 | while (cfg.advance()) {
109 | if (sawChange) {
110 | if (sawGCEvent) gc(cfg.global())
111 | stream.queue({vertices, edges})
112 | return setTimeout(iterate, 1000)
113 | }
114 | }
115 |
116 | stream.queue({vertices, edges, builtins})
117 | stream.queue(null)
118 | }
119 |
120 | function stop() {
121 | cancelled = true
122 | }
123 |
124 | function onvalue(value) {
125 | sawChange = true
126 | fromVia.set(value, new Map())
127 | if (isBuiltin) {
128 | builtins.add(value)
129 | return
130 | }
131 | vertices.add(value)
132 | if (value.isEither()) {
133 | for (const xs of value._outcomes) {
134 | onvalue(xs)
135 | onlink(value, xs, '[[maybe]]')
136 | }
137 | }
138 | }
139 |
140 | function onpushvalue(value) {
141 | sawChange = true
142 | let next = {parent: stack, value: value, toparent: null, tovalue: null}
143 | next.toparent = [stack, next, '']
144 | next.tovalue = [next, value, '']
145 | edges.add(next.toparent)
146 | edges.add(next.tovalue)
147 | vertices.add(next)
148 | stack = next
149 | }
150 |
151 | function onpopvalue(value) {
152 | sawChange = true
153 | let prev = stack
154 | if (!stack.parent) {
155 | throw new Error('cannot pop')
156 | }
157 | stack = stack.parent
158 | edges.delete(prev.toparent)
159 | edges.delete(prev.tovalue)
160 | vertices.delete(prev)
161 | }
162 |
163 | function onlink(from, to, via) {
164 | sawChange = true
165 | from = from || root
166 | to = to || root
167 | if (isBuiltin) {
168 | if (from.root || to.root) {
169 | vertices.add(from)
170 | vertices.add(to)
171 | }
172 | } else {
173 | if (builtins.has(from) || builtins.has(to)) {
174 | vertices.add(from)
175 | vertices.add(to)
176 | }
177 | }
178 | let tuple = [from, to, via]
179 | fromVia.get(from).set(via, tuple)
180 | edges.add(tuple)
181 | }
182 |
183 | function oncalled() {
184 | sawGCEvent = new Error().stack
185 | }
186 |
187 | function onunlink(from, to, via) {
188 | sawChange = true
189 | from = from || root
190 | to = to || root
191 | edges.delete(fromVia.get(from).get(via))
192 | fromVia.get(from).delete(via)
193 | }
194 |
195 | function gc(root) {
196 | const trace = sawGCEvent
197 | sawGCEvent = false
198 |
199 | const seenValues = new Set()
200 | const marked = new WeakSet()
201 | for (const obj of iterateObjects(root, seenValues)) {
202 | marked.add(obj)
203 | }
204 | let currentStack = stack
205 | while (currentStack) {
206 | if (currentStack.value && currentStack.value.names) {
207 | const seenValues = new Set()
208 | for (const obj of iterateObjects(currentStack.value, seenValues)) {
209 | marked.add(obj)
210 | }
211 | }
212 | currentStack = currentStack.parent
213 | }
214 | marked.add(root)
215 | for (const obj of vertices) {
216 | if (!marked.has(obj) && !obj.stack && !obj.toparent) {
217 | console.log('DELETE obj', obj, cfg.stackInfo(), trace)
218 | vertices.delete(obj)
219 | }
220 | }
221 | }
222 |
223 | function iterateObjects(obj, seenValues) {
224 | const stack = [obj.names()]
225 | seenValues.add(null)
226 | return {
227 | [Symbol.iterator]() {
228 | return this
229 | },
230 | next() {
231 | for (const name of stack[0]) {
232 | let value = name
233 | if (name.value) {
234 | value = name.value()
235 | }
236 | if (seenValues.has(value)) {
237 | continue
238 | }
239 | seenValues.add(value)
240 | stack.unshift(value.isEither() ? value._outcomes.values() : value.names())
241 | return {
242 | value: value,
243 | done: false
244 | }
245 | }
246 | stack.shift()
247 | if (!stack.length) {
248 | return {
249 | done: true,
250 | value: undefined
251 | }
252 | }
253 | return this.next()
254 | }
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/lib/cm-js.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = function (CodeMirror) {
3 | "use strict";
4 |
5 | CodeMirror.defineMode("javascript", function(config, parserConfig) {
6 | var indentUnit = config.indentUnit;
7 | var statementIndent = parserConfig.statementIndent;
8 | var jsonldMode = parserConfig.jsonld;
9 | var jsonMode = parserConfig.json || jsonldMode;
10 | var isTS = parserConfig.typescript;
11 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
12 |
13 | // Tokenizer
14 |
15 | var keywords = function(){
16 | function kw(type) {return {type: type, style: "keyword"};}
17 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
18 | var operator = kw("operator"), atom = {type: "atom", style: "atom"};
19 |
20 | var jsKeywords = {
21 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
22 | "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
23 | "var": kw("var"), "const": kw("var"), "let": kw("var"),
24 | "function": kw("function"), "catch": kw("catch"),
25 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
26 | "in": operator, "typeof": operator, "instanceof": operator,
27 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
28 | "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
29 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
30 | };
31 |
32 | // Extend the 'normal' keywords with the TypeScript language extensions
33 | if (isTS) {
34 | var type = {type: "variable", style: "variable-3"};
35 | var tsKeywords = {
36 | // object-like things
37 | "interface": kw("interface"),
38 | "extends": kw("extends"),
39 | "constructor": kw("constructor"),
40 |
41 | // scope modifiers
42 | "public": kw("public"),
43 | "private": kw("private"),
44 | "protected": kw("protected"),
45 | "static": kw("static"),
46 |
47 | // types
48 | "string": type, "number": type, "bool": type, "any": type
49 | };
50 |
51 | for (var attr in tsKeywords) {
52 | jsKeywords[attr] = tsKeywords[attr];
53 | }
54 | }
55 |
56 | return jsKeywords;
57 | }();
58 |
59 | var isOperatorChar = /[+\-*&%=<>!?|~^]/;
60 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
61 |
62 | function readRegexp(stream) {
63 | var escaped = false, next, inSet = false;
64 | while ((next = stream.next()) != null) {
65 | if (!escaped) {
66 | if (next == "/" && !inSet) return;
67 | if (next == "[") inSet = true;
68 | else if (inSet && next == "]") inSet = false;
69 | }
70 | escaped = !escaped && next == "\\";
71 | }
72 | }
73 |
74 | // Used as scratch variables to communicate multiple values without
75 | // consing up tons of objects.
76 | var type, content;
77 | function ret(tp, style, cont) {
78 | type = tp; content = cont;
79 | return style;
80 | }
81 | function tokenBase(stream, state) {
82 | var ch = stream.next();
83 | if (ch == '"' || ch == "'") {
84 | state.tokenize = tokenString(ch);
85 | return state.tokenize(stream, state);
86 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
87 | return ret("number", "number");
88 | } else if (ch == "." && stream.match("..")) {
89 | return ret("spread", "meta");
90 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
91 | return ret(ch);
92 | } else if (ch == "=" && stream.eat(">")) {
93 | return ret("=>", "operator");
94 | } else if (ch == "0" && stream.eat(/x/i)) {
95 | stream.eatWhile(/[\da-f]/i);
96 | return ret("number", "number");
97 | } else if (/\d/.test(ch)) {
98 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
99 | return ret("number", "number");
100 | } else if (ch == "/") {
101 | if (stream.eat("*")) {
102 | state.tokenize = tokenComment;
103 | return tokenComment(stream, state);
104 | } else if (stream.eat("/")) {
105 | stream.skipToEnd();
106 | return ret("comment", "comment");
107 | } else if (state.lastType == "operator" || state.lastType == "keyword c" ||
108 | state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
109 | readRegexp(stream);
110 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
111 | return ret("regexp", "string-2");
112 | } else {
113 | stream.eatWhile(isOperatorChar);
114 | return ret("operator", "operator", stream.current());
115 | }
116 | } else if (ch == "`") {
117 | state.tokenize = tokenQuasi;
118 | return tokenQuasi(stream, state);
119 | } else if (ch == "#") {
120 | stream.skipToEnd();
121 | return ret("error", "error");
122 | } else if (isOperatorChar.test(ch)) {
123 | stream.eatWhile(isOperatorChar);
124 | return ret("operator", "operator", stream.current());
125 | } else if (wordRE.test(ch)) {
126 | stream.eatWhile(wordRE);
127 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
128 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
129 | ret("variable", "variable", word);
130 | }
131 | }
132 |
133 | function tokenString(quote) {
134 | return function(stream, state) {
135 | var escaped = false, next;
136 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
137 | state.tokenize = tokenBase;
138 | return ret("jsonld-keyword", "meta");
139 | }
140 | while ((next = stream.next()) != null) {
141 | if (next == quote && !escaped) break;
142 | escaped = !escaped && next == "\\";
143 | }
144 | if (!escaped) state.tokenize = tokenBase;
145 | return ret("string", "string");
146 | };
147 | }
148 |
149 | function tokenComment(stream, state) {
150 | var maybeEnd = false, ch;
151 | while (ch = stream.next()) {
152 | if (ch == "/" && maybeEnd) {
153 | state.tokenize = tokenBase;
154 | break;
155 | }
156 | maybeEnd = (ch == "*");
157 | }
158 | return ret("comment", "comment");
159 | }
160 |
161 | function tokenQuasi(stream, state) {
162 | var escaped = false, next;
163 | while ((next = stream.next()) != null) {
164 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
165 | state.tokenize = tokenBase;
166 | break;
167 | }
168 | escaped = !escaped && next == "\\";
169 | }
170 | return ret("quasi", "string-2", stream.current());
171 | }
172 |
173 | var brackets = "([{}])";
174 | // This is a crude lookahead trick to try and notice that we're
175 | // parsing the argument patterns for a fat-arrow function before we
176 | // actually hit the arrow token. It only works if the arrow is on
177 | // the same line as the arguments and there's no strange noise
178 | // (comments) in between. Fallback is to only notice when we hit the
179 | // arrow, and not declare the arguments as locals for the arrow
180 | // body.
181 | function findFatArrow(stream, state) {
182 | if (state.fatArrowAt) state.fatArrowAt = null;
183 | var arrow = stream.string.indexOf("=>", stream.start);
184 | if (arrow < 0) return;
185 |
186 | var depth = 0, sawSomething = false;
187 | for (var pos = arrow - 1; pos >= 0; --pos) {
188 | var ch = stream.string.charAt(pos);
189 | var bracket = brackets.indexOf(ch);
190 | if (bracket >= 0 && bracket < 3) {
191 | if (!depth) { ++pos; break; }
192 | if (--depth == 0) break;
193 | } else if (bracket >= 3 && bracket < 6) {
194 | ++depth;
195 | } else if (wordRE.test(ch)) {
196 | sawSomething = true;
197 | } else if (/["'\/]/.test(ch)) {
198 | return;
199 | } else if (sawSomething && !depth) {
200 | ++pos;
201 | break;
202 | }
203 | }
204 | if (sawSomething && !depth) state.fatArrowAt = pos;
205 | }
206 |
207 | // Parser
208 |
209 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
210 |
211 | function JSLexical(indented, column, type, align, prev, info) {
212 | this.indented = indented;
213 | this.column = column;
214 | this.type = type;
215 | this.prev = prev;
216 | this.info = info;
217 | if (align != null) this.align = align;
218 | }
219 |
220 | function inScope(state, varname) {
221 | for (var v = state.localVars; v; v = v.next)
222 | if (v.name == varname) return true;
223 | for (var cx = state.context; cx; cx = cx.prev) {
224 | for (var v = cx.vars; v; v = v.next)
225 | if (v.name == varname) return true;
226 | }
227 | }
228 |
229 | function parseJS(state, style, type, content, stream) {
230 | var cc = state.cc;
231 | // Communicate our context to the combinators.
232 | // (Less wasteful than consing up a hundred closures on every call.)
233 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
234 |
235 | if (!state.lexical.hasOwnProperty("align"))
236 | state.lexical.align = true;
237 |
238 | while(true) {
239 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
240 | if (combinator(type, content)) {
241 | while(cc.length && cc[cc.length - 1].lex)
242 | cc.pop()();
243 | if (cx.marked) return cx.marked;
244 | if (type == "variable" && inScope(state, content)) return "variable-2";
245 | return style;
246 | }
247 | }
248 | }
249 |
250 | // Combinator utils
251 |
252 | var cx = {state: null, column: null, marked: null, cc: null};
253 | function pass() {
254 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
255 | }
256 | function cont() {
257 | pass.apply(null, arguments);
258 | return true;
259 | }
260 | function register(varname) {
261 | function inList(list) {
262 | for (var v = list; v; v = v.next)
263 | if (v.name == varname) return true;
264 | return false;
265 | }
266 | var state = cx.state;
267 | if (state.context) {
268 | cx.marked = "def";
269 | if (inList(state.localVars)) return;
270 | state.localVars = {name: varname, next: state.localVars};
271 | } else {
272 | if (inList(state.globalVars)) return;
273 | if (parserConfig.globalVars)
274 | state.globalVars = {name: varname, next: state.globalVars};
275 | }
276 | }
277 |
278 | // Combinators
279 |
280 | var defaultVars = {name: "this", next: {name: "arguments"}};
281 | function pushcontext() {
282 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
283 | cx.state.localVars = defaultVars;
284 | }
285 | function popcontext() {
286 | cx.state.localVars = cx.state.context.vars;
287 | cx.state.context = cx.state.context.prev;
288 | }
289 | function pushlex(type, info) {
290 | var result = function() {
291 | var state = cx.state, indent = state.indented;
292 | if (state.lexical.type == "stat") indent = state.lexical.indented;
293 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
294 | indent = outer.indented;
295 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
296 | };
297 | result.lex = true;
298 | return result;
299 | }
300 | function poplex() {
301 | var state = cx.state;
302 | if (state.lexical.prev) {
303 | if (state.lexical.type == ")")
304 | state.indented = state.lexical.indented;
305 | state.lexical = state.lexical.prev;
306 | }
307 | }
308 | poplex.lex = true;
309 |
310 | function expect(wanted) {
311 | function exp(type) {
312 | if (type == wanted) return cont();
313 | else if (wanted == ";") return pass();
314 | else return cont(exp);
315 | };
316 | return exp;
317 | }
318 |
319 | function statement(type, value) {
320 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
321 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
322 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
323 | if (type == "{") return cont(pushlex("}"), block, poplex);
324 | if (type == ";") return cont();
325 | if (type == "if") {
326 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
327 | cx.state.cc.pop()();
328 | return cont(pushlex("form"), expression, statement, poplex, maybeelse);
329 | }
330 | if (type == "function") return cont(functiondef);
331 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
332 | if (type == "variable") return cont(pushlex("stat"), maybelabel);
333 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
334 | block, poplex, poplex);
335 | if (type == "case") return cont(expression, expect(":"));
336 | if (type == "default") return cont(expect(":"));
337 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
338 | statement, poplex, popcontext);
339 | if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
340 | if (type == "class") return cont(pushlex("form"), className, poplex);
341 | if (type == "export") return cont(pushlex("form"), afterExport, poplex);
342 | if (type == "import") return cont(pushlex("form"), afterImport, poplex);
343 | return pass(pushlex("stat"), expression, expect(";"), poplex);
344 | }
345 | function expression(type) {
346 | return expressionInner(type, false);
347 | }
348 | function expressionNoComma(type) {
349 | return expressionInner(type, true);
350 | }
351 | function expressionInner(type, noComma) {
352 | if (cx.state.fatArrowAt == cx.stream.start) {
353 | var body = noComma ? arrowBodyNoComma : arrowBody;
354 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
355 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
356 | }
357 |
358 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
359 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
360 | if (type == "function") return cont(functiondef, maybeop);
361 | if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
362 | if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
363 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
364 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
365 | if (type == "{") return contCommasep(objprop, "}", null, maybeop);
366 | if (type == "quasi") { return pass(quasi, maybeop); }
367 | return cont();
368 | }
369 | function maybeexpression(type) {
370 | if (type.match(/[;\}\)\],]/)) return pass();
371 | return pass(expression);
372 | }
373 | function maybeexpressionNoComma(type) {
374 | if (type.match(/[;\}\)\],]/)) return pass();
375 | return pass(expressionNoComma);
376 | }
377 |
378 | function maybeoperatorComma(type, value) {
379 | if (type == ",") return cont(expression);
380 | return maybeoperatorNoComma(type, value, false);
381 | }
382 | function maybeoperatorNoComma(type, value, noComma) {
383 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
384 | var expr = noComma == false ? expression : expressionNoComma;
385 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
386 | if (type == "operator") {
387 | if (/\+\+|--/.test(value)) return cont(me);
388 | if (value == "?") return cont(expression, expect(":"), expr);
389 | return cont(expr);
390 | }
391 | if (type == "quasi") { return pass(quasi, me); }
392 | if (type == ";") return;
393 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
394 | if (type == ".") return cont(property, me);
395 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
396 | }
397 | function quasi(type, value) {
398 | if (type != "quasi") return pass();
399 | if (value.slice(value.length - 2) != "${") return cont(quasi);
400 | return cont(expression, continueQuasi);
401 | }
402 | function continueQuasi(type) {
403 | if (type == "}") {
404 | cx.marked = "string-2";
405 | cx.state.tokenize = tokenQuasi;
406 | return cont(quasi);
407 | }
408 | }
409 | function arrowBody(type) {
410 | findFatArrow(cx.stream, cx.state);
411 | return pass(type == "{" ? statement : expression);
412 | }
413 | function arrowBodyNoComma(type) {
414 | findFatArrow(cx.stream, cx.state);
415 | return pass(type == "{" ? statement : expressionNoComma);
416 | }
417 | function maybelabel(type) {
418 | if (type == ":") return cont(poplex, statement);
419 | return pass(maybeoperatorComma, expect(";"), poplex);
420 | }
421 | function property(type) {
422 | if (type == "variable") {cx.marked = "property"; return cont();}
423 | }
424 | function objprop(type, value) {
425 | if (type == "variable" || cx.style == "keyword") {
426 | cx.marked = "property";
427 | if (value == "get" || value == "set") return cont(getterSetter);
428 | return cont(afterprop);
429 | } else if (type == "number" || type == "string") {
430 | cx.marked = jsonldMode ? "property" : (cx.style + " property");
431 | return cont(afterprop);
432 | } else if (type == "jsonld-keyword") {
433 | return cont(afterprop);
434 | } else if (type == "[") {
435 | return cont(expression, expect("]"), afterprop);
436 | }
437 | }
438 | function getterSetter(type) {
439 | if (type != "variable") return pass(afterprop);
440 | cx.marked = "property";
441 | return cont(functiondef);
442 | }
443 | function afterprop(type) {
444 | if (type == ":") return cont(expressionNoComma);
445 | if (type == "(") return pass(functiondef);
446 | }
447 | function commasep(what, end) {
448 | function proceed(type) {
449 | if (type == ",") {
450 | var lex = cx.state.lexical;
451 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
452 | return cont(what, proceed);
453 | }
454 | if (type == end) return cont();
455 | return cont(expect(end));
456 | }
457 | return function(type) {
458 | if (type == end) return cont();
459 | return pass(what, proceed);
460 | };
461 | }
462 | function contCommasep(what, end, info) {
463 | for (var i = 3; i < arguments.length; i++)
464 | cx.cc.push(arguments[i]);
465 | return cont(pushlex(end, info), commasep(what, end), poplex);
466 | }
467 | function block(type) {
468 | if (type == "}") return cont();
469 | return pass(statement, block);
470 | }
471 | function maybetype(type) {
472 | if (isTS && type == ":") return cont(typedef);
473 | }
474 | function typedef(type) {
475 | if (type == "variable"){cx.marked = "variable-3"; return cont();}
476 | }
477 | function vardef() {
478 | return pass(pattern, maybetype, maybeAssign, vardefCont);
479 | }
480 | function pattern(type, value) {
481 | if (type == "variable") { register(value); return cont(); }
482 | if (type == "[") return contCommasep(pattern, "]");
483 | if (type == "{") return contCommasep(proppattern, "}");
484 | }
485 | function proppattern(type, value) {
486 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
487 | register(value);
488 | return cont(maybeAssign);
489 | }
490 | if (type == "variable") cx.marked = "property";
491 | return cont(expect(":"), pattern, maybeAssign);
492 | }
493 | function maybeAssign(_type, value) {
494 | if (value == "=") return cont(expressionNoComma);
495 | }
496 | function vardefCont(type) {
497 | if (type == ",") return cont(vardef);
498 | }
499 | function maybeelse(type, value) {
500 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
501 | }
502 | function forspec(type) {
503 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
504 | }
505 | function forspec1(type) {
506 | if (type == "var") return cont(vardef, expect(";"), forspec2);
507 | if (type == ";") return cont(forspec2);
508 | if (type == "variable") return cont(formaybeinof);
509 | return pass(expression, expect(";"), forspec2);
510 | }
511 | function formaybeinof(_type, value) {
512 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
513 | return cont(maybeoperatorComma, forspec2);
514 | }
515 | function forspec2(type, value) {
516 | if (type == ";") return cont(forspec3);
517 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
518 | return pass(expression, expect(";"), forspec3);
519 | }
520 | function forspec3(type) {
521 | if (type != ")") cont(expression);
522 | }
523 | function functiondef(type, value) {
524 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
525 | if (type == "variable") {register(value); return cont(functiondef);}
526 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
527 | }
528 | function funarg(type) {
529 | if (type == "spread") return cont(funarg);
530 | return pass(pattern, maybetype);
531 | }
532 | function className(type, value) {
533 | if (type == "variable") {register(value); return cont(classNameAfter);}
534 | }
535 | function classNameAfter(type, value) {
536 | if (value == "extends") return cont(expression, classNameAfter);
537 | if (type == "{") return cont(pushlex("}"), classBody, poplex);
538 | }
539 | function classBody(type, value) {
540 | if (type == "variable" || cx.style == "keyword") {
541 | cx.marked = "property";
542 | if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
543 | return cont(functiondef, classBody);
544 | }
545 | if (value == "*") {
546 | cx.marked = "keyword";
547 | return cont(classBody);
548 | }
549 | if (type == ";") return cont(classBody);
550 | if (type == "}") return cont();
551 | }
552 | function classGetterSetter(type) {
553 | if (type != "variable") return pass();
554 | cx.marked = "property";
555 | return cont();
556 | }
557 | function afterModule(type, value) {
558 | if (type == "string") return cont(statement);
559 | if (type == "variable") { register(value); return cont(maybeFrom); }
560 | }
561 | function afterExport(_type, value) {
562 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
563 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
564 | return pass(statement);
565 | }
566 | function afterImport(type) {
567 | if (type == "string") return cont();
568 | return pass(importSpec, maybeFrom);
569 | }
570 | function importSpec(type, value) {
571 | if (type == "{") return contCommasep(importSpec, "}");
572 | if (type == "variable") register(value);
573 | return cont();
574 | }
575 | function maybeFrom(_type, value) {
576 | if (value == "from") { cx.marked = "keyword"; return cont(expression); }
577 | }
578 | function arrayLiteral(type) {
579 | if (type == "]") return cont();
580 | return pass(expressionNoComma, maybeArrayComprehension);
581 | }
582 | function maybeArrayComprehension(type) {
583 | if (type == "for") return pass(comprehension, expect("]"));
584 | if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
585 | return pass(commasep(expressionNoComma, "]"));
586 | }
587 | function comprehension(type) {
588 | if (type == "for") return cont(forspec, comprehension);
589 | if (type == "if") return cont(expression, comprehension);
590 | }
591 |
592 | function isContinuedStatement(state, textAfter) {
593 | return state.lastType == "operator" || state.lastType == "," ||
594 | isOperatorChar.test(textAfter.charAt(0)) ||
595 | /[,.]/.test(textAfter.charAt(0));
596 | }
597 |
598 | // Interface
599 |
600 | return {
601 | startState: function(basecolumn) {
602 | var state = {
603 | tokenize: tokenBase,
604 | lastType: "sof",
605 | cc: [],
606 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
607 | localVars: parserConfig.localVars,
608 | context: parserConfig.localVars && {vars: parserConfig.localVars},
609 | indented: 0
610 | };
611 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
612 | state.globalVars = parserConfig.globalVars;
613 | return state;
614 | },
615 |
616 | token: function(stream, state) {
617 | if (stream.sol()) {
618 | if (!state.lexical.hasOwnProperty("align"))
619 | state.lexical.align = false;
620 | state.indented = stream.indentation();
621 | findFatArrow(stream, state);
622 | }
623 | if (state.tokenize != tokenComment && stream.eatSpace()) return null;
624 | var style = state.tokenize(stream, state);
625 | if (type == "comment") return style;
626 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
627 | return parseJS(state, style, type, content, stream);
628 | },
629 |
630 | indent: function(state, textAfter) {
631 | if (state.tokenize == tokenComment) return CodeMirror.Pass;
632 | if (state.tokenize != tokenBase) return 0;
633 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
634 | // Kludge to prevent 'maybelse' from blocking lexical scope pops
635 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
636 | var c = state.cc[i];
637 | if (c == poplex) lexical = lexical.prev;
638 | else if (c != maybeelse) break;
639 | }
640 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
641 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
642 | lexical = lexical.prev;
643 | var type = lexical.type, closing = firstChar == type;
644 |
645 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
646 | else if (type == "form" && firstChar == "{") return lexical.indented;
647 | else if (type == "form") return lexical.indented + indentUnit;
648 | else if (type == "stat")
649 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
650 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
651 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
652 | else if (lexical.align) return lexical.column + (closing ? 0 : 1);
653 | else return lexical.indented + (closing ? 0 : indentUnit);
654 | },
655 |
656 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
657 | blockCommentStart: jsonMode ? null : "/*",
658 | blockCommentEnd: jsonMode ? null : "*/",
659 | lineComment: jsonMode ? null : "//",
660 | fold: "brace",
661 |
662 | helperType: jsonMode ? "json" : "javascript",
663 | jsonldMode: jsonldMode,
664 | jsonMode: jsonMode
665 | };
666 | });
667 |
668 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
669 |
670 | CodeMirror.defineMIME("text/javascript", "javascript");
671 | CodeMirror.defineMIME("text/ecmascript", "javascript");
672 | CodeMirror.defineMIME("application/javascript", "javascript");
673 | CodeMirror.defineMIME("application/x-javascript", "javascript");
674 | CodeMirror.defineMIME("application/ecmascript", "javascript");
675 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
676 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
677 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
678 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
679 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
680 | };
681 |
--------------------------------------------------------------------------------
/lib/editor.js:
--------------------------------------------------------------------------------
1 | module.exports = init
2 |
3 | const frameDebounce = require('frame-debounce')
4 | const getSize = require('element-size')
5 | const CodeMirror = require('codemirror')
6 | const debounce = require('debounce')
7 | const through = require('through')
8 |
9 | require('./cm-js.js')(CodeMirror)
10 |
11 | function init(el) {
12 | const targetEl = document.createElement('div')
13 | el.appendChild(targetEl)
14 | targetEl.classList.add('editor')
15 | const editor = new CodeMirror(targetEl, {
16 | container: targetEl,
17 | theme: 'dracula',
18 | mode: 'javascript',
19 | lineNumbers: true,
20 | matchBrackets: true,
21 | indentWithTabs: false,
22 | styleActiveLine: true,
23 | showCursorWhenSelecting: true,
24 | viewportMargin: Infinity,
25 | keyMap: 'default',
26 | indentUnit: 2,
27 | tabSize: 2,
28 | value: ''
29 | })
30 |
31 | editor.addKeyMap({
32 | 'Tab': _ => editor.execCommand('insertSoftTab')
33 | })
34 |
35 | editor.on('change', debounce(_ => {
36 | stream.queue(editor.getValue() || '')
37 | }))
38 |
39 | const stream = through()
40 |
41 | window.addEventListener('resize', frameDebounce(resize))
42 | setTimeout(resize)
43 |
44 | return stream
45 |
46 | function resize(w, h) {
47 | if (w && h) return editor.setSize(w, h)
48 | let size = getSize(el)
49 | editor.setSize(w || size[0], h || size[1])
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "essim",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "beefy index.js:bundle.js"
9 | },
10 | "author": "Chris Dickinson (http://neversaw.us/)",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "babelify": "^6.0.2",
14 | "beefy": "^2.1.5",
15 | "browserify": "^9.0.8"
16 | },
17 | "browserify": {"transform": ["babelify"]},
18 | "dependencies": {
19 | "codemirror": "^5.2.0",
20 | "d3": "^3.5.5",
21 | "dagre-d3": "^0.4.3",
22 | "debounce": "^1.0.0",
23 | "element-size": "^1.1.1",
24 | "escontrol": "^4.0.0",
25 | "espree": "^2.0.2",
26 | "frame-debounce": "^1.0.1",
27 | "graphlib-dot": "^0.6.1",
28 | "inherits": "^2.0.1",
29 | "through": "^2.3.7"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------