-
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
refactor decorations to use selections #1221
Conversation
This is awesome! I do have question about does it improve dynamic decorator? When force rerender based on state not directly related to the editor (e.g. search result highlighting) |
Hey @lxcid good question! This PR itself doesn't support that, because the But! It does get us closer to being able to do things like that. I was thinking of adding a I haven't tested the performance of doing that for large editors, since you'll be re-decorating the entire document at once, but I'm sure there are things we can do to improve it. |
const decs = decorations.filter((d) => { | ||
const { startKey, endKey } = d | ||
if (startKey == key || endKey == key) return true | ||
const startsBefore = document.areDescendantsSorted(startKey, key) |
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.
These calls can become quite expensive. But for a classic use of decorators, the decorations should be scoped to a few blocks. And if need be, there are ways to improve the performance of areDescendantsSorted
, or maybe to process decorations upfront to get a list of individual text ranges instead (where startKey === endKey
).
We don't want premature optimization anyway.
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 though, this can be improved.
One improvement I think would be a method that checks (before, key, after)
or something about checking whether a node "overlaps" a range. Because here we have to do two separate checks which each iterate keys, etc. when really it could be handled in one go.
@@ -81,6 +73,9 @@ class Text extends React.Component { | |||
if (p.node == pLast && n.node != nLast) return true | |||
} | |||
|
|||
// Re-render if the current decorations have changed. | |||
if (!n.decorations.equals(p.decorations)) return 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.
Having reified decorations allows to run checks like these, which is great. It will probably fix a lot of "decorations not updating" issues.
@@ -165,19 +165,12 @@ class Node { | |||
first = normalizeKey(first) | |||
second = normalizeKey(second) | |||
|
|||
let sorted | |||
const keys = this.getKeysAsArray() |
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.
Oh, here is the optimization :D
if (!rule.decorate) return | ||
if (!rule.match(object)) return | ||
|
||
const decorations = rule.decorate(object) |
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 the decorate
calls themselves now live under a memoized function, which is nice :)
const a = getPoint(native.anchorNode, native.anchorOffset, state, editor) | ||
const f = getPoint(native.focusNode, native.focusOffset, state, editor) | ||
const a = findPoint(native.anchorNode, native.anchorOffset, state) | ||
const f = findPoint(native.focusNode, native.focusOffset, state) |
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.
Actually any place where we are doing findPoint
for both anchor and focus can be slightly improved by checking if collapsed
and if so only doing it once. But I'm not sure this is a bottleneck, so leaving for later.
*/ | ||
|
||
getKeys() { | ||
getKeysAsArray() { |
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 think this method can also be improved in performance if it was recursive, such that each nested child's keys would be cached, instead of having to iterate the entire tree each time the document changes.
4a9108c
to
3625665
Compare
Actually @lxcid I've just implemented that behavior that you're asking about! I added |
Wow! Thank for considering my request @ianstormtaylor and getting it in so soon! I can't wait to use it soon! The demo looks great! |
* refactor decorations to use selections * update docs * cleanup * add Selection.createList * fix tests * fix for nested blocks * fix lint * actually merge * revert small change * add state.decorations, with search example
This changes how decorations are applied in Slate.
Previously, you would use a
decorate(text, block)
function in the schema. It would be called for eachtext
node in the block, and you would add the marks to the characters yourself. But this led to issues because there are cases (like multi-line comments in syntax highlighting) where the decoration applies across multiple text nodes, and this system wasn't really built to handle that well.Now, you use a
decorate(block)
function in the schema. It is no longer called for each of the text nodes. Instead of applying the new marks to the characters yourself, you return an array of selection objects, with associated marks. For example:This would turn add a
bold
mark to the first character of the matched blocks.This would also make implementing things like multiple-cursor rendering easier, since the "decorations" now map exactly to the existing selections/ranges logic.
cc @Soreine, how does this sound? I think it should solve #893 and GitbookIO/slate-prism#5.