1use crate::schema::{ColorRole, DecorationKind, GutterDecorationKind, HoverPhase, Paint, Severity};
22use std::rc::Rc;
23
24pub const DECORATION_LEN: usize = 44;
26
27pub const SYNTAX_SPAN_LEN: usize = 28;
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum RecordFieldType {
33 U32,
34 U64,
35 Role {
38 name: &'static str,
39 optional: bool,
40 },
41 Paint,
43 Flag,
45 Text,
48}
49
50#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub struct RecordSchema {
64 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
74pub 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
115pub 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
129pub const FOLD_LEN: usize = 16;
131
132pub const GUTTER_DECORATION_LEN: usize = 28;
134
135pub 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
149pub 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
179pub 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
193pub 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
218pub(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#[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#[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 pub const fn at(position: Position) -> Self {
272 Self {
273 start: position,
274 end: position,
275 }
276 }
277
278 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#[derive(Clone, Debug, PartialEq)]
297pub struct Decoration {
298 pub version: u32,
300 pub kind: DecorationKind,
301 pub severity: Option<Severity>,
303 pub color: Option<ColorRole>,
306 pub range: CodeRange,
307 pub id: u64,
310 pub text: String,
312}
313
314impl Decoration {
315 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 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 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 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 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#[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 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 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 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#[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 pub const fn role(version: u32, range: CodeRange, role: ColorRole) -> Self {
566 Self::new(version, range, Paint::Role(role))
567 }
568}
569
570#[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 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
627pub 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#[derive(Clone, Debug, PartialEq)]
669pub struct CodeChange {
670 pub version: u32,
671 pub range: CodeRange,
672 pub text: String,
673}
674
675impl CodeChange {
676 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
700pub struct EditRejected {
701 pub request_id: u32,
703 pub base_version: u32,
704 pub current_version: u32,
706 pub range: CodeRange,
708}
709
710#[derive(Clone, Copy, Debug, Eq, PartialEq)]
716pub struct CodeHover {
717 pub decoration: Option<u64>,
719 pub position: Position,
720 pub phase: HoverPhase,
721}
722
723#[derive(Clone, Copy, Debug, Eq, PartialEq)]
728pub struct SaveRequest {
729 pub version: u32,
731}
732
733#[derive(Clone, Copy, Debug, Eq, PartialEq)]
735pub struct Fold {
736 pub version: u32,
738 pub start_line: u32,
740 pub end_line: u32,
743 pub collapsed: bool,
744}
745
746impl Fold {
747 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 pub const fn collapsed(mut self) -> Self {
759 self.collapsed = true;
760 self
761 }
762}
763
764#[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 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
833pub struct GutterDecoration {
834 pub version: u32,
836 pub line: u32,
837 pub kind: GutterDecorationKind,
838 pub color: Option<ColorRole>,
839 pub id: u64,
842 pub glyph: u32,
844}
845
846impl GutterDecoration {
847 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 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 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 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#[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 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#[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 pub fn is_caret(&self) -> bool {
982 self.anchor == self.active
983 }
984
985 pub fn range(&self) -> CodeRange {
987 CodeRange::new(self.anchor.min(self.active), self.anchor.max(self.active))
988 }
989}
990
991#[derive(Clone, Copy, Debug, Eq, PartialEq)]
993pub struct VisibleRange {
994 pub version: u32,
995 pub first_line: u32,
997 pub last_line: u32,
999}
1000
1001#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1003pub struct FoldChange {
1004 pub start_line: u32,
1006 pub collapsed: bool,
1007}
1008
1009#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1015pub struct CodeEditorColors {
1016 pub container: Option<ColorRole>,
1018 pub gutter: Option<ColorRole>,
1020 pub line_number: Option<ColorRole>,
1021 pub current_line_number: Option<ColorRole>,
1023 pub current_line: Option<ColorRole>,
1025}
1026
1027impl CodeEditorColors {
1028 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 pub fn is_empty(&self) -> bool {
1067 *self == Self::new()
1068 }
1069
1070 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 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#[derive(Clone, Copy, Debug, PartialEq)]
1111pub struct CodeMetrics {
1112 pub line_height_px: f64,
1116}
1117
1118#[derive(Clone, Copy, Debug, PartialEq)]
1120pub struct CodeScroll {
1121 pub offset_px: f64,
1124}