1#![doc = include_str!("../readme.md")]
2
3use std::error::Error;
4use std::fmt::{Debug, Display, Formatter};
5use std::ops::Range;
6
7pub mod clipboard;
8pub mod date_input;
9pub mod line_number;
10pub mod number_input;
11pub mod text_area;
12pub mod text_input;
13pub mod text_input_mask;
14pub mod undo_buffer;
15
16mod grapheme;
17mod range_map;
18mod text_core;
19mod text_mask_core;
20mod text_store;
21
22pub use grapheme::{Glyph, Grapheme};
23
24use crate::_private::NonExhaustive;
25pub use pure_rust_locales::Locale;
26pub use rat_cursor::{impl_screen_cursor, screen_cursor, HasScreenCursor};
27use rat_scrolled::ScrollStyle;
28use ratatui::style::Style;
29use ratatui::widgets::Block;
30
31pub mod event {
32 pub use rat_event::*;
37
38 #[derive(Debug)]
40 pub struct ReadOnly;
41
42 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44 pub enum TextOutcome {
45 Continue,
47 Unchanged,
50 Changed,
55 TextChanged,
57 }
58
59 impl ConsumedEvent for TextOutcome {
60 fn is_consumed(&self) -> bool {
61 *self != TextOutcome::Continue
62 }
63 }
64
65 impl From<bool> for TextOutcome {
67 fn from(value: bool) -> Self {
68 if value {
69 TextOutcome::Changed
70 } else {
71 TextOutcome::Unchanged
72 }
73 }
74 }
75
76 impl From<Outcome> for TextOutcome {
77 fn from(value: Outcome) -> Self {
78 match value {
79 Outcome::Continue => TextOutcome::Continue,
80 Outcome::Unchanged => TextOutcome::Unchanged,
81 Outcome::Changed => TextOutcome::Changed,
82 }
83 }
84 }
85
86 impl From<TextOutcome> for Outcome {
87 fn from(value: TextOutcome) -> Self {
88 match value {
89 TextOutcome::Continue => Outcome::Continue,
90 TextOutcome::Unchanged => Outcome::Unchanged,
91 TextOutcome::Changed => Outcome::Changed,
92 TextOutcome::TextChanged => Outcome::Changed,
93 }
94 }
95 }
96}
97
98#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
100pub enum TextFocusGained {
101 #[default]
103 None,
104 Overwrite,
106 SelectAll,
108}
109
110#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
112pub enum TextFocusLost {
113 #[default]
115 None,
116 Position0,
118}
119
120#[derive(Debug, Clone)]
122pub struct TextStyle {
123 pub style: Style,
124 pub focus: Option<Style>,
125 pub select: Option<Style>,
126 pub invalid: Option<Style>,
127
128 pub on_focus_gained: Option<TextFocusGained>,
130 pub on_focus_lost: Option<TextFocusLost>,
132
133 pub scroll: Option<ScrollStyle>,
134 pub block: Option<Block<'static>>,
135 pub border_style: Option<Style>,
136
137 pub non_exhaustive: NonExhaustive,
138}
139
140impl Default for TextStyle {
141 fn default() -> Self {
142 Self {
143 style: Default::default(),
144 focus: None,
145 select: None,
146 invalid: None,
147 on_focus_gained: None,
148 on_focus_lost: None,
149 scroll: None,
150 block: None,
151 border_style: None,
152 non_exhaustive: NonExhaustive,
153 }
154 }
155}
156
157pub mod core {
158 pub use crate::text_core::TextCore;
164 pub use crate::text_mask_core::MaskedCore;
165 pub use crate::text_store::text_rope::TextRope;
166 pub use crate::text_store::text_string::TextString;
167 pub use crate::text_store::TextStore;
168}
169
170#[derive(Debug, PartialEq)]
171pub enum TextError {
172 InvalidText(String),
174 Clipboard,
176 TextRangeOutOfBounds(TextRange),
178 TextPositionOutOfBounds(TextPosition),
180 LineIndexOutOfBounds(upos_type, upos_type),
185 ColumnIndexOutOfBounds(upos_type, upos_type),
187 ByteIndexOutOfBounds(usize, usize),
192 CharIndexOutOfBounds(usize, usize),
197 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
203 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
210 ByteIndexNotCharBoundary(usize),
214 ByteRangeNotCharBoundary(
221 Option<usize>, Option<usize>, ),
224 ByteRangeInvalid(
229 usize, usize, ),
232 CharRangeInvalid(
237 usize, usize, ),
240}
241
242impl Display for TextError {
243 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
244 write!(f, "{:?}", self)
245 }
246}
247
248impl Error for TextError {}
249
250#[allow(non_camel_case_types)]
252pub type upos_type = u32;
253#[allow(non_camel_case_types)]
255pub type ipos_type = i32;
256
257#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
259pub struct TextPosition {
260 pub y: upos_type,
261 pub x: upos_type,
262}
263
264impl TextPosition {
265 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
267 Self { y, x }
268 }
269}
270
271impl Debug for TextPosition {
272 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
273 write!(f, "{}|{}", self.x, self.y)
274 }
275}
276
277impl From<(upos_type, upos_type)> for TextPosition {
278 fn from(value: (upos_type, upos_type)) -> Self {
279 Self {
280 y: value.1,
281 x: value.0,
282 }
283 }
284}
285
286impl From<TextPosition> for (upos_type, upos_type) {
287 fn from(value: TextPosition) -> Self {
288 (value.x, value.y)
289 }
290}
291
292#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
294pub struct TextRange {
295 pub start: TextPosition,
297 pub end: TextPosition,
299}
300
301impl Debug for TextRange {
302 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
303 write!(
304 f,
305 "{}|{}-{}|{}",
306 self.start.x, self.start.y, self.end.x, self.end.y
307 )
308 }
309}
310
311impl From<Range<TextPosition>> for TextRange {
312 fn from(value: Range<TextPosition>) -> Self {
313 assert!(value.start <= value.end);
314 Self {
315 start: value.start,
316 end: value.end,
317 }
318 }
319}
320
321impl From<TextRange> for Range<TextPosition> {
322 fn from(value: TextRange) -> Self {
323 value.start..value.end
324 }
325}
326
327impl TextRange {
328 pub const MAX: TextRange = TextRange {
330 start: TextPosition {
331 y: upos_type::MAX,
332 x: upos_type::MAX,
333 },
334 end: TextPosition {
335 y: upos_type::MAX,
336 x: upos_type::MAX,
337 },
338 };
339
340 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
345 let start = start.into();
346 let end = end.into();
347
348 assert!(start <= end);
349
350 TextRange { start, end }
351 }
352
353 #[inline]
355 pub fn is_empty(&self) -> bool {
356 self.start == self.end
357 }
358
359 #[inline]
361 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
362 let pos = pos.into();
363 pos >= self.start && pos < self.end
364 }
365
366 #[inline]
368 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
369 let pos = pos.into();
370 pos >= self.end
371 }
372
373 #[inline]
375 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
376 let pos = pos.into();
377 pos < self.start
378 }
379
380 #[inline(always)]
382 pub fn contains(&self, other: TextRange) -> bool {
383 other.start >= self.start && other.end <= self.end
384 }
385
386 #[inline(always)]
388 pub fn before(&self, other: TextRange) -> bool {
389 other.start > self.end
390 }
391
392 #[inline(always)]
394 pub fn after(&self, other: TextRange) -> bool {
395 other.end < self.start
396 }
397
398 #[inline(always)]
400 pub fn intersects(&self, other: TextRange) -> bool {
401 other.start <= self.end && other.end >= self.start
402 }
403
404 #[inline]
407 pub fn expand(&self, range: TextRange) -> TextRange {
408 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
409 }
410
411 #[inline]
414 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
415 let delta_lines = self.end.y - self.start.y;
416
417 if pos < self.start {
419 pos
420 } else if pos == self.start {
421 self.end
422 } else {
423 if pos.y > self.start.y {
424 TextPosition::new(pos.x, pos.y + delta_lines)
425 } else if pos.y == self.start.y {
426 if pos.x >= self.start.x {
427 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
428 } else {
429 pos
430 }
431 } else {
432 pos
433 }
434 }
435 }
436
437 #[inline]
440 pub fn shrink(&self, range: TextRange) -> TextRange {
441 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
442 }
443
444 #[inline]
447 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
448 let delta_lines = self.end.y - self.start.y;
449
450 if pos < self.start {
452 pos
453 } else if pos >= self.start && pos <= self.end {
454 self.start
455 } else {
456 if pos.y > self.end.y {
458 TextPosition::new(pos.x, pos.y - delta_lines)
459 } else if pos.y == self.end.y {
460 if pos.x >= self.end.x {
461 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
462 } else {
463 pos
464 }
465 } else {
466 pos
467 }
468 }
469 }
470}
471
472pub trait Cursor: Iterator {
477 fn prev(&mut self) -> Option<Self::Item>;
479
480 fn rev_cursor(self) -> impl Cursor<Item = Self::Item>
483 where
484 Self: Sized;
485
486 fn text_offset(&self) -> usize;
488}
489
490mod _private {
491 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
492 pub struct NonExhaustive;
493}