Skip to main content

compose_rust/
code.rs

1//! A code editor's document as the Host sees it.
2//!
3//! The Renderer owns the text. It owns the caret, the selection, the scroll position, the
4//! undo history and the composition an input method is building, for the same reason a
5//! text field owns them: a round trip to this side per keystroke would add a frame of
6//! latency to every key. What the Host gets is each committed edit, in order, with the
7//! version of the document it produced, which is exactly what a language server or an
8//! extension host needs to follow along.
9//!
10//! What the Host says back is said as ranges. Colour runs for the syntax and decorations
11//! for diagnostics, lenses, hover anchors and inline suggestions, each a blob of
12//! fixed-length records carrying the document version its ranges were written against.
13//! The Renderer moves them along with whatever the reader typed since, so a list that took
14//! a language server a second to compute still lands on the text it was about.
15//!
16//! Positions are a line and a column counted from zero, and a column counts UTF-16 code
17//! units. That is the unit a language server and VS Code count in, and any other unit
18//! would make every application recount every range on the way through. A line ends at
19//! `\n`, `\r\n` or a lone `\r`, which is also the language server's rule.
20
21use crate::schema::{ColorRole, DecorationKind, GutterDecorationKind, HoverPhase, Paint, Severity};
22use std::rc::Rc;
23
24/// Bytes of one decoration record.
25pub const DECORATION_LEN: usize = 44;
26
27/// Bytes of one syntax colour run record.
28pub const SYNTAX_SPAN_LEN: usize = 28;
29
30/// What one field of a code editor record holds.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum RecordFieldType {
33    U32,
34    U64,
35    /// A role tag in a `u16`. `optional` says that zero means "not given" rather than a
36    /// broken record.
37    Role {
38        name: &'static str,
39        optional: bool,
40    },
41    /// A `Paint` in a `u64`.
42    Paint,
43    /// A `u32` that is 0 or 1. Any other value makes the record unreadable.
44    Flag,
45    /// An `(offset, length)` pair of `u32` naming UTF-8 bytes behind the records, measured
46    /// from the start of the blob, the way a `Canvas` names the text it draws.
47    Text,
48}
49
50/// One field of a fixed-length record, at the byte it starts at.
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub struct RecordField {
53    pub name: &'static str,
54    pub offset: u16,
55    pub ty: RecordFieldType,
56}
57
58/// A fixed-length record that travels inside a property's blob.
59///
60/// Codegen writes the Renderer's decoder from this, so the two sides read the same bytes
61/// at the same offsets and nobody keeps a second copy of the layout by hand.
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct RecordSchema {
64    /// The Kotlin class the decoder produces.
65    pub name: &'static str,
66    pub length: u16,
67    pub fields: &'static [RecordField],
68}
69
70const fn field(name: &'static str, offset: u16, ty: RecordFieldType) -> RecordField {
71    RecordField { name, offset, ty }
72}
73
74/// One decoration: a version, a kind, a severity and a colour role, a range, the id the
75/// application gave it, and the text a lens or a suggestion shows. Bytes 10 and 11 are
76/// reserved and written as zero.
77pub const DECORATION_RECORD: RecordSchema = RecordSchema {
78    name: "DecorationRecord",
79    length: DECORATION_LEN as u16,
80    fields: &[
81        field("version", 0, RecordFieldType::U32),
82        field(
83            "kind",
84            4,
85            RecordFieldType::Role {
86                name: "DecorationKind",
87                optional: false,
88            },
89        ),
90        field(
91            "severity",
92            6,
93            RecordFieldType::Role {
94                name: "Severity",
95                optional: true,
96            },
97        ),
98        field(
99            "color",
100            8,
101            RecordFieldType::Role {
102                name: "ColorRole",
103                optional: true,
104            },
105        ),
106        field("startLine", 12, RecordFieldType::U32),
107        field("startColumn", 16, RecordFieldType::U32),
108        field("endLine", 20, RecordFieldType::U32),
109        field("endColumn", 24, RecordFieldType::U32),
110        field("id", 28, RecordFieldType::U64),
111        field("text", 36, RecordFieldType::Text),
112    ],
113};
114
115/// One colour run of the syntax: a version, a range and the paint it is drawn in.
116pub const SYNTAX_SPAN_RECORD: RecordSchema = RecordSchema {
117    name: "SyntaxSpanRecord",
118    length: SYNTAX_SPAN_LEN as u16,
119    fields: &[
120        field("version", 0, RecordFieldType::U32),
121        field("startLine", 4, RecordFieldType::U32),
122        field("startColumn", 8, RecordFieldType::U32),
123        field("endLine", 12, RecordFieldType::U32),
124        field("endColumn", 16, RecordFieldType::U32),
125        field("paint", 20, RecordFieldType::Paint),
126    ],
127};
128
129/// Bytes of one fold record.
130pub const FOLD_LEN: usize = 16;
131
132/// Bytes of one gutter decoration record.
133pub const GUTTER_DECORATION_LEN: usize = 28;
134
135/// One stretch of lines that can be folded: a version, the line it starts on, the last line
136/// it covers and whether it is folded now. Folding keeps `start_line` and hides the lines
137/// after it up to `end_line`.
138pub const FOLD_RECORD: RecordSchema = RecordSchema {
139    name: "FoldRecord",
140    length: FOLD_LEN as u16,
141    fields: &[
142        field("version", 0, RecordFieldType::U32),
143        field("startLine", 4, RecordFieldType::U32),
144        field("endLine", 8, RecordFieldType::U32),
145        field("collapsed", 12, RecordFieldType::Flag),
146    ],
147};
148
149/// One mark beside the line numbers: a version, the line, what it looks like, an optional
150/// colour role, the id the application gave it and the character a glyph shows. Bytes 24
151/// to 27 are reserved and written as zero.
152pub const GUTTER_DECORATION_RECORD: RecordSchema = RecordSchema {
153    name: "GutterDecorationRecord",
154    length: GUTTER_DECORATION_LEN as u16,
155    fields: &[
156        field("version", 0, RecordFieldType::U32),
157        field("line", 4, RecordFieldType::U32),
158        field(
159            "kind",
160            8,
161            RecordFieldType::Role {
162                name: "GutterDecorationKind",
163                optional: false,
164            },
165        ),
166        field(
167            "color",
168            10,
169            RecordFieldType::Role {
170                name: "ColorRole",
171                optional: true,
172            },
173        ),
174        field("id", 12, RecordFieldType::U64),
175        field("glyph", 20, RecordFieldType::U32),
176    ],
177};
178
179const fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
180    let mut index = 0;
181    while index < bytes.len() {
182        hash ^= bytes[index] as u64;
183        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
184        index += 1;
185    }
186    hash
187}
188
189/// Folds a record layout into the schema hash, so a Renderer built against another
190/// layout is refused at the handshake rather than reading the wrong bytes.
191pub(crate) const fn hash_record_schema(mut hash: u64, schema: RecordSchema) -> u64 {
192    hash = hash_bytes(hash, schema.name.as_bytes());
193    hash = hash_bytes(hash, &schema.length.to_le_bytes());
194    let mut index = 0;
195    while index < schema.fields.len() {
196        let field = schema.fields[index];
197        hash = hash_bytes(hash, field.name.as_bytes());
198        hash = hash_bytes(hash, &field.offset.to_le_bytes());
199        hash = match field.ty {
200            RecordFieldType::U32 => hash_bytes(hash, &[1]),
201            RecordFieldType::U64 => hash_bytes(hash, &[2]),
202            RecordFieldType::Role { name, optional } => {
203                let hash = hash_bytes(hash, &[3, optional as u8]);
204                hash_bytes(hash, name.as_bytes())
205            }
206            RecordFieldType::Paint => hash_bytes(hash, &[4]),
207            RecordFieldType::Text => hash_bytes(hash, &[5]),
208            RecordFieldType::Flag => hash_bytes(hash, &[6]),
209        };
210        index += 1;
211    }
212    hash
213}
214
215/// A place in the document: a line and a column, both from zero, the column in UTF-16
216/// code units.
217#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
218pub struct Position {
219    pub line: u32,
220    pub column: u32,
221}
222
223impl Position {
224    pub const fn new(line: u32, column: u32) -> Self {
225        Self { line, column }
226    }
227}
228
229/// A stretch of the document from `start` up to, and not including, `end`.
230#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
231pub struct CodeRange {
232    pub start: Position,
233    pub end: Position,
234}
235
236impl CodeRange {
237    pub const fn new(start: Position, end: Position) -> Self {
238        Self { start, end }
239    }
240
241    /// The empty range at one place, which is where an insertion goes.
242    pub const fn at(position: Position) -> Self {
243        Self {
244            start: position,
245            end: position,
246        }
247    }
248
249    /// A range given as four numbers, in the order a language server writes them.
250    pub const fn of(start_line: u32, start_column: u32, end_line: u32, end_column: u32) -> Self {
251        Self {
252            start: Position::new(start_line, start_column),
253            end: Position::new(end_line, end_column),
254        }
255    }
256
257    pub fn is_empty(&self) -> bool {
258        self.start == self.end
259    }
260}
261
262/// One thing drawn over the document, said as a range and never as a picture.
263///
264/// The colour of an underline is a role, never a literal, and is left to the design system
265/// where it is not given; the shape of the mark, a squiggle or a line, and its weight are
266/// always the design system's.
267#[derive(Clone, Debug, PartialEq)]
268pub struct Decoration {
269    /// The document version the range was written against.
270    pub version: u32,
271    pub kind: DecorationKind,
272    /// Only an underline has one.
273    pub severity: Option<Severity>,
274    /// The role an underline is drawn in. None leaves it to the design system, which picks
275    /// one that suits the severity.
276    pub color: Option<ColorRole>,
277    pub range: CodeRange,
278    /// The application's own name for this decoration, reported when it is pressed,
279    /// accepted or hovered. Zero means it cannot be pressed and is never reported.
280    pub id: u64,
281    /// What a lens or a suggestion shows. Empty for the other two kinds.
282    pub text: String,
283}
284
285impl Decoration {
286    /// A diagnostic mark under a range.
287    pub fn underline(version: u32, range: CodeRange, severity: Severity) -> Self {
288        Self {
289            version,
290            kind: DecorationKind::Underline,
291            severity: Some(severity),
292            color: None,
293            range,
294            id: 0,
295            text: String::new(),
296        }
297    }
298
299    /// A short line of text shown above `line`, which reports `id` when pressed.
300    pub fn code_lens(version: u32, line: u32, text: impl Into<String>, id: u64) -> Self {
301        Self {
302            version,
303            kind: DecorationKind::CodeLens,
304            severity: None,
305            color: None,
306            range: CodeRange::at(Position::new(line, 0)),
307            id,
308            text: text.into(),
309        }
310    }
311
312    /// A range that reports `id` when a pointer comes to rest over it.
313    pub fn hover_anchor(version: u32, range: CodeRange, id: u64) -> Self {
314        Self {
315            version,
316            kind: DecorationKind::HoverAnchor,
317            severity: None,
318            color: None,
319            range,
320            id,
321            text: String::new(),
322        }
323    }
324
325    /// A suggestion shown faintly at `at`. Accepting it inserts `text` there as an
326    /// ordinary edit and then reports `id`.
327    pub fn ghost_text(version: u32, at: Position, text: impl Into<String>, id: u64) -> Self {
328        Self {
329            version,
330            kind: DecorationKind::GhostText,
331            severity: None,
332            color: None,
333            range: CodeRange::at(at),
334            id,
335            text: text.into(),
336        }
337    }
338
339    /// Draws an underline in this role rather than the one the design system would pick.
340    pub fn with_color(mut self, role: ColorRole) -> Self {
341        self.color = Some(role);
342        self
343    }
344
345    pub fn with_id(mut self, id: u64) -> Self {
346        self.id = id;
347        self
348    }
349}
350
351fn put_u16(out: &mut Vec<u8>, value: u16) {
352    out.extend_from_slice(&value.to_le_bytes());
353}
354
355fn put_u32(out: &mut Vec<u8>, value: u32) {
356    out.extend_from_slice(&value.to_le_bytes());
357}
358
359fn put_u64(out: &mut Vec<u8>, value: u64) {
360    out.extend_from_slice(&value.to_le_bytes());
361}
362
363fn put_range(out: &mut Vec<u8>, range: CodeRange) {
364    put_u32(out, range.start.line);
365    put_u32(out, range.start.column);
366    put_u32(out, range.end.line);
367    put_u32(out, range.end.column);
368}
369
370fn word(record: &[u8], at: usize) -> u32 {
371    u32::from_le_bytes([record[at], record[at + 1], record[at + 2], record[at + 3]])
372}
373
374fn half(record: &[u8], at: usize) -> u16 {
375    u16::from_le_bytes([record[at], record[at + 1]])
376}
377
378fn long(record: &[u8], at: usize) -> u64 {
379    u64::from(word(record, at)) | (u64::from(word(record, at + 4)) << 32)
380}
381
382fn range_at(record: &[u8], at: usize) -> CodeRange {
383    CodeRange::of(
384        word(record, at),
385        word(record, at + 4),
386        word(record, at + 8),
387        word(record, at + 12),
388    )
389}
390
391/// The decorations over one editor, encoded and ready to travel.
392///
393/// Records first and the text they show after them, so the records stay fixed-length.
394/// Shared rather than copied, so the same list given again compares equal and costs no
395/// record at all.
396#[derive(Clone, Default, PartialEq)]
397pub struct Decorations {
398    bytes: Option<Rc<[u8]>>,
399}
400
401impl std::fmt::Debug for Decorations {
402    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        formatter
404            .debug_struct("Decorations")
405            .field("decorations", &self.decorations().len())
406            .finish()
407    }
408}
409
410impl Decorations {
411    pub fn new(decorations: impl IntoIterator<Item = Decoration>) -> Self {
412        let decorations: Vec<Decoration> = decorations.into_iter().collect();
413        let records_len = decorations.len() * DECORATION_LEN;
414        let mut bytes = Vec::with_capacity(records_len);
415        let mut texts = Vec::new();
416        for decoration in &decorations {
417            put_u32(&mut bytes, decoration.version);
418            put_u16(&mut bytes, decoration.kind as u16);
419            put_u16(
420                &mut bytes,
421                decoration.severity.map_or(0, |value| value as u16),
422            );
423            put_u16(&mut bytes, decoration.color.map_or(0, |value| value as u16));
424            put_u16(&mut bytes, 0);
425            put_range(&mut bytes, decoration.range);
426            put_u64(&mut bytes, decoration.id);
427            // An empty text points at the end of the records, which is where the text
428            // region starts, so a reader never sees an offset into the records themselves.
429            put_u32(&mut bytes, (records_len + texts.len()) as u32);
430            put_u32(&mut bytes, decoration.text.len() as u32);
431            texts.extend_from_slice(decoration.text.as_bytes());
432        }
433        bytes.extend_from_slice(&texts);
434        Self::from_bytes(bytes)
435    }
436
437    /// Adopts an already encoded list, which is what the protocol vectors carry.
438    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
439        let bytes = bytes.into();
440        Self {
441            bytes: (!bytes.is_empty()).then_some(bytes),
442        }
443    }
444
445    pub fn as_bytes(&self) -> &[u8] {
446        self.bytes.as_deref().unwrap_or(&[])
447    }
448
449    pub fn is_empty(&self) -> bool {
450        self.bytes.is_none()
451    }
452
453    /// The decorations this list holds, read back.
454    ///
455    /// The records end where the text region begins, which is the first offset any record
456    /// names for its text, or the end of the blob where none names one. A record whose
457    /// kind, severity or colour is not one this side knows is skipped, which is what the
458    /// Renderer does with it too.
459    pub fn decorations(&self) -> Vec<Decoration> {
460        let bytes = self.as_bytes();
461        let mut end = bytes.len();
462        let mut at = 0;
463        let mut found = Vec::new();
464        while at + DECORATION_LEN <= end {
465            let record = &bytes[at..at + DECORATION_LEN];
466            at += DECORATION_LEN;
467            let text_offset = word(record, 36) as usize;
468            let text_len = word(record, 40) as usize;
469            if text_len > 0 {
470                end = end.min(text_offset);
471            }
472            let Ok(kind) = DecorationKind::try_from(half(record, 4)) else {
473                continue;
474            };
475            let severity = match half(record, 6) {
476                0 => None,
477                tag => match Severity::try_from(tag) {
478                    Ok(severity) => Some(severity),
479                    Err(()) => continue,
480                },
481            };
482            let color = match half(record, 8) {
483                0 => None,
484                tag => match ColorRole::try_from(tag) {
485                    Ok(role) => Some(role),
486                    Err(()) => continue,
487                },
488            };
489            let text = if text_len == 0 {
490                String::new()
491            } else {
492                match bytes
493                    .get(text_offset..text_offset + text_len)
494                    .and_then(|run| std::str::from_utf8(run).ok())
495                {
496                    Some(text) => text.to_owned(),
497                    None => continue,
498                }
499            };
500            found.push(Decoration {
501                version: word(record, 0),
502                kind,
503                severity,
504                color,
505                range: range_at(record, 12),
506                id: long(record, 28),
507                text,
508            });
509        }
510        found
511    }
512}
513
514/// One colour run of the syntax.
515///
516/// A paint rather than a colour, and in practice a role: the code colour roles are what a
517/// highlighter on this side maps its token kinds onto, so the same run comes out in the
518/// right colour under every design system and in both light and dark.
519#[derive(Clone, Copy, Debug, PartialEq)]
520pub struct SyntaxSpan {
521    pub version: u32,
522    pub range: CodeRange,
523    pub paint: Paint,
524}
525
526impl SyntaxSpan {
527    pub const fn new(version: u32, range: CodeRange, paint: Paint) -> Self {
528        Self {
529            version,
530            range,
531            paint,
532        }
533    }
534
535    /// A run drawn in one colour role.
536    pub const fn role(version: u32, range: CodeRange, role: ColorRole) -> Self {
537        Self::new(version, range, Paint::Role(role))
538    }
539}
540
541/// The colour runs of one editor's syntax, encoded and ready to travel.
542#[derive(Clone, Default, PartialEq)]
543pub struct SyntaxSpans {
544    bytes: Option<Rc<[u8]>>,
545}
546
547impl std::fmt::Debug for SyntaxSpans {
548    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549        formatter
550            .debug_struct("SyntaxSpans")
551            .field("spans", &(self.as_bytes().len() / SYNTAX_SPAN_LEN))
552            .finish()
553    }
554}
555
556impl SyntaxSpans {
557    pub fn new(spans: impl IntoIterator<Item = SyntaxSpan>) -> Self {
558        let mut bytes = Vec::new();
559        for span in spans {
560            put_u32(&mut bytes, span.version);
561            put_range(&mut bytes, span.range);
562            put_u64(&mut bytes, span.paint.to_bits());
563        }
564        Self::from_bytes(bytes)
565    }
566
567    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
568        let bytes = bytes.into();
569        Self {
570            bytes: (!bytes.is_empty()).then_some(bytes),
571        }
572    }
573
574    pub fn as_bytes(&self) -> &[u8] {
575        self.bytes.as_deref().unwrap_or(&[])
576    }
577
578    pub fn is_empty(&self) -> bool {
579        self.bytes.is_none()
580    }
581
582    /// The runs this list holds, read back. A run whose paint this side cannot read is
583    /// skipped.
584    pub fn spans(&self) -> Vec<SyntaxSpan> {
585        self.as_bytes()
586            .chunks_exact(SYNTAX_SPAN_LEN)
587            .filter_map(|record| {
588                Some(SyntaxSpan {
589                    version: word(record, 0),
590                    range: range_at(record, 4),
591                    paint: Paint::from_bits(long(record, 20))?,
592                })
593            })
594            .collect()
595    }
596}
597
598/// Where a position falls in `text`, as a byte offset, or None where the document has no
599/// such place: a line past the last one, a column past the end of its line, or a column
600/// that would split a character written as two UTF-16 units.
601pub fn byte_offset(text: &str, position: Position) -> Option<usize> {
602    let bytes = text.as_bytes();
603    let mut start = 0;
604    for _ in 0..position.line {
605        let at = bytes[start..]
606            .iter()
607            .position(|byte| *byte == b'\n' || *byte == b'\r')?
608            + start;
609        start = if bytes[at] == b'\r' && bytes.get(at + 1) == Some(&b'\n') {
610            at + 2
611        } else {
612            at + 1
613        };
614    }
615    let end = bytes[start..]
616        .iter()
617        .position(|byte| *byte == b'\n' || *byte == b'\r')
618        .map_or(bytes.len(), |offset| start + offset);
619    let mut units = 0_u32;
620    let mut offset = start;
621    for character in text[start..end].chars() {
622        if units == position.column {
623            return Some(offset);
624        }
625        units += character.len_utf16() as u32;
626        offset += character.len_utf8();
627        if units > position.column {
628            return None;
629        }
630    }
631    (units == position.column).then_some(offset)
632}
633
634/// One committed edit, as the Renderer reported it.
635///
636/// `range` is in the document as it stood before the edit, and `version` is the version
637/// the edit produced. The shape is a language server's `TextDocumentContentChangeEvent`,
638/// so forwarding one is a matter of copying four numbers and a string.
639#[derive(Clone, Debug, PartialEq)]
640pub struct CodeChange {
641    pub version: u32,
642    pub range: CodeRange,
643    pub text: String,
644}
645
646impl CodeChange {
647    /// Applies this edit to a copy of the document the Host keeps.
648    ///
649    /// Applying every change in the order they arrived, starting from the text the editor
650    /// was opened with, gives the text the reader sees. Returns false, and leaves `text`
651    /// alone, where the range does not fit the document, which means the copy has fallen
652    /// out of step with the editor.
653    pub fn apply_to(&self, text: &mut String) -> bool {
654        let (Some(start), Some(end)) = (
655            byte_offset(text, self.range.start),
656            byte_offset(text, self.range.end),
657        ) else {
658            return false;
659        };
660        if start > end {
661            return false;
662        }
663        text.replace_range(start..end, &self.text);
664        true
665    }
666}
667
668/// A Host edit the Renderer did not apply, because the reader had changed the same place
669/// since the version the edit was written against.
670#[derive(Clone, Copy, Debug, Eq, PartialEq)]
671pub struct EditRejected {
672    /// The id the application gave the edit.
673    pub request_id: u32,
674    pub base_version: u32,
675    /// The version the document had reached when the edit arrived.
676    pub current_version: u32,
677    /// The range exactly as it was sent.
678    pub range: CodeRange,
679}
680
681/// A pointer came to rest over a place in the editor, or left it.
682///
683/// How long it has to rest is the design system's, the same kind of delay a tooltip
684/// waits, and nothing is sent again while it stays. What to show is the application's,
685/// drawn with the overlay widgets it already has.
686#[derive(Clone, Copy, Debug, Eq, PartialEq)]
687pub struct CodeHover {
688    /// The id of the hover anchor under the pointer, or None where there is none.
689    pub decoration: Option<u64>,
690    pub position: Position,
691    pub phase: HoverPhase,
692}
693
694/// The reader pressed the save shortcut: Command S on macOS, Control S elsewhere.
695///
696/// Writing the file is this side's job, on a worker, never on the thread the editor runs
697/// on.
698#[derive(Clone, Copy, Debug, Eq, PartialEq)]
699pub struct SaveRequest {
700    /// The version of the document the reader saw when they pressed it.
701    pub version: u32,
702}
703
704/// One stretch of lines the reader can fold.
705#[derive(Clone, Copy, Debug, Eq, PartialEq)]
706pub struct Fold {
707    /// The document version the lines were written against.
708    pub version: u32,
709    /// The line that stays when the stretch is folded.
710    pub start_line: u32,
711    /// The last line the fold hides. Folding hides the lines after `start_line` up to and
712    /// including this one.
713    pub end_line: u32,
714    pub collapsed: bool,
715}
716
717impl Fold {
718    /// A stretch from `start_line` to `end_line`, unfolded.
719    pub const fn new(version: u32, start_line: u32, end_line: u32) -> Self {
720        Self {
721            version,
722            start_line,
723            end_line,
724            collapsed: false,
725        }
726    }
727
728    /// The same stretch, folded.
729    pub const fn collapsed(mut self) -> Self {
730        self.collapsed = true;
731        self
732    }
733}
734
735/// The stretches of one editor that can be folded, encoded and ready to travel.
736#[derive(Clone, Default, PartialEq)]
737pub struct Folds {
738    bytes: Option<Rc<[u8]>>,
739}
740
741impl std::fmt::Debug for Folds {
742    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743        formatter
744            .debug_struct("Folds")
745            .field("folds", &(self.as_bytes().len() / FOLD_LEN))
746            .finish()
747    }
748}
749
750impl Folds {
751    pub fn new(folds: impl IntoIterator<Item = Fold>) -> Self {
752        let mut bytes = Vec::new();
753        for fold in folds {
754            put_u32(&mut bytes, fold.version);
755            put_u32(&mut bytes, fold.start_line);
756            put_u32(&mut bytes, fold.end_line);
757            put_u32(&mut bytes, u32::from(fold.collapsed));
758        }
759        Self::from_bytes(bytes)
760    }
761
762    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
763        let bytes = bytes.into();
764        Self {
765            bytes: (!bytes.is_empty()).then_some(bytes),
766        }
767    }
768
769    pub fn as_bytes(&self) -> &[u8] {
770        self.bytes.as_deref().unwrap_or(&[])
771    }
772
773    pub fn is_empty(&self) -> bool {
774        self.bytes.is_none()
775    }
776
777    /// The folds this list holds, read back. A record whose collapsed word is neither 0
778    /// nor 1 is skipped, which is what the Renderer does with it too.
779    pub fn folds(&self) -> Vec<Fold> {
780        self.as_bytes()
781            .chunks_exact(FOLD_LEN)
782            .filter_map(|record| {
783                Some(Fold {
784                    version: word(record, 0),
785                    start_line: word(record, 4),
786                    end_line: word(record, 8),
787                    collapsed: match word(record, 12) {
788                        0 => false,
789                        1 => true,
790                        _ => return None,
791                    },
792                })
793            })
794            .collect()
795    }
796}
797
798/// One mark beside a line number, said as a line and never as a picture.
799///
800/// The colour is a role, never a literal, and is left to the design system where it is not
801/// given. How big a dot is, how wide a bar and where each sits are always the design
802/// system's.
803#[derive(Clone, Copy, Debug, Eq, PartialEq)]
804pub struct GutterDecoration {
805    /// The document version the line was written against.
806    pub version: u32,
807    pub line: u32,
808    pub kind: GutterDecorationKind,
809    pub color: Option<ColorRole>,
810    /// The application's own name for it, reported when it is pressed. Zero means it
811    /// cannot be pressed.
812    pub id: u64,
813    /// The character a glyph shows, as a Unicode scalar value. Zero for the other kinds.
814    pub glyph: u32,
815}
816
817impl GutterDecoration {
818    /// A dot beside `line`.
819    pub const fn dot(version: u32, line: u32, id: u64) -> Self {
820        Self {
821            version,
822            line,
823            kind: GutterDecorationKind::Dot,
824            color: None,
825            id,
826            glyph: 0,
827        }
828    }
829
830    /// A bar the height of `line`.
831    pub const fn bar(version: u32, line: u32, id: u64) -> Self {
832        Self {
833            version,
834            line,
835            kind: GutterDecorationKind::Bar,
836            color: None,
837            id,
838            glyph: 0,
839        }
840    }
841
842    /// One character beside `line`.
843    pub const fn glyph(version: u32, line: u32, glyph: char, id: u64) -> Self {
844        Self {
845            version,
846            line,
847            kind: GutterDecorationKind::Glyph,
848            color: None,
849            id,
850            glyph: glyph as u32,
851        }
852    }
853
854    /// Draws the mark in this role rather than the one the design system would pick.
855    pub const fn with_color(mut self, role: ColorRole) -> Self {
856        self.color = Some(role);
857        self
858    }
859
860    pub const fn with_id(mut self, id: u64) -> Self {
861        self.id = id;
862        self
863    }
864}
865
866/// The marks beside the line numbers of one editor, encoded and ready to travel.
867#[derive(Clone, Default, PartialEq)]
868pub struct GutterDecorations {
869    bytes: Option<Rc<[u8]>>,
870}
871
872impl std::fmt::Debug for GutterDecorations {
873    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
874        formatter
875            .debug_struct("GutterDecorations")
876            .field(
877                "decorations",
878                &(self.as_bytes().len() / GUTTER_DECORATION_LEN),
879            )
880            .finish()
881    }
882}
883
884impl GutterDecorations {
885    pub fn new(decorations: impl IntoIterator<Item = GutterDecoration>) -> Self {
886        let mut bytes = Vec::new();
887        for decoration in decorations {
888            put_u32(&mut bytes, decoration.version);
889            put_u32(&mut bytes, decoration.line);
890            put_u16(&mut bytes, decoration.kind as u16);
891            put_u16(&mut bytes, decoration.color.map_or(0, |value| value as u16));
892            put_u64(&mut bytes, decoration.id);
893            put_u32(&mut bytes, decoration.glyph);
894            put_u32(&mut bytes, 0);
895        }
896        Self::from_bytes(bytes)
897    }
898
899    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
900        let bytes = bytes.into();
901        Self {
902            bytes: (!bytes.is_empty()).then_some(bytes),
903        }
904    }
905
906    pub fn as_bytes(&self) -> &[u8] {
907        self.bytes.as_deref().unwrap_or(&[])
908    }
909
910    pub fn is_empty(&self) -> bool {
911        self.bytes.is_none()
912    }
913
914    /// The marks this list holds, read back. A record whose kind or colour this side does
915    /// not know is skipped, which is what the Renderer does with it too.
916    pub fn decorations(&self) -> Vec<GutterDecoration> {
917        self.as_bytes()
918            .chunks_exact(GUTTER_DECORATION_LEN)
919            .filter_map(|record| {
920                let kind = GutterDecorationKind::try_from(half(record, 8)).ok()?;
921                let color = match half(record, 10) {
922                    0 => None,
923                    tag => Some(ColorRole::try_from(tag).ok()?),
924                };
925                Some(GutterDecoration {
926                    version: word(record, 0),
927                    line: word(record, 4),
928                    kind,
929                    color,
930                    id: long(record, 12),
931                    glyph: word(record, 20),
932                })
933            })
934            .collect()
935    }
936}
937
938/// The selection of a code editor, as the Renderer reported it.
939///
940/// `anchor` is where the selection started and `active` where the caret is, so a selection
941/// made backwards has the anchor after the active end. Both are read against `version`,
942/// the same series [`CodeChange`] counts.
943#[derive(Clone, Copy, Debug, Eq, PartialEq)]
944pub struct SelectionChange {
945    pub version: u32,
946    pub anchor: Position,
947    pub active: Position,
948}
949
950impl SelectionChange {
951    /// Whether nothing is selected and the caret is a single place.
952    pub fn is_caret(&self) -> bool {
953        self.anchor == self.active
954    }
955
956    /// The selected stretch, with the earlier end first whichever way it was made.
957    pub fn range(&self) -> CodeRange {
958        CodeRange::new(self.anchor.min(self.active), self.anchor.max(self.active))
959    }
960}
961
962/// The lines of a code editor that are on screen.
963#[derive(Clone, Copy, Debug, Eq, PartialEq)]
964pub struct VisibleRange {
965    pub version: u32,
966    /// The first line the reader can see.
967    pub first_line: u32,
968    /// The last line the reader can see. Lines a fold hides are not counted.
969    pub last_line: u32,
970}
971
972/// The reader folded or unfolded a stretch of a code editor.
973#[derive(Clone, Copy, Debug, Eq, PartialEq)]
974pub struct FoldChange {
975    /// The line the stretch starts on.
976    pub start_line: u32,
977    pub collapsed: bool,
978}