Skip to content

Commit

Permalink
[jsx mode] Add
Browse files Browse the repository at this point in the history
Closes #3742
Closes #3744
  • Loading branch information
marijnh committed Dec 29, 2015
1 parent 07bcf88 commit b3f9487
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 14 deletions.
2 changes: 1 addition & 1 deletion doc/compress.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ <h2>Script compression helper</h2>
<option value="http://codemirror.net/mode/javascript/javascript.js">javascript.js</option>
<option value="http://codemirror.net/mode/jinja2/jinja2.js">jinja2.js</option>
<option value="http://codemirror.net/mode/julia/julia.js">julia.js</option>
<option value="http://codemirror.net/mode/livescript/livescript.js">livescript.js</option>
<option value="http://codemirror.net/mode/jsx/jsx.js">jsx.js</option>
<option value="http://codemirror.net/mode/lua/lua.js">lua.js</option>
<option value="http://codemirror.net/mode/markdown/markdown.js">markdown.js</option>
<option value="http://codemirror.net/mode/mathematica/mathematica.js">mathematica.js</option>
Expand Down
2 changes: 1 addition & 1 deletion mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ <h2>Language modes</h2>
<li><a href="idl/index.html">IDL</a></li>
<li><a href="clike/index.html">Java</a></li>
<li><a href="jade/index.html">Jade</a></li>
<li><a href="javascript/index.html">JavaScript</a></li>
<li><a href="javascript/index.html">JavaScript</a> (<a href="jsx/index.html">JSX</a>)</li>
<li><a href="jinja2/index.html">Jinja2</a></li>
<li><a href="julia/index.html">Julia</a></li>
<li><a href="kotlin/index.html">Kotlin</a></li>
Expand Down
16 changes: 13 additions & 3 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
})(function(CodeMirror) {
"use strict";

function expressionAllowed(stream, state, backUp) {
return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:])$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}

CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
Expand Down Expand Up @@ -126,8 +131,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (/^(?:operator|sof|keyword c|case|new|[\[{}\(,;:])$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - 1)))) {
} else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
Expand Down Expand Up @@ -711,7 +715,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {

helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode
jsonMode: jsonMode,

expressionAllowed: expressionAllowed,
skipExpression: function(state) {
var top = state.cc[state.cc.length - 1]
if (top == expression || top == expressionNoComma) state.cc.pop()
}
};
});

Expand Down
89 changes: 89 additions & 0 deletions mode/jsx/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!doctype html>

<title>CodeMirror: JSX mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">

<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../javascript/javascript.js"></script>
<script src="../xml/xml.js"></script>
<script src="jsx.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>

<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">JSX</a>
</ul>
</div>

<article>
<h2>JSX mode</h2>

<div><textarea id="code" name="code">// Code snippets from http://facebook.github.io/react/docs/jsx-in-depth.html

// Rendering HTML tags
var myDivElement = <div className="foo" />;
ReactDOM.render(myDivElement, document.getElementById('example'));

// Rendering React components
var MyComponent = React.createClass({/*...*/});
var myElement = <MyComponent someProperty={true} />;
ReactDOM.render(myElement, document.getElementById('example'));

// Namespaced components
var Form = MyFormComponent;

var App = (
<Form>
<Form.Row>
<Form.Label />
<Form.Input />
</Form.Row>
</Form>
);

// Attribute JavaScript expressions
var person = <Person name={window.isLoggedIn ? window.name : ''} />;

// Boolean attributes
<input type="button" disabled />;
<input type="button" disabled={true} />;

// Child JavaScript expressions
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;

// Comments
var content = (
<Nav>
{/* child comment, put {} around */}
<Person
/* multi
line
comment */
name={window.isLoggedIn ? window.name : ''} // end of line comment
/>
</Nav>
);
</textarea></div>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
mode: "jsx"
})
</script>

<p>JSX Mode for <a href="http://facebook.github.io/react">React</a>'s
JavaScript syntax extension.</p>

<p><strong>MIME types defined:</strong> <code>text/jsx</code>.</p>

</article>
85 changes: 85 additions & 0 deletions mode/jsx/jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"))
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript"], mod)
else // Plain browser env
mod(CodeMirror)
})(function(CodeMirror) {
"use strict"

function copyContext(context) {
return {state: CodeMirror.copyState(context.mode, context.state),
mode: context.mode,
depth: context.depth,
prev: context.prev && copyContext(context.prev)}
}

CodeMirror.defineMode("jsx", function(config) {
var xmlMode = CodeMirror.getMode(config, "xml")
var jsMode = CodeMirror.getMode(config, "javascript")

return {
startState: function() {
return {context: {state: CodeMirror.startState(jsMode), mode: jsMode}}
},

copyState: function(state) {
return {context: copyContext(state.context)}
},

token: function(stream, state) {
var cx = state.context
if (cx.mode == xmlMode) {
if (stream.peek() == "{") {
xmlMode.skipAttribute(cx.state)
state.context = {state: CodeMirror.startState(jsMode, xmlMode.indent(cx.state, "")),
mode: jsMode,
depth: 1,
prev: state.context}
return jsMode.token(stream, state.context.state)
} else { // FIXME skip attribute
var style = xmlMode.token(stream, cx.state), cur, brace
if (/\btag\b/.test(style) && !cx.state.context && /^\/?>$/.test(stream.current()))
state.context = state.context.prev
else if (!style && (brace = (cur = stream.current()).indexOf("{")) > -1)
stream.backUp(cur.length - brace)
return style
}
} else { // jsMode
if (stream.peek() == "<" && jsMode.expressionAllowed(stream, cx.state)) {
jsMode.skipExpression(cx.state)
state.context = {state: CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "")),
mode: xmlMode,
prev: state.context}
return xmlMode.token(stream, state.context.state)
} else {
var style = jsMode.token(stream, cx.state)
if (!style && cx.depth != null) {
var cur = stream.current()
if (cur == "{") {
cx.depth++
} else if (cur == "}") {
if (--cx.depth == 0) state.context = state.context.prev
}
}
return style
}
}
},

indent: function(state, textAfter, fullLine) {
return state.context.mode.indent(state.context.state, textAfter, fullLine)
},

innerMode: function(state) {
return state.context[state.context.length - 1]

This comment has been minimized.

Copy link
@thehogfather

thehogfather Dec 31, 2015

@marijnh would be nice to return an object with the mode property here so that we can have addons like code folding work seamlessly.

This seems to work for me locally and is such a trivial issue - i didn't think it warranted a PR also I was not too sure of any additional implications.

innerMode: function(state) {
      return {mode: state.context.mode}; 
}

This comment has been minimized.

Copy link
@marijnh

marijnh Jan 4, 2016

Author Member

This has already been fixed. The method now returns simply state.context, this here was a holdover from when I represented the context as an array rather than a linked list.

}
}
}, "xml", "javascript")

