styled_text: add compact style spans - #631
Conversation
|
This was done with the assistance of Codex (GPT 5.3, 5.4, 5.5, medium, high, xhigh). |
|
Some things I have in mind for follow up PRs:
|
PoignardAzur
left a comment
There was a problem hiding this comment.
Quick note: I think this code would be much easier to review if you split it into roughly one file per type. Right now it's kind of hard to find where types are defined without doing a lot of Ctrl+F.
30aec40 to
bb51b0a
Compare
bb51b0a to
45f79cc
Compare
|
I have removed |
Add `styled_text` as a first-slice crate for compact styled text storage. The core crate stores resolved complete styles as compact `StyleId` spans over `attributed_text`, with layout and paint payloads interned separately so paint-only changes can share layout identity. It includes a builder that resolves overlapping patch ranges and reusable segment workspaces for downstream layout or paint passes. This intentionally leaves lowering to Parley, cascading, editing policy, and application-specific style vocabularies outside the core `styled_text` crate.
45f79cc to
4722b21
Compare
DJMcNab
left a comment
There was a problem hiding this comment.
This seems reasonable to me.
Especially as we're not going to depend on this in Parley, it's fairly low-risk for experimentation. And it does unlock some doors for things we will need (HTML richtext copy/paste, etc.).
| //! That keeps the core crate independent of any toolkit's style vocabulary | ||
| //! while giving downstream code a simple stream of text ranges and full styles. | ||
| //! | ||
| //! ## Concepts |
There was a problem hiding this comment.
Nit:
| //! ## Concepts | |
| //! # Concepts |
(Plus elsewhere in this file)
This isn't blocking, but it means that if I make the README changes before this lands then we get consistency.
| run: cargo rdme --check --heading-base-level=0 --workspace-project=parlance | ||
|
|
||
| - name: Run cargo rdme (styled_text) | ||
| run: cargo rdme --check --heading-base-level=0 --workspace-project=styled_text |
There was a problem hiding this comment.
I know I need to go back to this repo and apply this change more widely, but we may as well start with the new best practise.
This previous mess was my fault, and I now know that the standard approach is to use h1 subheadings in Rust code.
| run: cargo rdme --check --heading-base-level=0 --workspace-project=styled_text | |
| run: cargo rdme --check --workspace-project=styled_text |
| /// | ||
| /// This record is exposed for diagnostics, invalidation decisions, and adapters | ||
| /// that need to know whether two full styles share the same layout or paint | ||
| /// payload. Text spans should store [`StyleId`], not this component record. |
There was a problem hiding this comment.
As a conceptual question, are the memory savings from interning a 2-id structure really worth much? Are we expecting to retain the same StyleId an average of more than twice for the majority of cases - or is this a play for handling huge documents (order of book length?)?
| layout: self.get_layout(record.layout_id())?, | ||
| paint: self.get_paint(record.paint_id())?, |
There was a problem hiding this comment.
Should these unwrap, or debug assert or something? It seems to me that this can only fail if something has gone wrong in our own bookkeeping.
| id: StyleId, | ||
| record: StyleRecord, | ||
| layout: &'a L, | ||
| paint: &'a P, |
There was a problem hiding this comment.
Is there a conceptual reason these aren't public fields (potentially #[non_exhaustive])
| /// Builder for [`StyleSet`] values. | ||
| /// | ||
| /// Interning is currently a linear search over retained vectors. That keeps the | ||
| /// first slice dependency-free and compact; callers can reuse a builder during |
There was a problem hiding this comment.
What is the "first slice" here?
There was a problem hiding this comment.
oh, a note to myself that should be removed. First slice of the implementation.
| self.intern_style_record(StyleRecord { layout, paint }) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
So to check my understanding, it's not possible to directly intern a style record which is a combination of two existing layouts and paints from ids?
| /// [`apply_style`](Self::apply_style). To introduce new styles, rebuild through | ||
| /// [`StyledTextBuilder`](crate::StyledTextBuilder) or a fresh [`StyleSet`]. |
There was a problem hiding this comment.
First-pass comment: I wonder if we can expose Arc::make_mut in a sensible way here.
| /// Finishes the builder and returns compact resolved styled text. | ||
| /// | ||
| /// Overlapping patches are applied in the order they were applied to the | ||
| /// builder over a clone of the base layout and paint styles for each | ||
| /// resolved non-base segment. |
There was a problem hiding this comment.
This is (up to)
| } else { | ||
| let mut resolved_spans = Vec::new(); | ||
| let mut workspace = AttributeSegmentsWorkspace::new(); | ||
| let mut pending: Option<(TextRange, StyleId)> = None; |
There was a problem hiding this comment.
I think this needs a comment here. I'm having a hard time grasping what this does.
Add
styled_textas a first-slice crate for compact styled text storage.The core crate stores resolved complete styles as compact
StyleIdspans overattributed_text, with layout and paint payloads interned separately so paint-only changes can share layout identity. It includes a builder that resolves overlapping patch ranges and reusable segment workspaces for downstream layout or paint passes.This intentionally leaves lowering to Parley, cascading, editing policy, and application-specific style vocabularies outside the core
styled_textcrate.