Skip to content

Commit e89840e

Browse files
committed
Replace CodeMirror with AceEditor
AceEditor is much more functional that CodeMirror as well as there are bunch of extensions/styles for the AceEditor in the Internet. For instance it provides an API to annotate lines or highlight them, which CodeMirror lacks of. This commit replaces CodeMirror with AceEditor, though some style enhacenements are required before going live.
1 parent 825bf1d commit e89840e

File tree

7 files changed

+200
-62
lines changed

7 files changed

+200
-62
lines changed

package-lock.json

Lines changed: 25 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
}
2828
},
2929
"dependencies": {
30-
"codemirror": "^5.33.0",
30+
"brace": "^0.11.0",
3131
"immutable": "^3.8.2",
3232
"parse-link-header": "^1.0.1",
3333
"prop-types": "^15.6.0",
3434
"react": "^16.0.0",
35-
"react-codemirror2": "^3.0.7",
35+
"react-ace": "^5.9.0",
3636
"react-custom-scrollbars": "^4.2.1",
3737
"react-dom": "^16.0.0",
3838
"react-redux": "^5.0.6",

src/components/NewSnippet.jsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
22
import { connect } from 'react-redux';
3-
import { Controlled as CodeMirror } from 'react-codemirror2';
3+
import AceEditor from 'react-ace';
44
import Tags from 'react-tagging-input';
55

6-
import 'codemirror/lib/codemirror.css';
6+
import brace from 'brace';
7+
import 'brace/ext/modelist';
78

89
import Title from './common/Title';
910
import ListBoxWithSearch from './ListBoxWithSearch';
@@ -21,9 +22,11 @@ class NewSnippet extends React.Component {
2122
syntax: '', // eslint-disable-line react/no-unused-state
2223
};
2324
this.onKeyPress = (e) => {
24-
if (e.which === 13) { // keyCode for Enter button
25+
// e.target.nodeName !=== 'TEXTAREA' is an ugly hack to allow enter
26+
// to insert "newline" into code editor. We need to figure out
27+
// a better way to handle this.
28+
if (e.which === 13 && e.target.nodeName !== 'TEXTAREA') { // keyCode for Enter button
2529
e.preventDefault();
26-
e.stopPropagation();
2730
}
2831
};
2932
this.postSnippet = this.postSnippet.bind(this);
@@ -63,6 +66,13 @@ class NewSnippet extends React.Component {
6366
}
6467

6568
render() {
69+
const { modesByName } = brace.acequire('ace/ext/modelist');
70+
const mode = modesByName[this.state.syntax] || modesByName.text;
71+
const syntaxes = this.props.syntaxes.map(item => ({
72+
name: modesByName[item].caption,
73+
value: item,
74+
}));
75+
6676
return (
6777
[
6878
<Title title="New snippet" key="New Snippet Title" />,
@@ -92,19 +102,29 @@ class NewSnippet extends React.Component {
92102
/>
93103
</div>
94104
<div className="new-snippet-code">
95-
<CodeMirror
105+
<AceEditor
106+
mode={mode.name}
107+
width="100%"
108+
height="100%"
109+
focus
110+
setOptions={{
111+
showFoldWidgets: false,
112+
useWorker: false,
113+
maxLines: Infinity,
114+
showPrintMargin: false,
115+
}}
96116
value={this.state.content}
97-
options={{ lineNumbers: true }}
98-
onBeforeChange={(editor, data, content) => { this.setState({ content }); }}
117+
onChange={(content) => { this.setState({ content }); }}
99118
/>
119+
100120
<div className="new-snippet-code-bottom-bar">
101121
<input type="submit" value="POST" />
102122
</div>
103123
</div>
104124
</div>
105125
<div className="new-snippet-lang-wrapper">
106126
<ListBoxWithSearch
107-
items={this.props.syntaxes}
127+
items={syntaxes}
108128
onClick={this.onSyntaxClick}
109129
/>
110130
</div>

src/components/Snippet.jsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import React from 'react';
22
import { connect } from 'react-redux';
3-
import { Controlled as CodeMirror } from 'react-codemirror2';
4-
5-
import codemirror from 'codemirror';
6-
import 'codemirror/lib/codemirror.css';
3+
import AceEditor from 'react-ace';
74

85
import Title from './common/Title';
96
import Spinner from './common/Spinner';
@@ -41,7 +38,6 @@ class Snippet extends React.Component {
4138
if (!snippet) return <Spinner />;
4239

4340
const snippetTitle = snippet.get('title') || `#${snippet.get('id')}, Untitled`;
44-
const modeInfo = codemirror.findModeByName(snippet.get('syntax'));
4541

4642
return (
4743
[
@@ -76,9 +72,20 @@ class Snippet extends React.Component {
7672
/>
7773
</div>
7874
<div className="snippet-code">
79-
<CodeMirror
75+
<AceEditor
76+
mode={snippet.get('syntax')}
77+
width="100%"
78+
height="100%"
79+
setOptions={{
80+
readOnly: true,
81+
highlightActiveLine: false,
82+
highlightGutterLine: false,
83+
showFoldWidgets: false,
84+
useWorker: false,
85+
maxLines: Infinity,
86+
showPrintMargin: false,
87+
}}
8088
value={`${snippet.get('content')}`}
81-
options={{ lineNumbers: true, readOnly: true, mode: modeInfo.mime }}
8289
/>
8390
<div className="snippet-code-bottom-bar">
8491
<button className="snippet-button light">Raw</button>

src/helpers/index.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import codemirror from 'codemirror';
1+
import brace from 'brace';
2+
import 'brace/ext/modelist';
23

34
const regExpEscape = string => string.replace(/[-[\]{}()*+?.,\\^$|]/g, '\\$&');
45

@@ -19,17 +20,20 @@ function download(text, name, mime) {
1920
}
2021

2122
function downloadSnippet(snippet) {
22-
// Despite using CodeMirror's modes as syntaxes on XSnippet API, we might
23-
// imagine other setup when more syntaxes can be used on server. Hence, we
24-
// must be prepared and fallback on "Plain Text" mode if we can't figure out
25-
// what's extension and/or MIME type.
26-
const modeInfo = codemirror.findModeByName(snippet.get('syntax'))
27-
|| codemirror.findModeByName('Plain Text');
23+
const { modesByName } = brace.acequire('ace/ext/modelist');
2824

25+
// Despite using AceEditor's modes as syntaxes, we can imagine other setup
26+
// when more or even other syntaxes can be used on API side. Hence, we better
27+
// be prepared and fallback to "Text" mode if unknown syntaxes it is.
28+
const mode = modesByName[snippet.get('syntax')] || modesByName.text;
29+
const ext = mode.extensions.split('|')[0] || 'txt';
2930
const content = snippet.get('content');
30-
const name = `${snippet.get('id')}.${modeInfo.ext[0]}`;
31+
const name = `${snippet.get('id')}.${ext}`;
3132

32-
download(content, name, modeInfo.mime);
33+
// Unfortunately, AceEditor doesn't maintain MIME type map so we don't know
34+
// for sure which mode corresponds to which MIME type. Hence, let's use
35+
// text/plain until we come up with better idea.
36+
download(content, name, 'text/plain');
3337
}
3438

3539
export { regExpEscape, downloadSnippet };

src/styles/common/overwrite.styl

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
11
@import './common/variables.styl'
22
@import './common/mixins.styl'
33

4-
.react-codemirror2,
5-
.CodeMirror
6-
height: 100% !important
7-
line-height: 1.4
4+
.snippet .ace_gutter-layer
5+
width: 40px !important
86

9-
.new-snippet .react-codemirror2,
10-
.new-snippet .CodeMirror
11-
color: text-dark !important
12-
13-
.CodeMirror-gutter
14-
&.CodeMirror-linenumbers
15-
width: 39px
16-
17-
.CodeMirror-sizer
18-
margin-left: 39px
19-
padding-top: 5px
20-
21-
.CodeMirror-gutters
22-
border-right: none
23-
24-
.snippet .CodeMirror-line
25-
padding-left: 20px !important
26-
27-
.snippet .CodeMirror-cursor {
7+
.snippet .ace_cursor
288
display: none !important
29-
}
309

3110
.react-tags
3211
display: flex

0 commit comments

Comments
 (0)