Skip to content

Commit 833ccba

Browse files
committed
Add simple mode addon
1 parent c576c4c commit 833ccba

File tree

4 files changed

+378
-0
lines changed

4 files changed

+378
-0
lines changed

addon/mode/simple.js

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
2+
// Distributed under an MIT license: http://codemirror.net/LICENSE
3+
4+
(function(mod) {
5+
if (typeof exports == "object" && typeof module == "object") // CommonJS
6+
mod(require("../../lib/codemirror"));
7+
else if (typeof define == "function" && define.amd) // AMD
8+
define(["../../lib/codemirror"], mod);
9+
else // Plain browser env
10+
mod(CodeMirror);
11+
})(function(CodeMirror) {
12+
"use strict";
13+
14+
CodeMirror.defineSimpleMode = function(name, states, props) {
15+
CodeMirror.defineMode(name, function(config) {
16+
return CodeMirror.simpleMode(config, states, props);
17+
});
18+
};
19+
20+
CodeMirror.simpleMode = function(config, states) {
21+
ensureState(states, "start");
22+
var states_ = {}, meta = states.meta || {}, hasIndentation = false;
23+
for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
24+
var list = states_[state] = [], orig = states[state];
25+
for (var i = 0; i < orig.length; i++) {
26+
var data = orig[i];
27+
list.push(new Rule(data, states));
28+
if (data.indent || data.dedent) hasIndentation = true;
29+
}
30+
}
31+
var mode = {
32+
startState: function() {
33+
return {state: "start", pending: null,
34+
local: null, localState: null,
35+
indent: hasIndentation ? [] : null};
36+
},
37+
copyState: function(state) {
38+
var s = {state: state.state, pending: state.pending,
39+
local: state.local, localState: null,
40+
indent: state.indent && state.indent.slice(0)};
41+
if (state.localState)
42+
s.localState = CodeMirror.copyState(state.local.mode, state.localState);
43+
for (var pers = state.persistentStates; pers; pers = pers.next)
44+
s.persistentStates = {mode: pers.mode,
45+
spec: pers.spec,
46+
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
47+
next: s.persistentStates};
48+
return s;
49+
},
50+
token: tokenFunction(states_, config),
51+
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
52+
indent: indentFunction(states_, meta)
53+
};
54+
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
55+
mode[prop] = meta[prop];
56+
return mode;
57+
};
58+
59+
function ensureState(states, name) {
60+
if (!states.hasOwnProperty(name))
61+
throw new Error("Undefined state " + name + "in simple mode");
62+
}
63+
64+
function toRegex(val, caret) {
65+
if (!val) return /(?:)/;
66+
var flags = "";
67+
if (val instanceof RegExp) {
68+
if (val.ignoreCase) flags = "i";
69+
val = val.source;
70+
} else {
71+
val = String(val);
72+
}
73+
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
74+
}
75+
76+
function asToken(val) {
77+
if (!val) return null;
78+
if (typeof val == "string") return val.replace(/\./g, " ");
79+
var result = [];
80+
for (var i = 0; i < val.length; i++)
81+
result.push(val[i] && val[i].replace(/\./g, " "));
82+
return result;
83+
}
84+
85+
function Rule(data, states) {
86+
if (data.next) ensureState(states, data.next);
87+
this.regex = toRegex(data.regex);
88+
this.token = asToken(data.token);
89+
this.data = data;
90+
}
91+
92+
function tokenFunction(states, config) {
93+
return function(stream, state) {
94+
if (state.pending) {
95+
var pend = state.pending.shift();
96+
if (state.pending.length == 0) state.pending = null;
97+
stream.pos += pend.text.length;
98+
return pend.token;
99+
}
100+
101+
if (state.local) {
102+
if (state.local.end && stream.match(state.local.end)) {
103+
var tok = state.local.endToken || null;
104+
state.local = state.localState = null;
105+
return tok;
106+
} else {
107+
var tok = state.local.mode.token(stream, state.localState), m;
108+
if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
109+
stream.pos = stream.start + m.index;
110+
return tok;
111+
}
112+
}
113+
114+
var curState = states[state.state];
115+
for (var i = 0; i < curState.length; i++) {
116+
var rule = curState[i];
117+
var matches = stream.match(rule.regex);
118+
if (matches) {
119+
if (rule.data.next)
120+
state.state = rule.data.next;
121+
if (rule.data.mode)
122+
enterLocalMode(config, state, rule.data.mode, rule.token);
123+
if (rule.data.indent)
124+
state.indent.push(stream.indentation() + config.indentUnit);
125+
if (rule.data.dedent)
126+
state.indent.pop();
127+
if (matches.length > 2) {
128+
state.pending = [];
129+
for (var j = 2; j < matches.length; j++)
130+
state.pending.push({text: matches[j], token: rule.token[j - 1]});
131+
stream.backUp(matches[0].length - matches[1].length);
132+
return rule.token[0];
133+
} else if (rule.token && rule.token.join) {
134+
return rule.token[0];
135+
} else {
136+
return rule.token;
137+
}
138+
}
139+
}
140+
stream.next();
141+
return null;
142+
};
143+
}
144+
145+
function cmp(a, b) {
146+
if (a === b) return true;
147+
if (!a || typeof a != "object" || !b || typeof b != "object") return false;
148+
var props = 0;
149+
for (var prop in a) if (a.hasOwnProperty(prop)) {
150+
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
151+
props++;
152+
}
153+
for (var prop in b) if (b.hasOwnProperty(prop)) props--;
154+
return props == 0;
155+
}
156+
157+
function enterLocalMode(config, state, spec, token) {
158+
var pers;
159+
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
160+
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
161+
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
162+
var lState = pers ? pers.state : CodeMirror.startState(mode);
163+
if (spec.persistent && !pers)
164+
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
165+
166+
state.localState = lState;
167+
state.local = {mode: mode,
168+
end: spec.end && toRegex(spec.end),
169+
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
170+
endToken: token && token.join ? token[token.length - 1] : token};
171+
}
172+
173+
function indexOf(val, arr) {
174+
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
175+
}
176+
177+
function indentFunction(states, meta) {
178+
return function(state, textAfter, line) {
179+
if (state.local && state.local.mode.indent)
180+
return state.local.mode.indent(state.localState, textAfter, line);
181+
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
182+
return CodeMirror.Pass;
183+
184+
var pos = state.indent.length - 1, rules = states[state.state];
185+
scan: for (;;) {
186+
for (var i = 0; i < rules.length; i++) {
187+
var rule = rules[i], m = rule.regex.exec(textAfter);
188+
if (m) {
189+
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) pos--;
190+
if (rule.next) rules = states[rule.next];
191+
textAfter = textAfter.slice(m[0].length);
192+
continue scan;
193+
}
194+
}
195+
break;
196+
}
197+
return pos < 0 ? 0 : state.indent[pos];
198+
};
199+
}
200+
});

