-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Code Highlighting Example double characters #4445
Comments
I was having this issue (alongside some other ones) so I tried making my own improvements. I just made this a couple of days ago, so it's not very high quality, but it gets the job done. Here is how it looks with my own theme: If anyone has any questions, please ask! I stripped down some of my editor specific implementation, but it should work for the most part if anyone else wants to try. Issue My Fixes
import Prism from "prismjs";
import "prismjs/themes/prism-coy.css";
import components from "prismjs/components"
import { Text } from 'slate';
const push_string = (token, path, start, ranges, token_type="text") => {
ranges.push({
"prism_token": token_type,
anchor: { path, offset: start },
focus: { path, offset: start + token.length },
})
start += token.length
return start;
}
// This recurses through the Prism.tokenizes result and creates stylized ranges based on the token type
const recurseTokenize = (token, path, ranges, start, parent_tag) => {
// Uses the parent's token type if a Token only has a string as its content
if (typeof token === 'string') {
return push_string(token, path, start, ranges, parent_tag)
}
if ('content' in token) {
// Calls recurseTokenize on nested Tokens in content
for (const subToken of token.content) {
start = recurseTokenize(subToken, path, ranges, start, token.type);
}
return start
}
}
const decorateCodeFunc = (editor, [node, path]) => {
const ranges = []
if (!Text.isText(node)) {
return ranges
}
// You can use this to specify a language like in the gif if you add a bit of logic
let language = "html";
let lang_aliases = { "html": "markup" }
if (language in lang_aliases) {
language = lang_aliases[language]
}
if (!(language in components.languages)) {
return ranges
}
// If you wanna import dynamically use this line, but beware the massive import (211 KB!!)
// require(`prismjs/components/prism-${language}`)
const tokens = Prism.tokenize(node.text, Prism.languages[language])
let start = 0
for (const token of tokens) {
start = recurseTokenize(token, path, ranges, start);
}
return ranges
}
// the different token types can be found on Prismjs website
const CodeLeaf = ({ attributes, children, leaf }) => {
return (
<span {...attributes} className={`token ${leaf.prism_token}`}>
{children}
</span>
)
}
const CodeBlock = (props) => {
return (
<pre className="code-block" {...props.attributes}>
<code>{props.children}</code>
</pre>
);
}
export {CodeBlock, CodeLeaf, decorateCodeFunc} |
@kalinkochnev I am new to slate and looking in using it as code editor, so your fix is interesting. I will need a bit more completed example to understand your fix, like a codesandbox or a zip with the complete project. Is it possible for you to provide such a complete example? |
@echarles Yeah I'll work on putting this up somehow as soon as I can, though in it's current state it slows down significantly with code blocks more than 40 lines long. This is an issue with both my implementation and the example syntax highlighting, but I'm working on a fix which might take a while. I'll upload what I have in its simplest form, but I'll also include something with my performance fixes eventually. |
@echarles Here is a version that works. It currently only does html because the imports for prismjs language grammars wasn't working in the sandbox. The whole performance issue I'm talking about isn't too noticeable when highlighting html, but with something more complicated like js, it starts to get bogged down. |
Thx a bunch @kalinkochnev I have that working locally which is great. Indeed performance needs to be worked on. Pasting the complete HTML source of this github issue page still hangs after 1 minute. Pasting just the 50 first lines works fine. Maybe (not sure) inspiration can be taken from codemirror/dev#109 (see improvements from https://github.com/satya164/react-simple-code-editor vs https://github.com/datavis-tech/codearea). I guess incremental parsing should be implemented with https://github.com/tree-sitter/tree-sitter or https://github.com/lezer-parser |
Linking here to other discussions related to peformance
|
the doubled character bug is recent—works OK in slate-react 0.65.3 (see https://codesandbox.io/s/code-editor-bug-reproduction-0-65-3-j673k), fails in the next snapshot version 0.66.0-20217122211 (see https://codesandbox.io/s/code-editor-bug-reproduction-0-66-0-20217122211-dmdsl). |
That's a shame as #3888 is pretty promising in what it strives to solve. It would be great if someone can look at it closer. We've had a lot of PRs lately around IME. |
We have elaborated in the previous comments to more thoughts for highlighting, and now it looks like we have a clue to fix the root cause of the regression. I can spend a bit of time to try to fix it (this will allow me also to discover slate code base). Any pointer where to look at (block code that could be responsible, idea to hack/debug the behaviour) will help me in that process. Thx! |
The commit that broke things was a fairly big change to try to rely on native browser events more as part of React's shift towards using browser native events ( some details in https://reactjs.org/blog/2020/08/10/react-v17-rc.html ). The files changed in https://github.com/ianstormtaylor/slate/pull/3888/files would be where to focus (It's about 200 lines), but my off the cuff guess is that somewhere we're having two events fire for the insert. |
I spent some time on it yesterday; I don't think it has to do with event handling, but rather with the rendering changes in
(where the decoration for If you select the point between I'll keep digging and see if I can understand exactly what's happening. |
Visual representation of @jaked 's explanation |
The issue seems to be a new const TextString = React.memo(
(props: { text: string; isTrailing?: boolean }) => { In the example above, what happens here is that for the first span, if (ref.current && ref.current.textContent !== text) {
forceUpdateFlag.current = !forceUpdateFlag.current
} If I remove the @bkrausz I think this might have been a mistake introduced in a300183 (since the |
Same here.
No idea here... trying out quickly a few other examples does not seem to give an issue. Running BTW Are there any benchmark to measure any regression in terms of performance? |
Not yet, but we're finally working to add tests for slate-react and I would like to see what we can do to add performance regression tests as well. I think regression performances are a big risk currently with landing PRs in slate-react. |
Yea, I think that's worth trying. The comment indicates that this was originally a fix for typing a letter and then undoing, since React would then fail to re-render. Can someone check that? I will express some general FUD over the dual-path nature of my PR: I suspect we'll run into subtle bugs that have to do with the mixing of native events and handled events. Not sure if there's a longer term solution (I'm not aware of what other editors do). |
Yes, would love seeing that but that is not a small piece of work. I have worked on benchmarks for jupyterlab (https://github.com/jupyterlab/benchmarks) and a current initiative is to run the comparison benchmark on each PR. For the
I have tried to undo with the fix (no |
Raised a PR, #4460. |
I'm still having this issue in the newest version. Details |
Reproduces for me in CodeSandbox. I'm not able to reproduce it in my local repo (on main), and it doesn't affect my app when I upgrade to the latest published Slate. I also tried your example as a static page with no build step so I could be sure what versions of things are imported (see https://glitch.com/edit/#!/mulberry-amenable-ink) but it doesn't repro there either. Weird. |
Yes, it's very weird. I'm still not able to reproduce the bug on my slatejs local repo, even when I changed the code example to match that of my create-react-app. Anyways, after debugging and comparing both, I found the following differences: context: The effect of typing
|
it seems like it would be worth trying to bisect between a broken version and a working version to figure out what's different. Could you post your create-react-app code somewhere? |
That's what I've been trying to do without any luck. |
the Webpack build blew up for me and I wasn't sure how to debug it, so I switched to Snowpack and ripped some stuff out, see here: https://github.com/jaked/slate-prism-editor If you run this with
??? @dylans probably worth reopening the issue :( |
I happened to be reading A (Mostly) Complete Guide to React Rendering Behavior and this jumped out at me:
@mterezac mentioned that "In the broken version, the TextString component is called twice", and had I noticed The existing code in const TextString = (props: { text: string; isTrailing?: boolean }) => {
...
if (ref.current && ref.current.textContent !== text) {
forceUpdateFlag.current = !forceUpdateFlag.current
}
// This component may have skipped rendering due to native operations being
// applied. If an undo is performed React will see the old and new shadow DOM
// match and not apply an update. Forces each render to actually reconcile.
return (
<span data-slate-string ref={ref} key={forceUpdateFlag.current ? 'A' : 'B'}>
... which, if rendered twice, will flip |
Description
When typing around highlighted words, characters are inserted twice.
Recording
Sandbox
https://www.slatejs.org/examples/code-highlighting
Expectation
Character should be inserted only once
Environment
The text was updated successfully, but these errors were encountered: