1use atelier_sdk_diff::PackageId;
2
3use crate::error::Error;
4
5pub const READ_WINDOW_MAX: usize = 50_000;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ReadWindow {
13 pub start: usize,
15 pub end: usize,
17 pub total: usize,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ReadResult {
24 pub content: String,
26 pub window: ReadWindow,
28 pub next: Option<usize>,
31 pub projected_by: Option<PackageId>,
33}
34
35pub(crate) fn window_size(requested: Option<usize>) -> Result<usize, Error> {
37 match requested {
38 None => Ok(READ_WINDOW_MAX),
39 Some(size) if (1..=READ_WINDOW_MAX).contains(&size) => Ok(size),
40 Some(_) => Err(Error::WindowTooLarge {
41 max: READ_WINDOW_MAX,
42 }),
43 }
44}
45
46pub(crate) fn window_text(
50 text: &str,
51 start: usize,
52 size: usize,
53 projected_by: Option<PackageId>,
54) -> ReadResult {
55 let total = text.len();
56 let start = snap_forward(text, start.min(total));
57 let end = snap_back(text, start.saturating_add(size).min(total)).max(start);
58 assert!(start <= end);
61 assert!(end <= total);
62 assert!(
63 end - start <= size + 3,
64 "a boundary snap moves at most one character"
65 );
66 ReadResult {
67 content: text[start..end].to_owned(),
68 window: ReadWindow { start, end, total },
69 next: (end < total).then_some(end),
70 projected_by,
71 }
72}
73
74fn snap_forward(text: &str, mut at: usize) -> usize {
75 while at < text.len() && !text.is_char_boundary(at) {
76 at += 1;
77 }
78 at
79}
80
81fn snap_back(text: &str, mut at: usize) -> usize {
82 while at > 0 && !text.is_char_boundary(at) {
83 at -= 1;
84 }
85 at
86}
87
88#[cfg(test)]
89mod tests {
90 use super::{READ_WINDOW_MAX, window_size, window_text};
91
92 #[test]
93 fn windows_chain_through_the_text_and_reassemble_it() {
94 let text = "0123456789";
95
96 let first = window_text(text, 0, 4, None);
97 assert_eq!(first.content, "0123");
98 assert_eq!((first.window.start, first.window.end), (0, 4));
99 assert_eq!(first.window.total, 10);
100 assert_eq!(first.next, Some(4));
101
102 let second = window_text(text, 4, 4, None);
103 assert_eq!(second.content, "4567");
104 assert_eq!(second.next, Some(8));
105
106 let last = window_text(text, 8, 4, None);
107 assert_eq!(last.content, "89");
108 assert_eq!(last.next, None);
109
110 assert_eq!(
111 format!("{}{}{}", first.content, second.content, last.content),
112 text
113 );
114 }
115
116 #[test]
117 fn window_edges_snap_to_character_boundaries() {
118 let text = "ané";
120 let clipped = window_text(text, 0, 3, None);
121 assert_eq!(clipped.content, "an");
122 assert_eq!(clipped.next, Some(2));
123
124 let rest = window_text(text, 2, 3, None);
125 assert_eq!(rest.content, "é");
126 assert_eq!(rest.next, None);
127 }
128
129 #[test]
130 fn a_start_past_the_end_yields_an_empty_final_window() {
131 let result = window_text("abc", 10, 5, None);
132 assert_eq!(result.content, "");
133 assert_eq!((result.window.start, result.window.end), (3, 3));
134 assert_eq!(result.next, None);
135 }
136
137 #[test]
138 fn window_sizes_are_bounded() {
139 assert_eq!(window_size(None).unwrap(), READ_WINDOW_MAX);
140 assert_eq!(window_size(Some(1)).unwrap(), 1);
141 assert!(window_size(Some(0)).is_err());
142 assert!(window_size(Some(READ_WINDOW_MAX + 1)).is_err());
143 }
144}