1use super::{CompactionRecommendation, CompactionUrgency, FragmentationStats, LineEnding};
2use encoding_rs::{Encoding, UTF_16BE, UTF_16LE, UTF_8};
3use std::fmt;
4use std::io;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7
8#[derive(Clone, Copy)]
10pub struct DocumentEncoding(&'static Encoding);
11
12impl DocumentEncoding {
13 pub const fn utf8() -> Self {
15 Self(UTF_8)
16 }
17
18 pub const fn utf16le() -> Self {
20 Self(UTF_16LE)
21 }
22
23 pub const fn utf16be() -> Self {
25 Self(UTF_16BE)
26 }
27
28 pub fn from_label(label: &str) -> Option<Self> {
30 Encoding::for_label(label.as_bytes()).map(Self)
31 }
32
33 pub fn name(self) -> &'static str {
35 self.0.name()
36 }
37
38 pub fn is_utf8(self) -> bool {
40 self.0 == UTF_8
41 }
42
43 pub fn can_roundtrip_save(self) -> bool {
45 self.0.output_encoding() == self.0
46 }
47
48 pub(crate) const fn as_encoding(self) -> &'static Encoding {
49 self.0
50 }
51}
52
53impl Default for DocumentEncoding {
54 fn default() -> Self {
55 Self::utf8()
56 }
57}
58
59impl PartialEq for DocumentEncoding {
60 fn eq(&self, other: &Self) -> bool {
61 std::ptr::eq(self.0, other.0)
62 }
63}
64
65impl Eq for DocumentEncoding {}
66
67impl std::hash::Hash for DocumentEncoding {
68 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
69 self.name().hash(state);
70 }
71}
72
73impl fmt::Debug for DocumentEncoding {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 f.debug_tuple("DocumentEncoding")
76 .field(&self.name())
77 .finish()
78 }
79}
80
81impl fmt::Display for DocumentEncoding {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 f.write_str(self.name())
84 }
85}
86
87#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
90pub enum OpenEncodingPolicy {
91 #[default]
93 Utf8FastPath,
94 AutoDetect,
100 Reinterpret(DocumentEncoding),
106}
107
108#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
111pub struct DocumentOpenOptions {
112 encoding_policy: OpenEncodingPolicy,
113}
114
115impl DocumentOpenOptions {
116 pub const fn new() -> Self {
118 Self {
119 encoding_policy: OpenEncodingPolicy::Utf8FastPath,
120 }
121 }
122
123 pub const fn with_auto_encoding_detection(mut self) -> Self {
125 self.encoding_policy = OpenEncodingPolicy::AutoDetect;
126 self
127 }
128
129 pub const fn with_reinterpretation(mut self, encoding: DocumentEncoding) -> Self {
131 self.encoding_policy = OpenEncodingPolicy::Reinterpret(encoding);
132 self
133 }
134
135 pub const fn with_encoding(mut self, encoding: DocumentEncoding) -> Self {
140 self.encoding_policy = OpenEncodingPolicy::Reinterpret(encoding);
141 self
142 }
143
144 pub const fn encoding_policy(self) -> OpenEncodingPolicy {
146 self.encoding_policy
147 }
148
149 pub const fn encoding_override(self) -> Option<DocumentEncoding> {
154 match self.encoding_policy {
155 OpenEncodingPolicy::Reinterpret(encoding) => Some(encoding),
156 OpenEncodingPolicy::Utf8FastPath | OpenEncodingPolicy::AutoDetect => None,
157 }
158 }
159}
160
161#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
164pub enum SaveEncodingPolicy {
165 #[default]
167 Preserve,
168 Convert(DocumentEncoding),
170}
171
172#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
174pub struct DocumentSaveOptions {
175 encoding_policy: SaveEncodingPolicy,
176}
177
178impl DocumentSaveOptions {
179 pub const fn new() -> Self {
181 Self {
182 encoding_policy: SaveEncodingPolicy::Preserve,
183 }
184 }
185
186 pub const fn with_encoding(mut self, encoding: DocumentEncoding) -> Self {
188 self.encoding_policy = SaveEncodingPolicy::Convert(encoding);
189 self
190 }
191
192 pub const fn encoding_policy(self) -> SaveEncodingPolicy {
194 self.encoding_policy
195 }
196}
197
198#[derive(Debug, Clone, Default, PartialEq, Eq)]
199pub struct LineSlice {
200 text: String,
201 exact: bool,
202}
203
204impl LineSlice {
205 pub fn new(text: String, exact: bool) -> Self {
207 Self { text, exact }
208 }
209
210 pub fn text(&self) -> &str {
212 &self.text
213 }
214
215 pub fn into_text(self) -> String {
217 self.text
218 }
219
220 pub fn is_exact(&self) -> bool {
222 self.exact
223 }
224
225 pub fn is_empty(&self) -> bool {
227 self.text.is_empty()
228 }
229}
230
231impl AsRef<str> for LineSlice {
232 fn as_ref(&self) -> &str {
233 self.text()
234 }
235}
236
237impl Deref for LineSlice {
238 type Target = str;
239
240 fn deref(&self) -> &Self::Target {
241 self.text()
242 }
243}
244
245impl fmt::Display for LineSlice {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 f.write_str(self.text())
248 }
249}
250
251impl From<LineSlice> for String {
252 fn from(value: LineSlice) -> Self {
253 value.into_text()
254 }
255}
256
257#[derive(Debug, Clone, Default, PartialEq, Eq)]
262pub struct TextSlice {
263 text: String,
264 exact: bool,
265}
266
267impl TextSlice {
268 pub fn new(text: String, exact: bool) -> Self {
270 Self { text, exact }
271 }
272
273 pub fn text(&self) -> &str {
275 &self.text
276 }
277
278 pub fn into_text(self) -> String {
280 self.text
281 }
282
283 pub fn is_exact(&self) -> bool {
285 self.exact
286 }
287
288 pub fn is_empty(&self) -> bool {
290 self.text.is_empty()
291 }
292}
293
294impl AsRef<str> for TextSlice {
295 fn as_ref(&self) -> &str {
296 self.text()
297 }
298}
299
300impl Deref for TextSlice {
301 type Target = str;
302
303 fn deref(&self) -> &Self::Target {
304 self.text()
305 }
306}
307
308impl fmt::Display for TextSlice {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 f.write_str(self.text())
311 }
312}
313
314impl From<TextSlice> for String {
315 fn from(value: TextSlice) -> Self {
316 value.into_text()
317 }
318}
319
320#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
328pub struct TextPosition {
329 line0: usize,
330 col0: usize,
331}
332
333impl TextPosition {
334 pub const fn new(line0: usize, col0: usize) -> Self {
336 Self { line0, col0 }
337 }
338
339 pub const fn line0(self) -> usize {
341 self.line0
342 }
343
344 pub const fn col0(self) -> usize {
346 self.col0
347 }
348}
349
350impl From<(usize, usize)> for TextPosition {
351 fn from(value: (usize, usize)) -> Self {
352 Self::new(value.0, value.1)
353 }
354}
355
356impl From<TextPosition> for (usize, usize) {
357 fn from(value: TextPosition) -> Self {
358 (value.line0, value.col0)
359 }
360}
361
362#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
370pub struct TextRange {
371 start: TextPosition,
372 len_chars: usize,
373}
374
375impl TextRange {
376 pub const fn new(start: TextPosition, len_chars: usize) -> Self {
378 Self { start, len_chars }
379 }
380
381 pub const fn empty(start: TextPosition) -> Self {
383 Self::new(start, 0)
384 }
385
386 pub const fn start(self) -> TextPosition {
388 self.start
389 }
390
391 pub const fn len_chars(self) -> usize {
393 self.len_chars
394 }
395
396 pub const fn is_empty(self) -> bool {
398 self.len_chars == 0
399 }
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
404pub struct SearchMatch {
405 range: TextRange,
406 end: TextPosition,
407}
408
409impl SearchMatch {
410 pub const fn new(range: TextRange, end: TextPosition) -> Self {
412 Self { range, end }
413 }
414
415 pub const fn range(self) -> TextRange {
417 self.range
418 }
419
420 pub const fn start(self) -> TextPosition {
422 self.range.start()
423 }
424
425 pub const fn end(self) -> TextPosition {
427 self.end
428 }
429
430 pub const fn len_chars(self) -> usize {
432 self.range.len_chars()
433 }
434
435 pub const fn is_empty(self) -> bool {
437 self.range.is_empty()
438 }
439
440 pub const fn selection(self) -> TextSelection {
442 TextSelection::new(self.start(), self.end())
443 }
444}
445
446#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
451pub struct TextSelection {
452 anchor: TextPosition,
453 head: TextPosition,
454}
455
456impl TextSelection {
457 pub const fn new(anchor: TextPosition, head: TextPosition) -> Self {
459 Self { anchor, head }
460 }
461
462 pub const fn caret(position: TextPosition) -> Self {
464 Self::new(position, position)
465 }
466
467 pub const fn anchor(self) -> TextPosition {
469 self.anchor
470 }
471
472 pub const fn head(self) -> TextPosition {
474 self.head
475 }
476
477 pub fn is_caret(self) -> bool {
479 self.anchor == self.head
480 }
481}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
485pub struct ViewportRequest {
486 first_line0: usize,
487 line_count: usize,
488 start_col: usize,
489 max_cols: usize,
490}
491
492impl Default for ViewportRequest {
493 fn default() -> Self {
494 Self::new(0, 0)
495 }
496}
497
498impl ViewportRequest {
499 pub const fn new(first_line0: usize, line_count: usize) -> Self {
504 Self {
505 first_line0,
506 line_count,
507 start_col: 0,
508 max_cols: usize::MAX,
509 }
510 }
511
512 pub const fn with_columns(mut self, start_col: usize, max_cols: usize) -> Self {
517 self.start_col = start_col;
518 self.max_cols = max_cols;
519 self
520 }
521
522 pub const fn first_line0(self) -> usize {
524 self.first_line0
525 }
526
527 pub const fn line_count(self) -> usize {
529 self.line_count
530 }
531
532 pub const fn start_col(self) -> usize {
534 self.start_col
535 }
536
537 pub const fn max_cols(self) -> usize {
539 self.max_cols
540 }
541}
542
543#[derive(Debug, Clone, PartialEq, Eq)]
545pub struct ViewportRow {
546 line0: usize,
547 slice: LineSlice,
548}
549
550impl ViewportRow {
551 pub fn new(line0: usize, slice: LineSlice) -> Self {
553 Self { line0, slice }
554 }
555
556 pub fn line0(&self) -> usize {
558 self.line0
559 }
560
561 pub fn line_number(&self) -> usize {
563 self.line0.saturating_add(1)
564 }
565
566 pub fn slice(&self) -> &LineSlice {
568 &self.slice
569 }
570
571 pub fn into_slice(self) -> LineSlice {
573 self.slice
574 }
575
576 pub fn text(&self) -> &str {
578 self.slice.text()
579 }
580
581 pub fn is_exact(&self) -> bool {
583 self.slice.is_exact()
584 }
585}
586
587#[derive(Debug, Clone, PartialEq, Eq)]
589pub struct Viewport {
590 request: ViewportRequest,
591 total_lines: LineCount,
592 rows: Vec<ViewportRow>,
593}
594
595impl Viewport {
596 pub fn new(request: ViewportRequest, total_lines: LineCount, rows: Vec<ViewportRow>) -> Self {
598 Self {
599 request,
600 total_lines,
601 rows,
602 }
603 }
604
605 pub fn request(&self) -> ViewportRequest {
607 self.request
608 }
609
610 pub fn total_lines(&self) -> LineCount {
612 self.total_lines
613 }
614
615 pub fn rows(&self) -> &[ViewportRow] {
617 &self.rows
618 }
619
620 pub fn into_rows(self) -> Vec<ViewportRow> {
622 self.rows
623 }
624
625 pub fn len(&self) -> usize {
627 self.rows.len()
628 }
629
630 pub fn is_empty(&self) -> bool {
632 self.rows.is_empty()
633 }
634}
635
636#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
638pub struct EditResult {
639 changed: bool,
640 cursor: TextPosition,
641}
642
643impl EditResult {
644 pub const fn new(changed: bool, cursor: TextPosition) -> Self {
646 Self { changed, cursor }
647 }
648
649 pub const fn changed(self) -> bool {
651 self.changed
652 }
653
654 pub const fn cursor(self) -> TextPosition {
656 self.cursor
657 }
658}
659
660#[derive(Debug, Clone, Default, PartialEq, Eq)]
662pub struct CutResult {
663 text: String,
664 edit: EditResult,
665}
666
667impl CutResult {
668 pub fn new(text: String, edit: EditResult) -> Self {
670 Self { text, edit }
671 }
672
673 pub fn text(&self) -> &str {
675 &self.text
676 }
677
678 pub fn into_text(self) -> String {
680 self.text
681 }
682
683 pub const fn edit(&self) -> EditResult {
685 self.edit
686 }
687
688 pub const fn changed(&self) -> bool {
690 self.edit.changed()
691 }
692
693 pub const fn cursor(&self) -> TextPosition {
695 self.edit.cursor()
696 }
697}
698
699#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
701pub struct ByteProgress {
702 completed_bytes: usize,
703 total_bytes: usize,
704}
705
706impl ByteProgress {
707 pub const fn new(completed_bytes: usize, total_bytes: usize) -> Self {
709 Self {
710 completed_bytes,
711 total_bytes,
712 }
713 }
714
715 pub const fn completed_bytes(self) -> usize {
717 self.completed_bytes
718 }
719
720 pub const fn total_bytes(self) -> usize {
722 self.total_bytes
723 }
724
725 pub fn fraction(self) -> f32 {
727 if self.total_bytes == 0 {
728 0.0
729 } else {
730 self.completed_bytes as f32 / self.total_bytes as f32
731 }
732 }
733}
734
735#[derive(Debug, Clone, Copy, PartialEq, Eq)]
738#[must_use]
739pub enum LineCount {
740 Exact(usize),
741 Estimated(usize),
742}
743
744impl LineCount {
745 pub fn exact(self) -> Option<usize> {
747 match self {
748 Self::Exact(lines) => Some(lines),
749 Self::Estimated(_) => None,
750 }
751 }
752
753 pub fn display_rows(self) -> usize {
755 match self {
756 Self::Exact(lines) | Self::Estimated(lines) => lines.max(1),
757 }
758 }
759
760 pub fn is_exact(self) -> bool {
762 matches!(self, Self::Exact(_))
763 }
764}
765
766#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
768pub enum DocumentBacking {
769 Mmap,
770 PieceTable,
771 Rope,
772}
773
774impl DocumentBacking {
775 pub const fn as_str(self) -> &'static str {
777 match self {
778 Self::Mmap => "mmap",
779 Self::PieceTable => "piece-table",
780 Self::Rope => "rope",
781 }
782 }
783}
784
785#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
787pub enum EditCapability {
788 Editable {
789 backing: DocumentBacking,
790 },
791 RequiresPromotion {
792 from: DocumentBacking,
793 to: DocumentBacking,
794 },
795 Unsupported {
796 backing: DocumentBacking,
797 reason: &'static str,
798 },
799}
800
801impl EditCapability {
802 pub const fn is_editable(self) -> bool {
804 !matches!(self, Self::Unsupported { .. })
805 }
806
807 pub const fn requires_promotion(self) -> bool {
809 matches!(self, Self::RequiresPromotion { .. })
810 }
811
812 pub const fn current_backing(self) -> DocumentBacking {
814 match self {
815 Self::Editable { backing } | Self::Unsupported { backing, .. } => backing,
816 Self::RequiresPromotion { from, .. } => from,
817 }
818 }
819
820 pub const fn target_backing(self) -> Option<DocumentBacking> {
822 match self {
823 Self::RequiresPromotion { to, .. } => Some(to),
824 _ => None,
825 }
826 }
827
828 pub const fn reason(self) -> Option<&'static str> {
830 match self {
831 Self::Unsupported { reason, .. } => Some(reason),
832 _ => None,
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, Eq)]
839pub struct DocumentStatus {
840 path: Option<PathBuf>,
841 dirty: bool,
842 file_len: usize,
843 line_count: LineCount,
844 line_ending: LineEnding,
845 encoding: DocumentEncoding,
846 decoding_had_errors: bool,
847 indexing: Option<ByteProgress>,
848 backing: DocumentBacking,
849}
850
851impl DocumentStatus {
852 #[allow(clippy::too_many_arguments)]
854 pub fn new(
855 path: Option<PathBuf>,
856 dirty: bool,
857 file_len: usize,
858 line_count: LineCount,
859 line_ending: LineEnding,
860 encoding: DocumentEncoding,
861 decoding_had_errors: bool,
862 indexing: Option<ByteProgress>,
863 backing: DocumentBacking,
864 ) -> Self {
865 Self {
866 path,
867 dirty,
868 file_len,
869 line_count,
870 line_ending,
871 encoding,
872 decoding_had_errors,
873 indexing,
874 backing,
875 }
876 }
877
878 pub fn path(&self) -> Option<&Path> {
880 self.path.as_deref()
881 }
882
883 pub fn is_dirty(&self) -> bool {
885 self.dirty
886 }
887
888 pub fn file_len(&self) -> usize {
890 self.file_len
891 }
892
893 pub fn line_count(&self) -> LineCount {
895 self.line_count
896 }
897
898 pub fn exact_line_count(&self) -> Option<usize> {
900 self.line_count.exact()
901 }
902
903 pub fn display_line_count(&self) -> usize {
905 self.line_count.display_rows()
906 }
907
908 pub fn is_line_count_exact(&self) -> bool {
910 self.line_count.is_exact()
911 }
912
913 pub fn line_ending(&self) -> LineEnding {
915 self.line_ending
916 }
917
918 pub fn encoding(&self) -> DocumentEncoding {
920 self.encoding
921 }
922
923 pub fn decoding_had_errors(&self) -> bool {
925 self.decoding_had_errors
926 }
927
928 pub fn indexing_state(&self) -> Option<ByteProgress> {
930 self.indexing
931 }
932
933 pub fn is_indexing(&self) -> bool {
935 self.indexing.is_some()
936 }
937
938 pub fn backing(&self) -> DocumentBacking {
940 self.backing
941 }
942
943 pub fn has_edit_buffer(&self) -> bool {
945 !matches!(self.backing, DocumentBacking::Mmap)
946 }
947
948 pub fn has_rope(&self) -> bool {
950 matches!(self.backing, DocumentBacking::Rope)
951 }
952
953 pub fn has_piece_table(&self) -> bool {
955 matches!(self.backing, DocumentBacking::PieceTable)
956 }
957}
958
959#[derive(Debug, Clone, Copy, PartialEq)]
962pub struct DocumentMaintenanceStatus {
963 backing: DocumentBacking,
964 fragmentation: Option<FragmentationStats>,
965 compaction: Option<CompactionRecommendation>,
966}
967
968#[derive(Debug, Clone, Copy, PartialEq, Eq)]
970pub enum MaintenanceAction {
971 None,
973 IdleCompaction,
975 ExplicitCompaction,
977}
978
979impl MaintenanceAction {
980 pub const fn as_str(self) -> &'static str {
982 match self {
983 Self::None => "none",
984 Self::IdleCompaction => "idle-compaction",
985 Self::ExplicitCompaction => "explicit-compaction",
986 }
987 }
988}
989
990impl DocumentMaintenanceStatus {
991 pub const fn new(
993 backing: DocumentBacking,
994 fragmentation: Option<FragmentationStats>,
995 compaction: Option<CompactionRecommendation>,
996 ) -> Self {
997 Self {
998 backing,
999 fragmentation,
1000 compaction,
1001 }
1002 }
1003
1004 pub const fn backing(self) -> DocumentBacking {
1006 self.backing
1007 }
1008
1009 pub const fn has_piece_table(self) -> bool {
1011 matches!(self.backing, DocumentBacking::PieceTable)
1012 }
1013
1014 pub const fn fragmentation_stats(self) -> Option<FragmentationStats> {
1016 self.fragmentation
1017 }
1018
1019 pub const fn has_fragmentation_stats(self) -> bool {
1021 self.fragmentation.is_some()
1022 }
1023
1024 pub const fn compaction_recommendation(self) -> Option<CompactionRecommendation> {
1026 self.compaction
1027 }
1028
1029 pub const fn is_compaction_recommended(self) -> bool {
1031 self.compaction.is_some()
1032 }
1033
1034 pub fn compaction_urgency(self) -> Option<CompactionUrgency> {
1036 self.compaction
1037 .map(|recommendation| recommendation.urgency())
1038 }
1039
1040 pub fn recommended_action(self) -> MaintenanceAction {
1042 match self.compaction_urgency() {
1043 Some(CompactionUrgency::Deferred) => MaintenanceAction::IdleCompaction,
1044 Some(CompactionUrgency::Forced) => MaintenanceAction::ExplicitCompaction,
1045 None => MaintenanceAction::None,
1046 }
1047 }
1048
1049 pub fn should_run_idle_compaction(self) -> bool {
1051 self.recommended_action() == MaintenanceAction::IdleCompaction
1052 }
1053
1054 pub fn should_wait_for_explicit_compaction(self) -> bool {
1057 self.recommended_action() == MaintenanceAction::ExplicitCompaction
1058 }
1059}
1060
1061#[derive(Debug)]
1063pub enum DocumentError {
1064 Open { path: PathBuf, source: io::Error },
1066 Map { path: PathBuf, source: io::Error },
1068 Write { path: PathBuf, source: io::Error },
1070 Encoding {
1072 path: PathBuf,
1073 operation: &'static str,
1074 encoding: DocumentEncoding,
1075 message: String,
1076 },
1077 EditUnsupported {
1079 path: Option<PathBuf>,
1080 reason: &'static str,
1081 },
1082}
1083
1084impl std::fmt::Display for DocumentError {
1085 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1086 match self {
1087 Self::Open { path, source } => write!(f, "open `{}`: {source}", path.display()),
1088 Self::Map { path, source } => write!(f, "mmap `{}`: {source}", path.display()),
1089 Self::Write { path, source } => write!(f, "write `{}`: {source}", path.display()),
1090 Self::Encoding {
1091 path,
1092 operation,
1093 encoding,
1094 message,
1095 } => write!(
1096 f,
1097 "{operation} `{}` with encoding `{encoding}`: {message}",
1098 path.display()
1099 ),
1100 Self::EditUnsupported { path, reason } => {
1101 if let Some(path) = path {
1102 write!(f, "edit `{}`: {reason}", path.display())
1103 } else {
1104 write!(f, "edit: {reason}")
1105 }
1106 }
1107 }
1108 }
1109}
1110
1111impl std::error::Error for DocumentError {
1112 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1113 match self {
1114 Self::Open { source, .. } | Self::Map { source, .. } | Self::Write { source, .. } => {
1115 Some(source)
1116 }
1117 Self::Encoding { .. } | Self::EditUnsupported { .. } => None,
1118 }
1119 }
1120}