1#![forbid(unsafe_code)]
2
3pub mod cursor;
58pub mod editor;
59pub mod rope;
60pub mod segment;
61pub mod text;
62pub mod view;
63pub mod width_cache;
64pub mod wrap;
65
66#[cfg(feature = "markup")]
67pub mod markup;
68
69#[cfg(feature = "bidi")]
70pub mod bidi;
71
72#[cfg(feature = "normalization")]
73pub mod normalization;
74
75pub mod search;
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
79pub struct TextMeasurement {
80 pub minimum: usize,
82 pub maximum: usize,
84}
85
86impl TextMeasurement {
87 pub const ZERO: Self = Self {
89 minimum: 0,
90 maximum: 0,
91 };
92
93 pub fn union(self, other: Self) -> Self {
95 Self {
96 minimum: self.minimum.max(other.minimum),
97 maximum: self.maximum.max(other.maximum),
98 }
99 }
100
101 pub fn stack(self, other: Self) -> Self {
103 Self {
104 minimum: self.minimum.saturating_add(other.minimum),
105 maximum: self.maximum.saturating_add(other.maximum),
106 }
107 }
108
109 pub fn clamp(self, min_width: Option<usize>, max_width: Option<usize>) -> Self {
111 let mut result = self;
112 if let Some(min_w) = min_width {
113 result.minimum = result.minimum.max(min_w);
114 result.maximum = result.maximum.max(min_w);
115 }
116 if let Some(max_w) = max_width {
117 result.minimum = result.minimum.min(max_w);
118 result.maximum = result.maximum.min(max_w);
119 }
120 result
121 }
122}
123
124pub use cursor::{CursorNavigator, CursorPosition};
125pub use editor::{Editor, Selection};
126pub use rope::Rope;
127pub use segment::{ControlCode, Segment, SegmentLine, SegmentLines, join_lines, split_into_lines};
128pub use text::{Line, Span, Text};
129pub use view::{TextView, ViewLine, Viewport};
130pub use width_cache::{
131 CacheStats, CountMinSketch, DEFAULT_CACHE_CAPACITY, Doorkeeper, TinyLfuWidthCache, WidthCache,
132};
133pub use wrap::{
134 KpBreakResult, WrapMode, WrapOptions, ascii_width, display_width, grapheme_count,
135 grapheme_width, graphemes, has_wide_chars, is_ascii_only, truncate_to_width,
136 truncate_to_width_with_info, truncate_with_ellipsis, word_boundaries, word_segments,
137 wrap_optimal, wrap_text, wrap_text_optimal, wrap_with_options,
138};
139
140#[cfg(feature = "markup")]
141pub use markup::{MarkupError, MarkupParser, parse_markup};
142
143#[cfg(feature = "normalization")]
144pub use normalization::{NormForm, eq_normalized, is_normalized, normalize, normalize_for_search};
145
146pub use search::{
147 SearchResult, search_ascii_case_insensitive, search_exact, search_exact_overlapping,
148};
149#[cfg(feature = "normalization")]
150pub use search::{search_case_insensitive, search_normalized};
151
152#[cfg(test)]
153mod measurement_tests {
154 use super::TextMeasurement;
155
156 #[test]
157 fn union_uses_max_bounds() {
158 let a = TextMeasurement {
159 minimum: 2,
160 maximum: 8,
161 };
162 let b = TextMeasurement {
163 minimum: 4,
164 maximum: 6,
165 };
166 let merged = a.union(b);
167 assert_eq!(
168 merged,
169 TextMeasurement {
170 minimum: 4,
171 maximum: 8
172 }
173 );
174 }
175
176 #[test]
177 fn stack_adds_bounds() {
178 let a = TextMeasurement {
179 minimum: 1,
180 maximum: 5,
181 };
182 let b = TextMeasurement {
183 minimum: 2,
184 maximum: 7,
185 };
186 let stacked = a.stack(b);
187 assert_eq!(
188 stacked,
189 TextMeasurement {
190 minimum: 3,
191 maximum: 12
192 }
193 );
194 }
195
196 #[test]
197 fn clamp_enforces_min() {
198 let measurement = TextMeasurement {
199 minimum: 2,
200 maximum: 6,
201 };
202 let clamped = measurement.clamp(Some(5), None);
203 assert_eq!(
204 clamped,
205 TextMeasurement {
206 minimum: 5,
207 maximum: 6
208 }
209 );
210 }
211
212 #[test]
213 fn clamp_enforces_max() {
214 let measurement = TextMeasurement {
215 minimum: 4,
216 maximum: 10,
217 };
218 let clamped = measurement.clamp(None, Some(6));
219 assert_eq!(
220 clamped,
221 TextMeasurement {
222 minimum: 4,
223 maximum: 6
224 }
225 );
226 }
227
228 #[test]
229 fn clamp_preserves_ordering() {
230 let measurement = TextMeasurement {
231 minimum: 3,
232 maximum: 5,
233 };
234 let clamped = measurement.clamp(Some(7), Some(4));
235 assert!(clamped.minimum <= clamped.maximum);
236 assert_eq!(clamped.minimum, 4);
237 assert_eq!(clamped.maximum, 4);
238 }
239
240 #[test]
241 fn zero_constant() {
242 assert_eq!(TextMeasurement::ZERO.minimum, 0);
243 assert_eq!(TextMeasurement::ZERO.maximum, 0);
244 }
245
246 #[test]
247 fn default_is_zero() {
248 let m = TextMeasurement::default();
249 assert_eq!(m, TextMeasurement::ZERO);
250 }
251
252 #[test]
253 fn union_with_zero_is_identity() {
254 let m = TextMeasurement {
255 minimum: 5,
256 maximum: 10,
257 };
258 assert_eq!(m.union(TextMeasurement::ZERO), m);
259 assert_eq!(TextMeasurement::ZERO.union(m), m);
260 }
261
262 #[test]
263 fn stack_with_zero_is_identity() {
264 let m = TextMeasurement {
265 minimum: 5,
266 maximum: 10,
267 };
268 assert_eq!(m.stack(TextMeasurement::ZERO), m);
269 assert_eq!(TextMeasurement::ZERO.stack(m), m);
270 }
271
272 #[test]
273 fn stack_saturates_on_overflow() {
274 let big = TextMeasurement {
275 minimum: usize::MAX - 1,
276 maximum: usize::MAX,
277 };
278 let one = TextMeasurement {
279 minimum: 5,
280 maximum: 5,
281 };
282 let stacked = big.stack(one);
283 assert_eq!(stacked.maximum, usize::MAX);
285 }
286
287 #[test]
288 fn clamp_no_constraints() {
289 let m = TextMeasurement {
290 minimum: 3,
291 maximum: 7,
292 };
293 let clamped = m.clamp(None, None);
294 assert_eq!(clamped, m);
295 }
296
297 #[test]
298 fn clamp_min_raises_both_bounds() {
299 let m = TextMeasurement {
300 minimum: 1,
301 maximum: 2,
302 };
303 let clamped = m.clamp(Some(5), None);
305 assert_eq!(clamped.minimum, 5);
306 assert_eq!(clamped.maximum, 5);
307 }
308
309 #[test]
310 fn union_is_commutative() {
311 let a = TextMeasurement {
312 minimum: 2,
313 maximum: 8,
314 };
315 let b = TextMeasurement {
316 minimum: 4,
317 maximum: 6,
318 };
319 assert_eq!(a.union(b), b.union(a));
320 }
321
322 #[test]
323 fn stack_is_commutative() {
324 let a = TextMeasurement {
325 minimum: 2,
326 maximum: 8,
327 };
328 let b = TextMeasurement {
329 minimum: 4,
330 maximum: 6,
331 };
332 assert_eq!(a.stack(b), b.stack(a));
333 }
334}