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
179/// Bytes of a code editor's colour record.
180pub const CODE_EDITOR_COLORS_LEN: usize = 12;
181
182const fn optional_color(name: &'static str, offset: u16) -> RecordField {
183    field(
184        name,
185        offset,
186        RecordFieldType::Role {
187            name: "ColorRole",
188            optional: true,
189        },
190    )
191}
192
193/// The colours an application gives an editor: a role for the container, the gutter, a
194/// line number, the current line's number and the current line. Zero leaves that part to
195/// the design system. Bytes 10 and 11 are reserved and written as zero.
196pub const CODE_EDITOR_COLORS_RECORD: RecordSchema = RecordSchema {
197    name: "CodeEditorColorsRecord",
198    length: CODE_EDITOR_COLORS_LEN as u16,
199    fields: &[
200        optional_color("container", 0),
201        optional_color("gutter", 2),
202        optional_color("lineNumber", 4),
203        optional_color("currentLineNumber", 6),
204        optional_color("currentLine", 8),
205    ],
206};
207
208const fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
209    let mut index = 0;
210    while index < bytes.len() {
211        hash ^= bytes[index] as u64;
212        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
213        index += 1;
214    }
215    hash
216}
217
218/// Folds a record layout into the schema hash, so a Renderer built against another
219/// layout is refused at the handshake rather than reading the wrong bytes.
220pub(crate) const fn hash_record_schema(mut hash: u64, schema: RecordSchema) -> u64 {
221    hash = hash_bytes(hash, schema.name.as_bytes());
222    hash = hash_bytes(hash, &schema.length.to_le_bytes());
223    let mut index = 0;
224    while index < schema.fields.len() {
225        let field = schema.fields[index];
226        hash = hash_bytes(hash, field.name.as_bytes());
227        hash = hash_bytes(hash, &field.offset.to_le_bytes());
228        hash = match field.ty {
229            RecordFieldType::U32 => hash_bytes(hash, &[1]),
230            RecordFieldType::U64 => hash_bytes(hash, &[2]),
231            RecordFieldType::Role { name, optional } => {
232                let hash = hash_bytes(hash, &[3, optional as u8]);
233                hash_bytes(hash, name.as_bytes())
234            }
235            RecordFieldType::Paint => hash_bytes(hash, &[4]),
236            RecordFieldType::Text => hash_bytes(hash, &[5]),
237            RecordFieldType::Flag => hash_bytes(hash, &[6]),
238        };
239        index += 1;
240    }
241    hash
242}
243
244/// A place in the document: a line and a column, both from zero, the column in UTF-16
245/// code units.
246#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
247pub struct Position {
248    pub line: u32,
249    pub column: u32,
250}
251
252impl Position {
253    pub const fn new(line: u32, column: u32) -> Self {
254        Self { line, column }
255    }
256}
257
258/// A stretch of the document from `start` up to, and not including, `end`.
259#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
260pub struct CodeRange {
261    pub start: Position,
262    pub end: Position,
263}
264
265impl CodeRange {
266    pub const fn new(start: Position, end: Position) -> Self {
267        Self { start, end }
268    }
269
270    /// The empty range at one place, which is where an insertion goes.
271    pub const fn at(position: Position) -> Self {
272        Self {
273            start: position,
274            end: position,
275        }
276    }
277
278    /// A range given as four numbers, in the order a language server writes them.
279    pub const fn of(start_line: u32, start_column: u32, end_line: u32, end_column: u32) -> Self {
280        Self {
281            start: Position::new(start_line, start_column),
282            end: Position::new(end_line, end_column),
283        }
284    }
285
286    pub fn is_empty(&self) -> bool {
287        self.start == self.end
288    }
289}
290
291/// One thing drawn over the document, said as a range and never as a picture.
292///
293/// The colour of an underline is a role, never a literal, and is left to the design system
294/// where it is not given; the shape of the mark, a squiggle or a line, and its weight are
295/// always the design system's.
296#[derive(Clone, Debug, PartialEq)]
297pub struct Decoration {
298    /// The document version the range was written against.
299    pub version: u32,
300    pub kind: DecorationKind,
301    /// Only an underline has one.
302    pub severity: Option<Severity>,
303    /// The role an underline is drawn in. None leaves it to the design system, which picks
304    /// one that suits the severity.
305    pub color: Option<ColorRole>,
306    pub range: CodeRange,
307    /// The application's own name for this decoration, reported when it is pressed,
308    /// accepted or hovered. Zero means it cannot be pressed and is never reported.
309    pub id: u64,
310    /// What a lens or a suggestion shows. Empty for the other two kinds.
311    pub text: String,
312}
313
314impl Decoration {
315    /// A diagnostic mark under a range.
316    pub fn underline(version: u32, range: CodeRange, severity: Severity) -> Self {
317        Self {
318            version,
319            kind: DecorationKind::Underline,
320            severity: Some(severity),
321            color: None,
322            range,
323            id: 0,
324            text: String::new(),
325        }
326    }
327
328    /// A short line of text shown above `line`, which reports `id` when pressed.
329    pub fn code_lens(version: u32, line: u32, text: impl Into<String>, id: u64) -> Self {
330        Self {
331            version,
332            kind: DecorationKind::CodeLens,
333            severity: None,
334            color: None,
335            range: CodeRange::at(Position::new(line, 0)),
336            id,
337            text: text.into(),
338        }
339    }
340
341    /// A range that reports `id` when a pointer comes to rest over it.
342    pub fn hover_anchor(version: u32, range: CodeRange, id: u64) -> Self {
343        Self {
344            version,
345            kind: DecorationKind::HoverAnchor,
346            severity: None,
347            color: None,
348            range,
349            id,
350            text: String::new(),
351        }
352    }
353
354    /// A suggestion shown faintly at `at`. Accepting it inserts `text` there as an
355    /// ordinary edit and then reports `id`.
356    pub fn ghost_text(version: u32, at: Position, text: impl Into<String>, id: u64) -> Self {
357        Self {
358            version,
359            kind: DecorationKind::GhostText,
360            severity: None,
361            color: None,
362            range: CodeRange::at(at),
363            id,
364            text: text.into(),
365        }
366    }
367
368    /// Draws an underline in this role rather than the one the design system would pick.
369    pub fn with_color(mut self, role: ColorRole) -> Self {
370        self.color = Some(role);
371        self
372    }
373
374    pub fn with_id(mut self, id: u64) -> Self {
375        self.id = id;
376        self
377    }
378}
379
380fn put_u16(out: &mut Vec<u8>, value: u16) {
381    out.extend_from_slice(&value.to_le_bytes());
382}
383
384fn put_u32(out: &mut Vec<u8>, value: u32) {
385    out.extend_from_slice(&value.to_le_bytes());
386}
387
388fn put_u64(out: &mut Vec<u8>, value: u64) {
389    out.extend_from_slice(&value.to_le_bytes());
390}
391
392fn put_range(out: &mut Vec<u8>, range: CodeRange) {
393    put_u32(out, range.start.line);
394    put_u32(out, range.start.column);
395    put_u32(out, range.end.line);
396    put_u32(out, range.end.column);
397}
398
399fn word(record: &[u8], at: usize) -> u32 {
400    u32::from_le_bytes([record[at], record[at + 1], record[at + 2], record[at + 3]])
401}
402
403fn half(record: &[u8], at: usize) -> u16 {
404    u16::from_le_bytes([record[at], record[at + 1]])
405}
406
407fn long(record: &[u8], at: usize) -> u64 {
408    u64::from(word(record, at)) | (u64::from(word(record, at + 4)) << 32)
409}
410
411fn range_at(record: &[u8], at: usize) -> CodeRange {
412    CodeRange::of(
413        word(record, at),
414        word(record, at + 4),
415        word(record, at + 8),
416        word(record, at + 12),
417    )
418}
419
420/// The decorations over one editor, encoded and ready to travel.
421///
422/// Records first and the text they show after them, so the records stay fixed-length.
423/// Shared rather than copied, so the same list given again compares equal and costs no
424/// record at all.
425#[derive(Clone, Default, PartialEq)]
426pub struct Decorations {
427    bytes: Option<Rc<[u8]>>,
428}
429
430impl std::fmt::Debug for Decorations {
431    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432        formatter
433            .debug_struct("Decorations")
434            .field("decorations", &self.decorations().len())
435            .finish()
436    }
437}
438
439impl Decorations {
440    pub fn new(decorations: impl IntoIterator<Item = Decoration>) -> Self {
441        let decorations: Vec<Decoration> = decorations.into_iter().collect();
442        let records_len = decorations.len() * DECORATION_LEN;
443        let mut bytes = Vec::with_capacity(records_len);
444        let mut texts = Vec::new();
445        for decoration in &decorations {
446            put_u32(&mut bytes, decoration.version);
447            put_u16(&mut bytes, decoration.kind as u16);
448            put_u16(
449                &mut bytes,
450                decoration.severity.map_or(0, |value| value as u16),
451            );
452            put_u16(&mut bytes, decoration.color.map_or(0, |value| value as u16));
453            put_u16(&mut bytes, 0);
454            put_range(&mut bytes, decoration.range);
455            put_u64(&mut bytes, decoration.id);
456            // An empty text points at the end of the records, which is where the text
457            // region starts, so a reader never sees an offset into the records themselves.
458            put_u32(&mut bytes, (records_len + texts.len()) as u32);
459            put_u32(&mut bytes, decoration.text.len() as u32);
460            texts.extend_from_slice(decoration.text.as_bytes());
461        }
462        bytes.extend_from_slice(&texts);
463        Self::from_bytes(bytes)
464    }
465
466    /// Adopts an already encoded list, which is what the protocol vectors carry.
467    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
468        let bytes = bytes.into();
469        Self {
470            bytes: (!bytes.is_empty()).then_some(bytes),
471        }
472    }
473
474    pub fn as_bytes(&self) -> &[u8] {
475        self.bytes.as_deref().unwrap_or(&[])
476    }
477
478    pub fn is_empty(&self) -> bool {
479        self.bytes.is_none()
480    }
481
482    /// The decorations this list holds, read back.
483    ///
484    /// The records end where the text region begins, which is the first offset any record
485    /// names for its text, or the end of the blob where none names one. A record whose
486    /// kind, severity or colour is not one this side knows is skipped, which is what the
487    /// Renderer does with it too.
488    pub fn decorations(&self) -> Vec<Decoration> {
489        let bytes = self.as_bytes();
490        let mut end = bytes.len();
491        let mut at = 0;
492        let mut found = Vec::new();
493        while at + DECORATION_LEN <= end {
494            let record = &bytes[at..at + DECORATION_LEN];
495            at += DECORATION_LEN;
496            let text_offset = word(record, 36) as usize;
497            let text_len = word(record, 40) as usize;
498            if text_len > 0 {
499                end = end.min(text_offset);
500            }
501            let Ok(kind) = DecorationKind::try_from(half(record, 4)) else {
502                continue;
503            };
504            let severity = match half(record, 6) {
505                0 => None,
506                tag => match Severity::try_from(tag) {
507                    Ok(severity) => Some(severity),
508                    Err(()) => continue,
509                },
510            };
511            let color = match half(record, 8) {
512                0 => None,
513                tag => match ColorRole::try_from(tag) {
514                    Ok(role) => Some(role),
515                    Err(()) => continue,
516                },
517            };
518            let text = if text_len == 0 {
519                String::new()
520            } else {
521                match bytes
522                    .get(text_offset..text_offset + text_len)
523                    .and_then(|run| std::str::from_utf8(run).ok())
524                {
525                    Some(text) => text.to_owned(),
526                    None => continue,
527                }
528            };
529            found.push(Decoration {
530                version: word(record, 0),
531                kind,
532                severity,
533                color,
534                range: range_at(record, 12),
535                id: long(record, 28),
536                text,
537            });
538        }
539        found
540    }
541}
542
543/// One colour run of the syntax.
544///
545/// A paint rather than a colour, and in practice a role: the code colour roles are what a
546/// highlighter on this side maps its token kinds onto, so the same run comes out in the
547/// right colour under every design system and in both light and dark.
548#[derive(Clone, Copy, Debug, PartialEq)]
549pub struct SyntaxSpan {
550    pub version: u32,
551    pub range: CodeRange,
552    pub paint: Paint,
553}
554
555impl SyntaxSpan {
556    pub const fn new(version: u32, range: CodeRange, paint: Paint) -> Self {
557        Self {
558            version,
559            range,
560            paint,
561        }
562    }
563
564    /// A run drawn in one colour role.
565    pub const fn role(version: u32, range: CodeRange, role: ColorRole) -> Self {
566        Self::new(version, range, Paint::Role(role))
567    }
568}
569
570/// The colour runs of one editor's syntax, encoded and ready to travel.
571#[derive(Clone, Default, PartialEq)]
572pub struct SyntaxSpans {
573    bytes: Option<Rc<[u8]>>,
574}
575
576impl std::fmt::Debug for SyntaxSpans {
577    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
578        formatter
579            .debug_struct("SyntaxSpans")
580            .field("spans", &(self.as_bytes().len() / SYNTAX_SPAN_LEN))
581            .finish()
582    }
583}
584
585impl SyntaxSpans {
586    pub fn new(spans: impl IntoIterator<Item = SyntaxSpan>) -> Self {
587        let mut bytes = Vec::new();
588        for span in spans {
589            put_u32(&mut bytes, span.version);
590            put_range(&mut bytes, span.range);
591            put_u64(&mut bytes, span.paint.to_bits());
592        }
593        Self::from_bytes(bytes)
594    }
595
596    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
597        let bytes = bytes.into();
598        Self {
599            bytes: (!bytes.is_empty()).then_some(bytes),
600        }
601    }
602
603    pub fn as_bytes(&self) -> &[u8] {
604        self.bytes.as_deref().unwrap_or(&[])
605    }
606
607    pub fn is_empty(&self) -> bool {
608        self.bytes.is_none()
609    }
610
611    /// The runs this list holds, read back. A run whose paint this side cannot read is
612    /// skipped.
613    pub fn spans(&self) -> Vec<SyntaxSpan> {
614        self.as_bytes()
615            .chunks_exact(SYNTAX_SPAN_LEN)
616            .filter_map(|record| {
617                Some(SyntaxSpan {
618                    version: word(record, 0),
619                    range: range_at(record, 4),
620                    paint: Paint::from_bits(long(record, 20))?,
621                })
622            })
623            .collect()
624    }
625}
626
627/// Where a position falls in `text`, as a byte offset, or None where the document has no
628/// such place: a line past the last one, a column past the end of its line, or a column
629/// that would split a character written as two UTF-16 units.
630pub fn byte_offset(text: &str, position: Position) -> Option<usize> {
631    let bytes = text.as_bytes();
632    let mut start = 0;
633    for _ in 0..position.line {
634        let at = bytes[start..]
635            .iter()
636            .position(|byte| *byte == b'\n' || *byte == b'\r')?
637            + start;
638        start = if bytes[at] == b'\r' && bytes.get(at + 1) == Some(&b'\n') {
639            at + 2
640        } else {
641            at + 1
642        };
643    }
644    let end = bytes[start..]
645        .iter()
646        .position(|byte| *byte == b'\n' || *byte == b'\r')
647        .map_or(bytes.len(), |offset| start + offset);
648    let mut units = 0_u32;
649    let mut offset = start;
650    for character in text[start..end].chars() {
651        if units == position.column {
652            return Some(offset);
653        }
654        units += character.len_utf16() as u32;
655        offset += character.len_utf8();
656        if units > position.column {
657            return None;
658        }
659    }
660    (units == position.column).then_some(offset)
661}
662
663/// One committed edit, as the Renderer reported it.
664///
665/// `range` is in the document as it stood before the edit, and `version` is the version
666/// the edit produced. The shape is a language server's `TextDocumentContentChangeEvent`,
667/// so forwarding one is a matter of copying four numbers and a string.
668#[derive(Clone, Debug, PartialEq)]
669pub struct CodeChange {
670    pub version: u32,
671    pub range: CodeRange,
672    pub text: String,
673}
674
675impl CodeChange {
676    /// Applies this edit to a copy of the document the Host keeps.
677    ///
678    /// Applying every change in the order they arrived, starting from the text the editor
679    /// was opened with, gives the text the reader sees. Returns false, and leaves `text`
680    /// alone, where the range does not fit the document, which means the copy has fallen
681    /// out of step with the editor.
682    pub fn apply_to(&self, text: &mut String) -> bool {
683        let (Some(start), Some(end)) = (
684            byte_offset(text, self.range.start),
685            byte_offset(text, self.range.end),
686        ) else {
687            return false;
688        };
689        if start > end {
690            return false;
691        }
692        text.replace_range(start..end, &self.text);
693        true
694    }
695}
696
697/// A Host edit the Renderer did not apply, because the reader had changed the same place
698/// since the version the edit was written against.
699#[derive(Clone, Copy, Debug, Eq, PartialEq)]
700pub struct EditRejected {
701    /// The id the application gave the edit.
702    pub request_id: u32,
703    pub base_version: u32,
704    /// The version the document had reached when the edit arrived.
705    pub current_version: u32,
706    /// The range exactly as it was sent.
707    pub range: CodeRange,
708}
709
710/// A pointer came to rest over a place in the editor, or left it.
711///
712/// How long it has to rest is the design system's, the same kind of delay a tooltip
713/// waits, and nothing is sent again while it stays. What to show is the application's,
714/// drawn with the overlay widgets it already has.
715#[derive(Clone, Copy, Debug, Eq, PartialEq)]
716pub struct CodeHover {
717    /// The id of the hover anchor under the pointer, or None where there is none.
718    pub decoration: Option<u64>,
719    pub position: Position,
720    pub phase: HoverPhase,
721}
722
723/// The reader pressed the save shortcut: Command S on macOS, Control S elsewhere.
724///
725/// Writing the file is this side's job, on a worker, never on the thread the editor runs
726/// on.
727#[derive(Clone, Copy, Debug, Eq, PartialEq)]
728pub struct SaveRequest {
729    /// The version of the document the reader saw when they pressed it.
730    pub version: u32,
731}
732
733/// One stretch of lines the reader can fold.
734#[derive(Clone, Copy, Debug, Eq, PartialEq)]
735pub struct Fold {
736    /// The document version the lines were written against.
737    pub version: u32,
738    /// The line that stays when the stretch is folded.
739    pub start_line: u32,
740    /// The last line the fold hides. Folding hides the lines after `start_line` up to and
741    /// including this one.
742    pub end_line: u32,
743    pub collapsed: bool,
744}
745
746impl Fold {
747    /// A stretch from `start_line` to `end_line`, unfolded.
748    pub const fn new(version: u32, start_line: u32, end_line: u32) -> Self {
749        Self {
750            version,
751            start_line,
752            end_line,
753            collapsed: false,
754        }
755    }
756
757    /// The same stretch, folded.
758    pub const fn collapsed(mut self) -> Self {
759        self.collapsed = true;
760        self
761    }
762}
763
764/// The stretches of one editor that can be folded, encoded and ready to travel.
765#[derive(Clone, Default, PartialEq)]
766pub struct Folds {
767    bytes: Option<Rc<[u8]>>,
768}
769
770impl std::fmt::Debug for Folds {
771    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772        formatter
773            .debug_struct("Folds")
774            .field("folds", &(self.as_bytes().len() / FOLD_LEN))
775            .finish()
776    }
777}
778
779impl Folds {
780    pub fn new(folds: impl IntoIterator<Item = Fold>) -> Self {
781        let mut bytes = Vec::new();
782        for fold in folds {
783            put_u32(&mut bytes, fold.version);
784            put_u32(&mut bytes, fold.start_line);
785            put_u32(&mut bytes, fold.end_line);
786            put_u32(&mut bytes, u32::from(fold.collapsed));
787        }
788        Self::from_bytes(bytes)
789    }
790
791    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
792        let bytes = bytes.into();
793        Self {
794            bytes: (!bytes.is_empty()).then_some(bytes),
795        }
796    }
797
798    pub fn as_bytes(&self) -> &[u8] {
799        self.bytes.as_deref().unwrap_or(&[])
800    }
801
802    pub fn is_empty(&self) -> bool {
803        self.bytes.is_none()
804    }
805
806    /// The folds this list holds, read back. A record whose collapsed word is neither 0
807    /// nor 1 is skipped, which is what the Renderer does with it too.
808    pub fn folds(&self) -> Vec<Fold> {
809        self.as_bytes()
810            .chunks_exact(FOLD_LEN)
811            .filter_map(|record| {
812                Some(Fold {
813                    version: word(record, 0),
814                    start_line: word(record, 4),
815                    end_line: word(record, 8),
816                    collapsed: match word(record, 12) {
817                        0 => false,
818                        1 => true,
819                        _ => return None,
820                    },
821                })
822            })
823            .collect()
824    }
825}
826
827/// One mark beside a line number, said as a line and never as a picture.
828///
829/// The colour is a role, never a literal, and is left to the design system where it is not
830/// given. How big a dot is, how wide a bar and where each sits are always the design
831/// system's.
832#[derive(Clone, Copy, Debug, Eq, PartialEq)]
833pub struct GutterDecoration {
834    /// The document version the line was written against.
835    pub version: u32,
836    pub line: u32,
837    pub kind: GutterDecorationKind,
838    pub color: Option<ColorRole>,
839    /// The application's own name for it, reported when it is pressed. Zero means it
840    /// cannot be pressed.
841    pub id: u64,
842    /// The character a glyph shows, as a Unicode scalar value. Zero for the other kinds.
843    pub glyph: u32,
844}
845
846impl GutterDecoration {
847    /// A dot beside `line`.
848    pub const fn dot(version: u32, line: u32, id: u64) -> Self {
849        Self {
850            version,
851            line,
852            kind: GutterDecorationKind::Dot,
853            color: None,
854            id,
855            glyph: 0,
856        }
857    }
858
859    /// A bar the height of `line`.
860    pub const fn bar(version: u32, line: u32, id: u64) -> Self {
861        Self {
862            version,
863            line,
864            kind: GutterDecorationKind::Bar,
865            color: None,
866            id,
867            glyph: 0,
868        }
869    }
870
871    /// One character beside `line`.
872    pub const fn glyph(version: u32, line: u32, glyph: char, id: u64) -> Self {
873        Self {
874            version,
875            line,
876            kind: GutterDecorationKind::Glyph,
877            color: None,
878            id,
879            glyph: glyph as u32,
880        }
881    }
882
883    /// Draws the mark in this role rather than the one the design system would pick.
884    pub const fn with_color(mut self, role: ColorRole) -> Self {
885        self.color = Some(role);
886        self
887    }
888
889    pub const fn with_id(mut self, id: u64) -> Self {
890        self.id = id;
891        self
892    }
893}
894
895/// The marks beside the line numbers of one editor, encoded and ready to travel.
896#[derive(Clone, Default, PartialEq)]
897pub struct GutterDecorations {
898    bytes: Option<Rc<[u8]>>,
899}
900
901impl std::fmt::Debug for GutterDecorations {
902    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
903        formatter
904            .debug_struct("GutterDecorations")
905            .field(
906                "decorations",
907                &(self.as_bytes().len() / GUTTER_DECORATION_LEN),
908            )
909            .finish()
910    }
911}
912
913impl GutterDecorations {
914    pub fn new(decorations: impl IntoIterator<Item = GutterDecoration>) -> Self {
915        let mut bytes = Vec::new();
916        for decoration in decorations {
917            put_u32(&mut bytes, decoration.version);
918            put_u32(&mut bytes, decoration.line);
919            put_u16(&mut bytes, decoration.kind as u16);
920            put_u16(&mut bytes, decoration.color.map_or(0, |value| value as u16));
921            put_u64(&mut bytes, decoration.id);
922            put_u32(&mut bytes, decoration.glyph);
923            put_u32(&mut bytes, 0);
924        }
925        Self::from_bytes(bytes)
926    }
927
928    pub fn from_bytes(bytes: impl Into<Rc<[u8]>>) -> Self {
929        let bytes = bytes.into();
930        Self {
931            bytes: (!bytes.is_empty()).then_some(bytes),
932        }
933    }
934
935    pub fn as_bytes(&self) -> &[u8] {
936        self.bytes.as_deref().unwrap_or(&[])
937    }
938
939    pub fn is_empty(&self) -> bool {
940        self.bytes.is_none()
941    }
942
943    /// The marks this list holds, read back. A record whose kind or colour this side does
944    /// not know is skipped, which is what the Renderer does with it too.
945    pub fn decorations(&self) -> Vec<GutterDecoration> {
946        self.as_bytes()
947            .chunks_exact(GUTTER_DECORATION_LEN)
948            .filter_map(|record| {
949                let kind = GutterDecorationKind::try_from(half(record, 8)).ok()?;
950                let color = match half(record, 10) {
951                    0 => None,
952                    tag => Some(ColorRole::try_from(tag).ok()?),
953                };
954                Some(GutterDecoration {
955                    version: word(record, 0),
956                    line: word(record, 4),
957                    kind,
958                    color,
959                    id: long(record, 12),
960                    glyph: word(record, 20),
961                })
962            })
963            .collect()
964    }
965}
966
967/// The selection of a code editor, as the Renderer reported it.
968///
969/// `anchor` is where the selection started and `active` where the caret is, so a selection
970/// made backwards has the anchor after the active end. Both are read against `version`,
971/// the same series [`CodeChange`] counts.
972#[derive(Clone, Copy, Debug, Eq, PartialEq)]
973pub struct SelectionChange {
974    pub version: u32,
975    pub anchor: Position,
976    pub active: Position,
977}
978
979impl SelectionChange {
980    /// Whether nothing is selected and the caret is a single place.
981    pub fn is_caret(&self) -> bool {
982        self.anchor == self.active
983    }
984
985    /// The selected stretch, with the earlier end first whichever way it was made.
986    pub fn range(&self) -> CodeRange {
987        CodeRange::new(self.anchor.min(self.active), self.anchor.max(self.active))
988    }
989}
990
991/// The lines of a code editor that are on screen.
992#[derive(Clone, Copy, Debug, Eq, PartialEq)]
993pub struct VisibleRange {
994    pub version: u32,
995    /// The first line the reader can see.
996    pub first_line: u32,
997    /// The last line the reader can see. Lines a fold hides are not counted.
998    pub last_line: u32,
999}
1000
1001/// The reader folded or unfolded a stretch of a code editor.
1002#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1003pub struct FoldChange {
1004    /// The line the stretch starts on.
1005    pub start_line: u32,
1006    pub collapsed: bool,
1007}
1008
1009/// The colours of an editor's own parts, each a role of the theme.
1010///
1011/// A part that is not named keeps the colour the design system gives it, so an application
1012/// can set the container and the gutter and leave the rest. Roles and never literals: the
1013/// colours follow the theme from light to dark like every other colour here.
1014#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1015pub struct CodeEditorColors {
1016    /// What the text sits on.
1017    pub container: Option<ColorRole>,
1018    /// What the line numbers sit on.
1019    pub gutter: Option<ColorRole>,
1020    pub line_number: Option<ColorRole>,
1021    /// The number of the line the caret is on.
1022    pub current_line_number: Option<ColorRole>,
1023    /// The band behind the line the caret is on.
1024    pub current_line: Option<ColorRole>,
1025}
1026
1027impl CodeEditorColors {
1028    /// Every part left to the design system.
1029    pub const fn new() -> Self {
1030        Self {
1031            container: None,
1032            gutter: None,
1033            line_number: None,
1034            current_line_number: None,
1035            current_line: None,
1036        }
1037    }
1038
1039    pub const fn container(mut self, role: ColorRole) -> Self {
1040        self.container = Some(role);
1041        self
1042    }
1043
1044    pub const fn gutter(mut self, role: ColorRole) -> Self {
1045        self.gutter = Some(role);
1046        self
1047    }
1048
1049    pub const fn line_number(mut self, role: ColorRole) -> Self {
1050        self.line_number = Some(role);
1051        self
1052    }
1053
1054    pub const fn current_line_number(mut self, role: ColorRole) -> Self {
1055        self.current_line_number = Some(role);
1056        self
1057    }
1058
1059    pub const fn current_line(mut self, role: ColorRole) -> Self {
1060        self.current_line = Some(role);
1061        self
1062    }
1063
1064    /// Whether every part is left to the design system, in which case there is nothing to
1065    /// send.
1066    pub fn is_empty(&self) -> bool {
1067        *self == Self::new()
1068    }
1069
1070    /// The record as it travels in the `CodeEditorColors` property.
1071    pub fn to_bytes(&self) -> [u8; CODE_EDITOR_COLORS_LEN] {
1072        let mut bytes = [0_u8; CODE_EDITOR_COLORS_LEN];
1073        let slots = [
1074            self.container,
1075            self.gutter,
1076            self.line_number,
1077            self.current_line_number,
1078            self.current_line,
1079        ];
1080        for (index, slot) in slots.into_iter().enumerate() {
1081            let tag = slot.map_or(0, |role| role as u16);
1082            bytes[index * 2..index * 2 + 2].copy_from_slice(&tag.to_le_bytes());
1083        }
1084        bytes
1085    }
1086
1087    /// The record read back. `None` where the bytes are not one whole record or name a
1088    /// role this side does not know, which is what the Renderer refuses too.
1089    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
1090        if bytes.len() != CODE_EDITOR_COLORS_LEN {
1091            return None;
1092        }
1093        let slot = |index: usize| -> Option<Option<ColorRole>> {
1094            match half(bytes, index * 2) {
1095                0 => Some(None),
1096                tag => ColorRole::try_from(tag).ok().map(Some),
1097            }
1098        };
1099        Some(Self {
1100            container: slot(0)?,
1101            gutter: slot(1)?,
1102            line_number: slot(2)?,
1103            current_line_number: slot(3)?,
1104            current_line: slot(4)?,
1105        })
1106    }
1107}
1108
1109/// How an editor's rows are drawn, as the Renderer measured them.
1110#[derive(Clone, Copy, Debug, PartialEq)]
1111pub struct CodeMetrics {
1112    /// The height of one row in physical pixels. Every row of an editor has this height,
1113    /// so line `n` starts `n` times this far down when nothing is folded and no lens sits
1114    /// above a line.
1115    pub line_height_px: f64,
1116}
1117
1118/// How far an editor is scrolled.
1119#[derive(Clone, Copy, Debug, PartialEq)]
1120pub struct CodeScroll {
1121    /// The physical pixels of the document above the top of the view. The unit
1122    /// `Batch::set_scroll_offset` takes.
1123    pub offset_px: f64,
1124}