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 pub(crate) const fn from_encoding_rs(encoding: &'static Encoding) -> Self {
53 Self(encoding)
54 }
55}
56
57impl Default for DocumentEncoding {
58 fn default() -> Self {
59 Self::utf8()
60 }
61}
62
63impl PartialEq for DocumentEncoding {
64 fn eq(&self, other: &Self) -> bool {
65 std::ptr::eq(self.0, other.0)
66 }
67}
68
69impl Eq for DocumentEncoding {}
70
71impl std::hash::Hash for DocumentEncoding {
72 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
73 self.name().hash(state);
74 }
75}
76
77impl fmt::Debug for DocumentEncoding {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.debug_tuple("DocumentEncoding")
80 .field(&self.name())
81 .finish()
82 }
83}
84
85impl fmt::Display for DocumentEncoding {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str(self.name())
88 }
89}
90
91#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
93pub enum DocumentEncodingOrigin {
94 #[default]
96 NewDocument,
97 Utf8FastPath,
99 AutoDetected,
101 AutoDetectFallbackUtf8,
103 AutoDetectFallbackOverride,
105 ExplicitReinterpretation,
107 SaveConversion,
109}
110
111impl DocumentEncodingOrigin {
112 pub const fn as_str(self) -> &'static str {
114 match self {
115 Self::NewDocument => "new-document",
116 Self::Utf8FastPath => "utf8-fast-path",
117 Self::AutoDetected => "auto-detected",
118 Self::AutoDetectFallbackUtf8 => "auto-detect-fallback-utf8",
119 Self::AutoDetectFallbackOverride => "auto-detect-fallback-override",
120 Self::ExplicitReinterpretation => "explicit-reinterpretation",
121 Self::SaveConversion => "save-conversion",
122 }
123 }
124
125 pub const fn used_auto_detection(self) -> bool {
127 matches!(
128 self,
129 Self::AutoDetected | Self::AutoDetectFallbackUtf8 | Self::AutoDetectFallbackOverride
130 )
131 }
132
133 pub const fn is_explicit(self) -> bool {
135 matches!(
136 self,
137 Self::AutoDetectFallbackOverride
138 | Self::ExplicitReinterpretation
139 | Self::SaveConversion
140 )
141 }
142}
143
144#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
147pub enum OpenEncodingPolicy {
148 #[default]
150 Utf8FastPath,
151 AutoDetect,
157 AutoDetectOrReinterpret(DocumentEncoding),
164 Reinterpret(DocumentEncoding),
170}
171
172#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
175pub struct DocumentOpenOptions {
176 encoding_policy: OpenEncodingPolicy,
177}
178
179impl DocumentOpenOptions {
180 pub const fn new() -> Self {
182 Self {
183 encoding_policy: OpenEncodingPolicy::Utf8FastPath,
184 }
185 }
186
187 pub const fn with_auto_encoding_detection(mut self) -> Self {
189 self.encoding_policy = OpenEncodingPolicy::AutoDetect;
190 self
191 }
192
193 pub const fn with_auto_encoding_detection_and_fallback(
196 mut self,
197 encoding: DocumentEncoding,
198 ) -> Self {
199 self.encoding_policy = OpenEncodingPolicy::AutoDetectOrReinterpret(encoding);
200 self
201 }
202
203 pub const fn with_reinterpretation(mut self, encoding: DocumentEncoding) -> Self {
205 self.encoding_policy = OpenEncodingPolicy::Reinterpret(encoding);
206 self
207 }
208
209 pub const fn with_encoding(mut self, encoding: DocumentEncoding) -> Self {
214 self.encoding_policy = OpenEncodingPolicy::Reinterpret(encoding);
215 self
216 }
217
218 pub const fn encoding_policy(self) -> OpenEncodingPolicy {
220 self.encoding_policy
221 }
222
223 pub const fn encoding_override(self) -> Option<DocumentEncoding> {
228 match self.encoding_policy {
229 OpenEncodingPolicy::Reinterpret(encoding)
230 | OpenEncodingPolicy::AutoDetectOrReinterpret(encoding) => Some(encoding),
231 OpenEncodingPolicy::Utf8FastPath | OpenEncodingPolicy::AutoDetect => None,
232 }
233 }
234}
235
236#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
239pub enum SaveEncodingPolicy {
240 #[default]
242 Preserve,
243 Convert(DocumentEncoding),
245}
246
247#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
249pub struct DocumentSaveOptions {
250 encoding_policy: SaveEncodingPolicy,
251}
252
253impl DocumentSaveOptions {
254 pub const fn new() -> Self {
256 Self {
257 encoding_policy: SaveEncodingPolicy::Preserve,
258 }
259 }
260
261 pub const fn with_encoding(mut self, encoding: DocumentEncoding) -> Self {
263 self.encoding_policy = SaveEncodingPolicy::Convert(encoding);
264 self
265 }
266
267 pub const fn encoding_policy(self) -> SaveEncodingPolicy {
269 self.encoding_policy
270 }
271}
272
273#[derive(Debug, Clone, Default, PartialEq, Eq)]
274pub struct LineSlice {
275 text: String,
276 exact: bool,
277}
278
279impl LineSlice {
280 pub fn new(text: String, exact: bool) -> Self {
282 Self { text, exact }
283 }
284
285 pub fn text(&self) -> &str {
287 &self.text
288 }
289
290 pub fn into_text(self) -> String {
292 self.text
293 }
294
295 pub fn is_exact(&self) -> bool {
297 self.exact
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.text.is_empty()
303 }
304}
305
306impl AsRef<str> for LineSlice {
307 fn as_ref(&self) -> &str {
308 self.text()
309 }
310}
311
312impl Deref for LineSlice {
313 type Target = str;
314
315 fn deref(&self) -> &Self::Target {
316 self.text()
317 }
318}
319
320impl fmt::Display for LineSlice {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 f.write_str(self.text())
323 }
324}
325
326impl From<LineSlice> for String {
327 fn from(value: LineSlice) -> Self {
328 value.into_text()
329 }
330}
331
332#[derive(Debug, Clone, Default, PartialEq, Eq)]
337pub struct TextSlice {
338 text: String,
339 exact: bool,
340}
341
342impl TextSlice {
343 pub fn new(text: String, exact: bool) -> Self {
345 Self { text, exact }
346 }
347
348 pub fn text(&self) -> &str {
350 &self.text
351 }
352
353 pub fn into_text(self) -> String {
355 self.text
356 }
357
358 pub fn is_exact(&self) -> bool {
360 self.exact
361 }
362
363 pub fn is_empty(&self) -> bool {
365 self.text.is_empty()
366 }
367}
368
369impl AsRef<str> for TextSlice {
370 fn as_ref(&self) -> &str {
371 self.text()
372 }
373}
374
375impl Deref for TextSlice {
376 type Target = str;
377
378 fn deref(&self) -> &Self::Target {
379 self.text()
380 }
381}
382
383impl fmt::Display for TextSlice {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.write_str(self.text())
386 }
387}
388
389impl From<TextSlice> for String {
390 fn from(value: TextSlice) -> Self {
391 value.into_text()
392 }
393}
394
395#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
403pub struct TextPosition {
404 line0: usize,
405 col0: usize,
406}
407
408impl TextPosition {
409 pub const fn new(line0: usize, col0: usize) -> Self {
411 Self { line0, col0 }
412 }
413
414 pub const fn line0(self) -> usize {
416 self.line0
417 }
418
419 pub const fn col0(self) -> usize {
421 self.col0
422 }
423}
424
425impl From<(usize, usize)> for TextPosition {
426 fn from(value: (usize, usize)) -> Self {
427 Self::new(value.0, value.1)
428 }
429}
430
431impl From<TextPosition> for (usize, usize) {
432 fn from(value: TextPosition) -> Self {
433 (value.line0, value.col0)
434 }
435}
436
437#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
445pub struct TextRange {
446 start: TextPosition,
447 len_chars: usize,
448}
449
450impl TextRange {
451 pub const fn new(start: TextPosition, len_chars: usize) -> Self {
453 Self { start, len_chars }
454 }
455
456 pub const fn empty(start: TextPosition) -> Self {
458 Self::new(start, 0)
459 }
460
461 pub const fn start(self) -> TextPosition {
463 self.start
464 }
465
466 pub const fn len_chars(self) -> usize {
468 self.len_chars
469 }
470
471 pub const fn is_empty(self) -> bool {
473 self.len_chars == 0
474 }
475}
476
477#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
479pub struct SearchMatch {
480 range: TextRange,
481 end: TextPosition,
482}
483
484impl SearchMatch {
485 pub const fn new(range: TextRange, end: TextPosition) -> Self {
487 Self { range, end }
488 }
489
490 pub const fn range(self) -> TextRange {
492 self.range
493 }
494
495 pub const fn start(self) -> TextPosition {
497 self.range.start()
498 }
499
500 pub const fn end(self) -> TextPosition {
502 self.end
503 }
504
505 pub const fn len_chars(self) -> usize {
507 self.range.len_chars()
508 }
509
510 pub const fn is_empty(self) -> bool {
512 self.range.is_empty()
513 }
514
515 pub const fn selection(self) -> TextSelection {
517 TextSelection::new(self.start(), self.end())
518 }
519}
520
521#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
526pub struct TextSelection {
527 anchor: TextPosition,
528 head: TextPosition,
529}
530
531impl TextSelection {
532 pub const fn new(anchor: TextPosition, head: TextPosition) -> Self {
534 Self { anchor, head }
535 }
536
537 pub const fn caret(position: TextPosition) -> Self {
539 Self::new(position, position)
540 }
541
542 pub const fn anchor(self) -> TextPosition {
544 self.anchor
545 }
546
547 pub const fn head(self) -> TextPosition {
549 self.head
550 }
551
552 pub fn is_caret(self) -> bool {
554 self.anchor == self.head
555 }
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq)]
560pub struct ViewportRequest {
561 first_line0: usize,
562 line_count: usize,
563 start_col: usize,
564 max_cols: usize,
565}
566
567impl Default for ViewportRequest {
568 fn default() -> Self {
569 Self::new(0, 0)
570 }
571}
572
573impl ViewportRequest {
574 pub const fn new(first_line0: usize, line_count: usize) -> Self {
579 Self {
580 first_line0,
581 line_count,
582 start_col: 0,
583 max_cols: usize::MAX,
584 }
585 }
586
587 pub const fn with_columns(mut self, start_col: usize, max_cols: usize) -> Self {
592 self.start_col = start_col;
593 self.max_cols = max_cols;
594 self
595 }
596
597 pub const fn first_line0(self) -> usize {
599 self.first_line0
600 }
601
602 pub const fn line_count(self) -> usize {
604 self.line_count
605 }
606
607 pub const fn start_col(self) -> usize {
609 self.start_col
610 }
611
612 pub const fn max_cols(self) -> usize {
614 self.max_cols
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Eq)]
620pub struct ViewportRow {
621 line0: usize,
622 slice: LineSlice,
623}
624
625impl ViewportRow {
626 pub fn new(line0: usize, slice: LineSlice) -> Self {
628 Self { line0, slice }
629 }
630
631 pub fn line0(&self) -> usize {
633 self.line0
634 }
635
636 pub fn line_number(&self) -> usize {
638 self.line0.saturating_add(1)
639 }
640
641 pub fn slice(&self) -> &LineSlice {
643 &self.slice
644 }
645
646 pub fn into_slice(self) -> LineSlice {
648 self.slice
649 }
650
651 pub fn text(&self) -> &str {
653 self.slice.text()
654 }
655
656 pub fn is_exact(&self) -> bool {
658 self.slice.is_exact()
659 }
660}
661
662#[derive(Debug, Clone, PartialEq, Eq)]
664pub struct Viewport {
665 request: ViewportRequest,
666 total_lines: LineCount,
667 rows: Vec<ViewportRow>,
668}
669
670impl Viewport {
671 pub fn new(request: ViewportRequest, total_lines: LineCount, rows: Vec<ViewportRow>) -> Self {
673 Self {
674 request,
675 total_lines,
676 rows,
677 }
678 }
679
680 pub fn request(&self) -> ViewportRequest {
682 self.request
683 }
684
685 pub fn total_lines(&self) -> LineCount {
687 self.total_lines
688 }
689
690 pub fn rows(&self) -> &[ViewportRow] {
692 &self.rows
693 }
694
695 pub fn into_rows(self) -> Vec<ViewportRow> {
697 self.rows
698 }
699
700 pub fn len(&self) -> usize {
702 self.rows.len()
703 }
704
705 pub fn is_empty(&self) -> bool {
707 self.rows.is_empty()
708 }
709}
710
711#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
713pub struct EditResult {
714 changed: bool,
715 cursor: TextPosition,
716}
717
718impl EditResult {
719 pub const fn new(changed: bool, cursor: TextPosition) -> Self {
721 Self { changed, cursor }
722 }
723
724 pub const fn changed(self) -> bool {
726 self.changed
727 }
728
729 pub const fn cursor(self) -> TextPosition {
731 self.cursor
732 }
733}
734
735#[derive(Debug, Clone, Default, PartialEq, Eq)]
737pub struct CutResult {
738 text: String,
739 edit: EditResult,
740}
741
742impl CutResult {
743 pub fn new(text: String, edit: EditResult) -> Self {
745 Self { text, edit }
746 }
747
748 pub fn text(&self) -> &str {
750 &self.text
751 }
752
753 pub fn into_text(self) -> String {
755 self.text
756 }
757
758 pub const fn edit(&self) -> EditResult {
760 self.edit
761 }
762
763 pub const fn changed(&self) -> bool {
765 self.edit.changed()
766 }
767
768 pub const fn cursor(&self) -> TextPosition {
770 self.edit.cursor()
771 }
772}
773
774#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
776pub struct ByteProgress {
777 completed_bytes: usize,
778 total_bytes: usize,
779}
780
781impl ByteProgress {
782 pub const fn new(completed_bytes: usize, total_bytes: usize) -> Self {
784 Self {
785 completed_bytes,
786 total_bytes,
787 }
788 }
789
790 pub const fn completed_bytes(self) -> usize {
792 self.completed_bytes
793 }
794
795 pub const fn total_bytes(self) -> usize {
797 self.total_bytes
798 }
799
800 pub fn fraction(self) -> f32 {
802 if self.total_bytes == 0 {
803 1.0
804 } else {
805 self.completed_bytes.min(self.total_bytes) as f32 / self.total_bytes as f32
806 }
807 }
808}
809
810#[derive(Debug, Clone, Copy, PartialEq, Eq)]
813#[must_use]
814pub enum LineCount {
815 Exact(usize),
816 Estimated(usize),
817}
818
819impl LineCount {
820 pub fn exact(self) -> Option<usize> {
822 match self {
823 Self::Exact(lines) => Some(lines),
824 Self::Estimated(_) => None,
825 }
826 }
827
828 pub fn display_rows(self) -> usize {
830 match self {
831 Self::Exact(lines) | Self::Estimated(lines) => lines.max(1),
832 }
833 }
834
835 pub fn is_exact(self) -> bool {
837 matches!(self, Self::Exact(_))
838 }
839}
840
841#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
843pub enum DocumentBacking {
844 Mmap,
845 PieceTable,
846 Rope,
847}
848
849impl DocumentBacking {
850 pub const fn as_str(self) -> &'static str {
852 match self {
853 Self::Mmap => "mmap",
854 Self::PieceTable => "piece-table",
855 Self::Rope => "rope",
856 }
857 }
858}
859
860#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
862pub enum EditCapability {
863 Editable {
864 backing: DocumentBacking,
865 },
866 RequiresPromotion {
867 from: DocumentBacking,
868 to: DocumentBacking,
869 },
870 Unsupported {
871 backing: DocumentBacking,
872 reason: &'static str,
873 },
874}
875
876impl EditCapability {
877 pub const fn is_editable(self) -> bool {
879 !matches!(self, Self::Unsupported { .. })
880 }
881
882 pub const fn requires_promotion(self) -> bool {
884 matches!(self, Self::RequiresPromotion { .. })
885 }
886
887 pub const fn current_backing(self) -> DocumentBacking {
889 match self {
890 Self::Editable { backing } | Self::Unsupported { backing, .. } => backing,
891 Self::RequiresPromotion { from, .. } => from,
892 }
893 }
894
895 pub const fn target_backing(self) -> Option<DocumentBacking> {
897 match self {
898 Self::RequiresPromotion { to, .. } => Some(to),
899 _ => None,
900 }
901 }
902
903 pub const fn reason(self) -> Option<&'static str> {
905 match self {
906 Self::Unsupported { reason, .. } => Some(reason),
907 _ => None,
908 }
909 }
910}
911
912#[derive(Debug, Clone, PartialEq, Eq)]
914pub struct DocumentStatus {
915 path: Option<PathBuf>,
916 dirty: bool,
917 file_len: usize,
918 line_count: LineCount,
919 line_ending: LineEnding,
920 encoding: DocumentEncoding,
921 preserve_save_error: Option<DocumentEncodingErrorKind>,
922 encoding_origin: DocumentEncodingOrigin,
923 decoding_had_errors: bool,
924 indexing: Option<ByteProgress>,
925 backing: DocumentBacking,
926}
927
928impl DocumentStatus {
929 #[allow(clippy::too_many_arguments)]
931 pub fn new(
932 path: Option<PathBuf>,
933 dirty: bool,
934 file_len: usize,
935 line_count: LineCount,
936 line_ending: LineEnding,
937 encoding: DocumentEncoding,
938 preserve_save_error: Option<DocumentEncodingErrorKind>,
939 encoding_origin: DocumentEncodingOrigin,
940 decoding_had_errors: bool,
941 indexing: Option<ByteProgress>,
942 backing: DocumentBacking,
943 ) -> Self {
944 Self {
945 path,
946 dirty,
947 file_len,
948 line_count,
949 line_ending,
950 encoding,
951 preserve_save_error,
952 encoding_origin,
953 decoding_had_errors,
954 indexing,
955 backing,
956 }
957 }
958
959 pub fn path(&self) -> Option<&Path> {
961 self.path.as_deref()
962 }
963
964 pub fn is_dirty(&self) -> bool {
966 self.dirty
967 }
968
969 pub fn file_len(&self) -> usize {
971 self.file_len
972 }
973
974 pub fn line_count(&self) -> LineCount {
976 self.line_count
977 }
978
979 pub fn exact_line_count(&self) -> Option<usize> {
981 self.line_count.exact()
982 }
983
984 pub fn display_line_count(&self) -> usize {
986 self.line_count.display_rows()
987 }
988
989 pub fn is_line_count_exact(&self) -> bool {
991 self.line_count.is_exact()
992 }
993
994 pub fn line_ending(&self) -> LineEnding {
996 self.line_ending
997 }
998
999 pub fn encoding(&self) -> DocumentEncoding {
1001 self.encoding
1002 }
1003
1004 pub fn preserve_save_error(&self) -> Option<DocumentEncodingErrorKind> {
1006 self.preserve_save_error
1007 }
1008
1009 pub fn can_preserve_save(&self) -> bool {
1011 self.preserve_save_error().is_none()
1012 }
1013
1014 pub fn encoding_origin(&self) -> DocumentEncodingOrigin {
1016 self.encoding_origin
1017 }
1018
1019 pub fn decoding_had_errors(&self) -> bool {
1021 self.decoding_had_errors
1022 }
1023
1024 pub fn indexing_state(&self) -> Option<ByteProgress> {
1026 self.indexing
1027 }
1028
1029 pub fn is_indexing(&self) -> bool {
1031 self.indexing.is_some()
1032 }
1033
1034 pub fn backing(&self) -> DocumentBacking {
1036 self.backing
1037 }
1038
1039 pub fn has_edit_buffer(&self) -> bool {
1041 !matches!(self.backing, DocumentBacking::Mmap)
1042 }
1043
1044 pub fn has_rope(&self) -> bool {
1046 matches!(self.backing, DocumentBacking::Rope)
1047 }
1048
1049 pub fn has_piece_table(&self) -> bool {
1051 matches!(self.backing, DocumentBacking::PieceTable)
1052 }
1053}
1054
1055#[derive(Debug, Clone, Copy, PartialEq)]
1058pub struct DocumentMaintenanceStatus {
1059 backing: DocumentBacking,
1060 fragmentation: Option<FragmentationStats>,
1061 compaction: Option<CompactionRecommendation>,
1062}
1063
1064#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1066pub enum MaintenanceAction {
1067 None,
1069 IdleCompaction,
1071 ExplicitCompaction,
1073}
1074
1075impl MaintenanceAction {
1076 pub const fn as_str(self) -> &'static str {
1078 match self {
1079 Self::None => "none",
1080 Self::IdleCompaction => "idle-compaction",
1081 Self::ExplicitCompaction => "explicit-compaction",
1082 }
1083 }
1084}
1085
1086impl DocumentMaintenanceStatus {
1087 pub const fn new(
1089 backing: DocumentBacking,
1090 fragmentation: Option<FragmentationStats>,
1091 compaction: Option<CompactionRecommendation>,
1092 ) -> Self {
1093 Self {
1094 backing,
1095 fragmentation,
1096 compaction,
1097 }
1098 }
1099
1100 pub const fn backing(self) -> DocumentBacking {
1102 self.backing
1103 }
1104
1105 pub const fn has_piece_table(self) -> bool {
1107 matches!(self.backing, DocumentBacking::PieceTable)
1108 }
1109
1110 pub const fn fragmentation_stats(self) -> Option<FragmentationStats> {
1112 self.fragmentation
1113 }
1114
1115 pub const fn has_fragmentation_stats(self) -> bool {
1117 self.fragmentation.is_some()
1118 }
1119
1120 pub const fn compaction_recommendation(self) -> Option<CompactionRecommendation> {
1122 self.compaction
1123 }
1124
1125 pub const fn is_compaction_recommended(self) -> bool {
1127 self.compaction.is_some()
1128 }
1129
1130 pub fn compaction_urgency(self) -> Option<CompactionUrgency> {
1132 self.compaction
1133 .map(|recommendation| recommendation.urgency())
1134 }
1135
1136 pub fn recommended_action(self) -> MaintenanceAction {
1138 match self.compaction_urgency() {
1139 Some(CompactionUrgency::Deferred) => MaintenanceAction::IdleCompaction,
1140 Some(CompactionUrgency::Forced) => MaintenanceAction::ExplicitCompaction,
1141 None => MaintenanceAction::None,
1142 }
1143 }
1144
1145 pub fn should_run_idle_compaction(self) -> bool {
1147 self.recommended_action() == MaintenanceAction::IdleCompaction
1148 }
1149
1150 pub fn should_wait_for_explicit_compaction(self) -> bool {
1153 self.recommended_action() == MaintenanceAction::ExplicitCompaction
1154 }
1155}
1156
1157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1158pub enum DocumentEncodingErrorKind {
1159 OpenTranscodeTooLarge { max_bytes: usize },
1161 SaveReopenTooLarge { max_bytes: usize },
1164 PreserveSaveUnsupported,
1166 LossyDecodedPreserve,
1168 UnsupportedSaveTarget,
1170 RedirectedSaveTarget { actual: DocumentEncoding },
1172 UnrepresentableText,
1174}
1175
1176impl std::fmt::Display for DocumentEncodingErrorKind {
1177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1178 match self {
1179 Self::OpenTranscodeTooLarge { max_bytes } => write!(
1180 f,
1181 "non-UTF8 open currently requires full transcoding and is limited to {max_bytes} bytes"
1182 ),
1183 Self::SaveReopenTooLarge { max_bytes } => write!(
1184 f,
1185 "saving to this non-UTF8 target would require reopening a full transcoded buffer and is limited to {max_bytes} bytes"
1186 ),
1187 Self::PreserveSaveUnsupported => f.write_str(
1188 "preserve-save is not yet supported for this encoding; use DocumentSaveOptions::with_encoding(...) to convert to a supported target",
1189 ),
1190 Self::LossyDecodedPreserve => f.write_str(
1191 "preserve-save is rejected because opening this document already required lossy decoding; convert explicitly if you want to keep the repaired text",
1192 ),
1193 Self::UnsupportedSaveTarget => {
1194 f.write_str("this encoding is not yet supported as a save target")
1195 }
1196 Self::RedirectedSaveTarget { actual } => write!(
1197 f,
1198 "encoding_rs redirected this save target to `{actual}`"
1199 ),
1200 Self::UnrepresentableText => f.write_str(
1201 "the current document contains characters that are not representable in the target encoding",
1202 ),
1203 }
1204 }
1205}
1206
1207#[derive(Debug)]
1209pub enum DocumentError {
1210 Open { path: PathBuf, source: io::Error },
1212 Map { path: PathBuf, source: io::Error },
1214 Write { path: PathBuf, source: io::Error },
1216 Encoding {
1218 path: PathBuf,
1219 operation: &'static str,
1220 encoding: DocumentEncoding,
1221 reason: DocumentEncodingErrorKind,
1222 },
1223 EditUnsupported {
1225 path: Option<PathBuf>,
1226 reason: &'static str,
1227 },
1228}
1229
1230impl std::fmt::Display for DocumentError {
1231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1232 match self {
1233 Self::Open { path, source } => write!(f, "open `{}`: {source}", path.display()),
1234 Self::Map { path, source } => write!(f, "mmap `{}`: {source}", path.display()),
1235 Self::Write { path, source } => write!(f, "write `{}`: {source}", path.display()),
1236 Self::Encoding {
1237 path,
1238 operation,
1239 encoding,
1240 reason,
1241 } => write!(
1242 f,
1243 "{operation} `{}` with encoding `{encoding}`: {reason}",
1244 path.display()
1245 ),
1246 Self::EditUnsupported { path, reason } => {
1247 if let Some(path) = path {
1248 write!(f, "edit `{}`: {reason}", path.display())
1249 } else {
1250 write!(f, "edit: {reason}")
1251 }
1252 }
1253 }
1254 }
1255}
1256
1257impl std::error::Error for DocumentError {
1258 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1259 match self {
1260 Self::Open { source, .. } | Self::Map { source, .. } | Self::Write { source, .. } => {
1261 Some(source)
1262 }
1263 Self::Encoding { .. } | Self::EditUnsupported { .. } => None,
1264 }
1265 }
1266}