-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experimental CodeMirror editor #1670
Conversation
@wylieconlon relevant to your interests? mind taking a look? |
Yes, I'll take a look as soon as I can. Thanks for putting this out!
…On Thu, Mar 14, 2019 at 3:28 PM Mat Brown ***@***.***> wrote:
@wylieconlon <https://github.com/wylieconlon> relevant to your interests?
mind taking a look?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1670 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAora5HaiVwpjIOJLtm_Z644igO647vuks5vWqLsgaJpZM4b1Cxd>
.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I like the direction of this, my biggest concern is whether we can easily test the various effects without relying too heavily on Ace or CodeMirror APIs. My secondary concern is that a class component might have easier-to-follow logic, for example:
componentDidMount() {
this._editor = CodeMirror(this.editorContainer.current, {
mode: 'javascript',
theme: 'monokai',
value: this.props.source,
lineNumbers: true,
lineWrapping: true,
});
That being said, it's definitely the smaller concern. I'd just like to see your thoughts on both testing and class components.
gulpfile.js
Outdated
@@ -28,6 +28,7 @@ const srcDir = 'src'; | |||
const distDir = 'dist'; | |||
const stylesheetsDir = path.join(srcDir, 'css'); | |||
const highlightStylesheetsDir = 'node_modules/highlight.js/styles'; | |||
const codemirrorStylesheet = 'node_modules/codemirror/lib/codemirror.css'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are multiple built-in color schemes for codemirror that we could use as well- the default is pretty low contrast. For light schemes, I prefer Eclipse or Idea, but you can take a look at the built ins: https://codemirror.net/demo/theme.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the default scheme isn't amazing, but I actually went through all the themes that ship with the package and didn't find any I thought were overall better than the default (I also liked the IDE-derived themes but there were things I didn't like that I don't remember now; I think they were mostly in the way they themed HTML). This repository of themes looks promising though...
Anyway, seems like something we can continue to ponder between initial experimental-mode release and actually making this the official editor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default scheme is specifically less contrasty than these other options, which made it noticeable when switching back and forth. Totally fine with me to update later
.eslintrc
Outdated
@@ -922,6 +926,7 @@ | |||
"plugins": [ | |||
"import", | |||
"react", | |||
"react-hooks", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to start supporting hooks, should that be merged in separately?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So my basic thinking is:
- Hooks are good and class components are bad (more on that below)
- We should therefore not introduce any new class components, and when we need the features that require either hooks or class components in a new component, we should use hooks
- And soon we should probably transition fully to hooks even though the React folks say not to go all crazy rewriting components for hooks just yet
- So, with that in mind, this is the first time since hooks became a thing that we have a new component that needs to use hooks. So, this would be the exact time to "start supporting hooks" (I don't see much argument for, say, introducing the eslint plugin when there are no actual hooks to lint)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since writing this I'm now developing almost entirely in hooks, I think I'll write up some thoughts on that on a higher-up comment.
gulpfile.js
Outdated
@@ -68,6 +69,7 @@ gulp.task('css', () => { | |||
src([ | |||
path.join(bowerComponents, 'normalize-css/normalize.css'), | |||
path.join(highlightStylesheetsDir, 'github.css'), | |||
codemirrorStylesheet, | |||
path.join(stylesheetsDir, '**/*.css'), | |||
]). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there are multiple codemirror stylesheets, I think we want to define them all using something like this:
src(bowerStylesheets.concat(highlightStyleSheets, codemirrorStylesheets, sourceStylesheets))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you cool with what is there now? I'm not sure what the key difference is between what I had before and what you wrote in the above comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on our discussion of adding stylesheets later this is fine
package.json
Outdated
@@ -218,6 +219,7 @@ | |||
"loadjs": "^3.5.4", | |||
"lodash-es": "^4.17.11", | |||
"loop-breaker": "^0.1.0", | |||
"lru-cache": "^5.1.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used to cache CodeMirror.Doc
instances. Basically I want to hold on to the last few, so that the undo buffer is retained if you're switching back and forth between a couple projects, but don't want to build up an indefinitely growing collection of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense
import 'brace/mode/html'; | ||
import 'brace/mode/css'; | ||
import 'brace/mode/javascript'; | ||
import 'brace/theme/monokai'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are these styles?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't actually know, and I'm not sure if this line of code even does anything. In CodeMirror, monokai
is a dark theme...
src/components/CodeMirrorEditor.jsx
Outdated
@@ -0,0 +1,90 @@ | |||
import CodeMirror from 'codemirror'; | |||
import isNil from 'lodash-es/isNil'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not actually used
src/components/CodeMirrorEditor.jsx
Outdated
const mode = CODEMIRROR_MODES_MAP[language]; | ||
|
||
useEffect(() => { | ||
const {current: container} = containerRef; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would normally avoid stylistic comments, but this one is bothering me. Why destructure when the key isn't used? I prefer const container = containerRef.current;
and think it would simplify all the effects code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite right! I ended up rewriting most of these already, but will seek out any lingerers. I also added an entry to the contributor guide proposing some guidelines for when destructuring is useful.
src/components/CodeMirrorEditor.jsx
Outdated
container, | ||
{ | ||
lineNumbers: true, | ||
lineWrapping: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You haven't provided a theme name or mode to the initialize call- I think you can, since the props are available here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for a theme name, as I mentioned, I have yet to land on a theme that is obviously preferable to the default theme.
For the mode, it would feel good to put it here, but that would make the language
prop an effect dependency, which we definitely don't want here! Though practically we would never expect language
to change, if it did in fact change, we should not tear down and re-create the editor instance; we should just swap in a new Doc
instance, with the appropriate mode. And that's exactly what the following effect hook does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That wasn't obvious to me on first review, I'd appreciate a comment on why the mode is missing
src/components/CodeMirrorEditor.jsx
Outdated
const {current: container} = containerRef; | ||
|
||
const editor = editorRef.current = CodeMirror( | ||
container, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, I don't think you need to unwrap the reference at all. containerRef.current
would work fine here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something about refs makes me always want to unwrap them, basically so I don't have to look at the ref itself any more than necessary. Also, in some cases it actually does matter (e.g. in effect cleanup functions) so I think it's simpler to just have a global rule of "always unwrap ref values into their own variable" and then you don't have to worry about it.
src/components/CodeMirrorEditor.jsx
Outdated
if (doc !== editor.getDoc()) { | ||
editor.swapDoc(new CodeMirror.Doc('', mode)); | ||
} | ||
}, [language, mode, projectKey]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a nice one!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
@wylieconlon thanks for the thoughtful and detailed code review! i actually had a long flight yesterday and I think I got the CM editor close to parity with functionality with ACE, but didn't get a chance to push it until this morning. But I will definitely go through your review, respond to stuff, and make changes as appropriate : ) |
So after reading Dan Abramov's Big Blog Post I am pretty strongly convinced that class components are something approaching an antipattern (specifically the notion of Anyway, I believe this concludes all comment responses I owe you, looking forward to hearing your thoughts! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this is very clean, and I don't have any major concerns with this. I've been using hooks much more at work, so it was easier to read this code the second time around. I'm in favor of moving more components towards hook-based logic.
|
||
/* postcss-bem-linter: ignore */ | ||
.CodeMirror-activeline-background { | ||
background: var(--color-low-contrast-gray) !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hadn't realized we were using css variables, cool!
import ShallowRenderer from 'react-test-renderer/shallow'; | ||
import TestRenderer, {act} from 'react-test-renderer'; | ||
|
||
// eslint-disable-next-line import/no-extraneous-dependencies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s a fix in the master branch of eslint-plugin-import
that has yet to be released; once that lands this disable will no longer be necessary (and should itself trigger a lint error for no unnecessary disables)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't comment on the CodeMirror part because I don't know about that library either, just looked into things that could be improved.
|
||
_scrollToLine(lineNumber) { | ||
const shouldCenterVertically = true; | ||
const shouldAnimate = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these two consts will always be true, why not just directly set both to true
as arguments? If it's supposed to be configurable in the future, these values should be located in a properties/config file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure/good point but this is basically just moving the old Editor
component into a new place, and we’re deprecating it anyway, so I don’t think there’s any need to worry about this question
|
||
/* postcss-bem-linter: ignore */ | ||
.CodeMirror-activeline-background { | ||
background: var(--color-low-contrast-gray) !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you avoid using !important
? Also applies to line 565,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think so—generally using !important
is bad but in this case we are overriding a style in a vendor stylesheet so I don’t think there is any alternative.
60a6c95
to
42ad577
Compare
6c15224
to
4db91c5
Compare
N.B. I did a quick apples-to-apples on this and it looks like the bundle size will be about 10% smaller with CodeMirror fully replacing Ace. |
e8b08a3
to
ee72a83
Compare
7e34780
to
030560c
Compare
Creates a basic CodeMirror-backed editor component. Currently hard-coded to replace the old editor component but should actually be lazy-loaded only in experimental mode. Lacks most of the features of the old editor component so far, but bare-bones functionality is there.
Uses the `lint` addon, which basically does the right thing. Interface is a little strange in that you have to give it a function that returns a collection of errors (you cannot set errors imperatively). So in effect-world this means updating the editor configuration each time errors change, providing a new function to return the new set of errors. This seems to work well enough.
030560c
to
a2770ce
Compare
Creates an experimental
<CodeMirrorEditor>
component, which accepts the same (actually currently a subset of) the props as<Editor>
but uses CodeMirror rather than ACE as the underlying editor implementation.This is an initial spike and is not intended to be full-featured or release-ready. The specific goals of this pull request are:
We do get several other things for free/by default with CodeMirror. The full list of features that are required for parity with the ACE implementation can be found in the checklist in #1434.
In this pull request (and until that checklist is complete), the CodeMirror editor is only used in experimental mode (when the
experimental
query param is present in the URL). In order to avoid bloating the bundle unnecessarily, the<CodeMirrorEditor>
component is loaded asynchronously and lazily, in its own chunk, only in experimental mode. This means that the interface takes slightly longer to fully load in experimental mode.