demo/simplemode.html

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!doctype html>
2+
3+
<title>CodeMirror: Simple Mode Demo</title>
4+
<meta charset="utf-8"/>
5+
<link rel=stylesheet href="../doc/docs.css">
6+
7+
<link rel="stylesheet" href="../lib/codemirror.css">
8+
<script src="../lib/codemirror.js"></script>
9+
<script src="../addon/mode/simple.js"></script>
10+
<script src="../mode/xml/xml.js"></script>
11+
<style type="text/css">
12+
.CodeMirror {border: 1px solid silver; margin-bottom: 1em; }
13+
dt { text-indent: -2em; padding-left: 2em; margin-top: 1em; }
14+
dd { margin-left: 1.5em; margin-bottom: 1em; }
15+
dt {margin-top: 1em;}
16+
</style>
17+
18+
<div id=nav>
19+
<a href="http://codemirror.net"><img id=logo src="../doc/logo.png"></a>
20+
21+
<ul>
22+
<li><a href="../index.html">Home</a>
23+
<li><a href="../doc/manual.html">Manual</a>
24+
<li><a href="https://github.com/codemirror/codemirror">Code</a>
25+
</ul>
26+
<ul>
27+
<li><a class=active href="#">Simple Mode</a>
28+
</ul>
29+
</div>
30+
31+
<article>
32+
<h2>Simple Mode Demo</h2>
33+
34+
<p>The <a href="../addon/mode/simple.js"><code>mode/simple</code></a>
35+
addon allows CodeMirror modes to be specified with a relatively simple
36+
declarative format. This format is not as powerful as writing code
37+
directly against the <a href="../doc/manual.html#modeapi">mode
38+
interface</a>, but is a lot easier to get started with, and
39+
sufficiently expressive for many simple language modes.</p>
40+
41+
<p>This interface is still in flux. It is unlikely to be scrapped or
42+
overhauled completely, so do start writing code against it, but
43+
details might change as it stabilizes, and you might have to tweak
44+
your code when upgrading.</p>
45+
46+
<p>Simple modes (loosely based on
47+
the <a href="https://github.com/mozilla/skywriter/wiki/Common-JavaScript-Syntax-Highlighting-Specification">Common
48+
JavaScript Syntax Highlighting Specification</a>, which never took
49+
off), are state machines, where each state has a number of rules that
50+
match tokens. A rule describes a type of token that may occur in the
51+
current state, and possibly a transition to another state caused by
52+
that token.</p>
53+
54+
<p>The <code>CodeMirror.defineSimpleMode(name, states)</code> method
55+
takes a mode name and an object that describes the mode's states. The
56+
editor below shows an example of such a mode (and is itself
57+
highlighted by the mode shown in it).</p>
58+
59+
<div id="code"></div>
60+
61+
<p>Each state is an array of rules. A rule may have the following properties:</p>
62+
63+
<dl>
64+
<dt><code>regex</code></dt>
65+
<dd>The regular expression that matches the token. May be a string
66+
or a regex object. When a regex, the <code>ignoreCase</code> flag
67+
will be taken into account when matching the token. This regex
68+
should only capture groups when the <code>token</code> property is
69+
an array.</dd>
70+
<dt><code>token</code></dt>
71+
<dd>An optional token style. Multiple styles can be specified by
72+
separating them with dots or spaces. When the <code>regex</code> for
73+
this rule captures groups, it must capture <em>all</em> of the
74+
string (since JS provides no way to find out where a group matched),
75+
and this property must hold an array of token styles that has one
76+
style for each matched group.</dd>
77+
<dt><code>next</code></dt>
78+
<dd>When a <code>next</code> property is present, the mode will
79+
transfer to another state when the token is encountered.</dd>
80+
<dt><code>mode</code></dt>
81+
<dd>Can be used to embed another mode inside a mode. When present,
82+
must hold an object with a <code>spec</code> property that describes
83+
the embedded mode, and an optional <code>end</code> end property
84+
that specifies the regexp that will end the extent of the mode. When
85+
a <code>persistent</code> property is set (and true), the nested
86+
mode's state will be preserved between occurrences of the mode.</dd>
87+
<dt><code>indent</code></dt>
88+
<dd>When true, this token changes the indentation to be one unit
89+
more than the current line's indentation.</dd>
90+
<dt><code>dedent</code></dt>
91+
<dd>When true, this token will pop one scope off the indentation
92+
stack.</dd>
93+
<dt><code>dedentIfLineStart</code></dt>
94+
<dd>If a token has its <code>dedent</code> property set, it will, by
95+
default, cause lines where it appears at the start to be dedented.
96+
Set this property to false to prevent that behavior.</dd>
97+
</dl>
98+
99+
<p>The <code>meta</code> property of the states object is special, and
100+
will not be interpreted as a state. Instead, properties set on it will
101+
be set on the mode, which is useful for properties
102+
like <a href="../doc/manual.html#addon_comment"><code>lineComment</code></a>,
103+
which sets the comment style for a mode. The simple mode addon also
104+
recognizes a few such properties:</p>
105+
106+
<dl>
107+
<dt><code>dontIndentStates</code></dt>
108+
<dd>An array of states in which the mode's auto-indentation should
109+
not take effect. Usually used for multi-line comment and string
110+
states.</dd>
111+
</dl>
112+
113+
<script id="modecode">/* Example definition of a simple mode that understands a subset of
114+
* JavaScript:
115+
*/
116+
117+
CodeMirror.defineSimpleMode("simplemode", {
118+
// The start state contains the rules that are intially used
119+
start: [
120+
// The regex matches the token, the token property contains the type
121+
{regex: /"(?:[^\\]|\\.)*?"/, token: "string"},
122+
// You can match multiple tokens at once. Note that the captured
123+
// groups must span the whole string in this case
124+
{regex: /(function)(\s+)([a-z$][\w$]*)/,
125+
token: ["keyword", null, "variable-2"]},
126+
// Rules are matched in the order in which they appear, so there is
127+
// no ambiguity between this one and the one above
128+
{regex: /(?:function|var|return|if|for|while|else|do|this)\b/,
129+
token: "keyword"},
130+
{regex: /true|false|null|undefined/, token: "atom"},
131+
{regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
132+
token: "number"},
133+
{regex: /\/\/.*/, token: "comment"},
134+
{regex: /\/(?:[^\\]|\\.)*?\//, token: "variable-3"},
135+
// A next property will cause the mode to move to a different state
136+
{regex: /\/\*/, token: "comment", next: "comment"},
137+
{regex: /[-+\/*=<>!]+/, token: "operator"},
138+
// indent and dedent properties guide autoindentation
139+
{regex: /[\{\[\(]/, indent: true},
140+
{regex: /[\}\]\)]/, dedent: true},
141+
{regex: /[a-z$][\w$]*/, token: "variable"},
142+
// You can embed other modes with the mode property. This rule
143+
// causes all code between << and >> to be highlighted with the XML
144+
// mode.
145+
{regex: /<</, token: "meta", mode: {spec: "xml", end: />>/}}
146+
],
147+
// The multi-line comment state.
148+
comment: [
149+
{regex: /.*?\*\//, token: "comment", next: "start"},
150+
{regex: /.*/, token: "comment"}
151+
],
152+
// The meta property contains global information about the mode. It
153+
// can contain properties like lineComment, which are supported by
154+
// all modes, and also directives like dontIndentStates, which are
155+
// specific to simple modes.
156+
meta: {
157+
dontIndentStates: ["comment"],
158+
lineComment: "//"
159+
}
160+
});
161+
</script>
162+
163+
<script>
164+
var sc = document.getElementById("modecode");
165+
var code = document.getElementById("code");
166+
var editor = CodeMirror(code, {
167+
value: (sc.textContent || sc.innerText || sc.innerHTML),
168+
mode: "simplemode"
169+
});
170+
</script>
171+
172+
</article>

doc/compress.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ <h2>Script compression helper</h2>
219219
<option value="http://codemirror.net/addon/search/search.js">search.js</option>
220220
<option value="http://codemirror.net/addon/search/searchcursor.js">searchcursor.js</option>
221221
<option value="http://codemirror.net/addon/hint/show-hint.js">show-hint.js</option>
222+
<option value="http://codemirror.net/addon/mode/simple.js">simple.js</option>
222223
<option value="http://codemirror.net/addon/hint/sql-hint.js">sql-hint.js</option>
223224
<option value="http://codemirror.net/addon/edit/trailingspace.js">trailingspace.js</option>
224225
<option value="http://codemirror.net/addon/tern/tern.js">tern.js</option>

doc/manual.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,11 @@ <h2>Writing CodeMirror Modes</h2>
27152715
advances it past a token, and returns a style for that token. More
27162716
advanced modes can also handle indentation for the language.</p>
27172717

2718+
<p>This section describes the low-level mode interface. Many modes
2719+
are written directly against this, since it offers a lot of
2720+
control, but for a quick mode definition, you might want to use
2721+
the <a href="../demo/simplemode.html">simple mode addon</a>.</p>
2722+
27182723
<p id="defineMode">The mode script should
27192724
call <code><strong>CodeMirror.defineMode</strong></code> to
27202725
register itself with CodeMirror. This function takes two

0 commit comments

Comments
 (0)