CodeMirror.defineMIME("text/jsx", "jsx")
})
35 changes: 35 additions & 0 deletions mode/jsx/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function() {
var mode = CodeMirror.getMode({indentUnit: 2}, "jsx")
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)) }

MT("selfclose",
"[keyword var] [def x] [operator =] [bracket&tag <] [tag foo] [bracket&tag />] [operator +] [number 1];")

MT("openclose",
"([bracket&tag <][tag foo][bracket&tag >]hello [atom &amp;][bracket&tag </][tag foo][bracket&tag >][operator ++])")

MT("attr",
"([bracket&tag <][tag foo] [attribute abc]=[string 'value'][bracket&tag >]hello [atom &amp;][bracket&tag </][tag foo][bracket&tag >][operator ++])")

MT("braced_attr",
"([bracket&tag <][tag foo] [attribute abc]={[number 10]}[bracket&tag >]hello [atom &amp;][bracket&tag </][tag foo][bracket&tag >][operator ++])")

MT("braced_text",
"([bracket&tag <][tag foo][bracket&tag >]hello {[number 10]} [atom &amp;][bracket&tag </][tag foo][bracket&tag >][operator ++])")

MT("nested_tag",
"([bracket&tag <][tag foo][bracket&tag ><][tag bar][bracket&tag ></][tag bar][bracket&tag ></][tag foo][bracket&tag >][operator ++])")

MT("nested_jsx",
"[keyword return] (",
" [bracket&tag <][tag foo][bracket&tag >]",
" say {[number 1] [operator +] [bracket&tag <][tag bar] [attribute attr]={[number 10]}[bracket&tag />]}!",
" [bracket&tag </][tag foo][bracket&tag >][operator ++]",
")")

MT("preserve_js_context",
"[variable x] [operator =] [string-2 `quasi${][bracket&tag <][tag foo][bracket&tag />][string-2 }quoted`]")
})()
1 change: 1 addition & 0 deletions mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]},
{name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]},
{name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]},
{name: "JSX", mime: "text/jsx", mode: "jsx", ext: ["jsx"]},
{name: "Jinja2", mime: "null", mode: "jinja2"},
{name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]},
{name: "Kotlin", mime: "text/x-kotlin", mode: "clike", ext: ["kt"]},
Expand Down
25 changes: 16 additions & 9 deletions mode/xml/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,14 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
}

return {
startState: function() {
return {tokenize: inText,
state: baseState,
indented: 0,
tagName: null, tagStart: null,
context: null};
startState: function(baseIndent) {
var state = {tokenize: inText,
state: baseState,
indented: baseIndent || 0,
tagName: null, tagStart: null,
context: null}
if (baseIndent != null) state.baseIndent = baseIndent
return state
},

token: function(stream, state) {
Expand Down Expand Up @@ -362,18 +364,23 @@ CodeMirror.defineMode("xml", function(config, parserConfig) {
break;
}
}
while (context && !context.startOfLine)
while (context && context.prev && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return 0;
else return state.baseIndent || 0;
},

electricInput: /<\/[\s\w:]+>$/,
blockCommentStart: "<!--",
blockCommentEnd: "-->",

configuration: parserConfig.htmlMode ? "html" : "xml",
helperType: parserConfig.htmlMode ? "html" : "xml"
helperType: parserConfig.htmlMode ? "html" : "xml",

skipAttribute: function(state) {
if (state.state == attrValueState)
state.state = attrState
}
};
});

Expand Down
2 changes: 2 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<script src="../mode/haml/haml.js"></script>
<script src="../mode/htmlmixed/htmlmixed.js"></script>
<script src="../mode/javascript/javascript.js"></script>
<script src="../mode/jsx/jsx.js"></script>
<script src="../mode/markdown/markdown.js"></script>
<script src="../mode/php/php.js"></script>
<script src="../mode/ruby/ruby.js"></script>
Expand Down Expand Up @@ -107,6 +108,7 @@ <h2>Test Suite</h2>
<script src="../mode/gfm/test.js"></script>
<script src="../mode/haml/test.js"></script>
<script src="../mode/javascript/test.js"></script>
<script src="../mode/jsx/test.js"></script>
<script src="../mode/markdown/test.js"></script>
<script src="../mode/php/test.js"></script>
<script src="../mode/ruby/test.js"></script>
Expand Down

0 comments on commit b3f9487

Please sign in to comment.