1#![doc = include_str!("../readme.md")]
2#![allow(clippy::uninlined_format_args)]
3use std::error::Error;
4use std::fmt::{Debug, Display, Formatter};
5use std::ops::Range;
6
7pub mod clipboard;
8#[cfg(feature = "palette")]
9pub mod color_input;
10pub mod date_input;
11pub mod line_number;
12pub mod number_input;
13pub mod text_area;
14pub mod text_input;
15pub mod text_input_mask;
16pub mod undo_buffer;
17
18mod cache;
19mod glyph2;
20mod grapheme;
21mod range_map;
22mod text_core;
23mod text_store;
24
25pub use grapheme::Grapheme;
26
27use crate::_private::NonExhaustive;
28pub use pure_rust_locales::Locale;
29pub use rat_cursor::{HasScreenCursor, impl_screen_cursor, screen_cursor};
30use rat_scrolled::ScrollStyle;
31use ratatui::style::Style;
32use ratatui::widgets::Block;
33
34pub mod event {
35 pub use rat_event::*;
40
41 #[derive(Debug)]
43 pub struct ReadOnly;
44
45 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
47 pub enum TextOutcome {
48 Continue,
50 Unchanged,
53 Changed,
58 TextChanged,
60 }
61
62 impl ConsumedEvent for TextOutcome {
63 fn is_consumed(&self) -> bool {
64 *self != TextOutcome::Continue
65 }
66 }
67
68 impl From<bool> for TextOutcome {
70 fn from(value: bool) -> Self {
71 if value {
72 TextOutcome::Changed
73 } else {
74 TextOutcome::Unchanged
75 }
76 }
77 }
78
79 impl From<Outcome> for TextOutcome {
80 fn from(value: Outcome) -> Self {
81 match value {
82 Outcome::Continue => TextOutcome::Continue,
83 Outcome::Unchanged => TextOutcome::Unchanged,
84 Outcome::Changed => TextOutcome::Changed,
85 }
86 }
87 }
88
89 impl From<TextOutcome> for Outcome {
90 fn from(value: TextOutcome) -> Self {
91 match value {
92 TextOutcome::Continue => Outcome::Continue,
93 TextOutcome::Unchanged => Outcome::Unchanged,
94 TextOutcome::Changed => Outcome::Changed,
95 TextOutcome::TextChanged => Outcome::Changed,
96 }
97 }
98 }
99}
100
101#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
106pub enum TextFocusGained {
107 #[default]
109 None,
110 Overwrite,
113 SelectAll,
115}
116
117#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
122pub enum TextFocusLost {
123 #[default]
125 None,
126 Position0,
129}
130
131#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
136pub enum TextTab {
137 #[default]
139 MoveToNextSection,
140 MoveToNextWidget,
142}
143
144#[derive(Debug, Clone)]
146pub struct TextStyle {
147 pub style: Style,
148 pub focus: Option<Style>,
149 pub select: Option<Style>,
150 pub invalid: Option<Style>,
151
152 pub on_focus_gained: Option<TextFocusGained>,
154 pub on_focus_lost: Option<TextFocusLost>,
156 pub on_tab: Option<TextTab>,
158
159 pub scroll: Option<ScrollStyle>,
160 pub block: Option<Block<'static>>,
161 pub border_style: Option<Style>,
162
163 pub non_exhaustive: NonExhaustive,
164}
165
166impl Default for TextStyle {
167 fn default() -> Self {
168 Self {
169 style: Default::default(),
170 focus: None,
171 select: None,
172 invalid: None,
173 on_focus_gained: None,
174 on_focus_lost: None,
175 on_tab: None,
176 scroll: None,
177 block: None,
178 border_style: None,
179 non_exhaustive: NonExhaustive,
180 }
181 }
182}
183
184pub mod core {
185 pub use crate::text_core::TextCore;
191 pub use crate::text_core::core_op;
192 pub use crate::text_store::SkipLine;
193 pub use crate::text_store::TextStore;
194 pub use crate::text_store::text_rope::TextRope;
195 pub use crate::text_store::text_string::TextString;
196}
197
198#[derive(Debug, PartialEq)]
199pub enum TextError {
200 InvalidText(String),
202 Clipboard,
204 TextRangeOutOfBounds(TextRange),
206 TextPositionOutOfBounds(TextPosition),
208 LineIndexOutOfBounds(upos_type, upos_type),
213 ColumnIndexOutOfBounds(upos_type, upos_type),
215 ByteIndexOutOfBounds(usize, usize),
220 CharIndexOutOfBounds(usize, usize),
225 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
231 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
238 ByteIndexNotCharBoundary(usize),
242 ByteRangeNotCharBoundary(
249 Option<usize>, Option<usize>, ),
252 ByteRangeInvalid(
257 usize, usize, ),
260 CharRangeInvalid(
265 usize, usize, ),
268 InvalidSearch,
270}
271
272impl Display for TextError {
273 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
274 write!(f, "{:?}", self)
275 }
276}
277
278impl Error for TextError {}
279
280#[allow(non_camel_case_types)]
282pub type upos_type = u32;
283#[allow(non_camel_case_types)]
285pub type ipos_type = i32;
286
287#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
289pub struct TextPosition {
290 pub y: upos_type,
291 pub x: upos_type,
292}
293
294impl TextPosition {
295 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
297 Self { y, x }
298 }
299}
300
301impl Debug for TextPosition {
302 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
303 write!(f, "{}|{}", self.x, self.y)
304 }
305}
306
307impl From<(upos_type, upos_type)> for TextPosition {
308 fn from(value: (upos_type, upos_type)) -> Self {
309 Self {
310 y: value.1,
311 x: value.0,
312 }
313 }
314}
315
316impl From<TextPosition> for (upos_type, upos_type) {
317 fn from(value: TextPosition) -> Self {
318 (value.x, value.y)
319 }
320}
321
322#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
325pub struct TextRange {
326 pub start: TextPosition,
328 pub end: TextPosition,
330}
331
332impl Debug for TextRange {
333 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
334 write!(
335 f,
336 "{}|{}-{}|{}",
337 self.start.x, self.start.y, self.end.x, self.end.y
338 )
339 }
340}
341
342impl From<Range<TextPosition>> for TextRange {
343 fn from(value: Range<TextPosition>) -> Self {
344 assert!(value.start <= value.end);
345 Self {
346 start: value.start,
347 end: value.end,
348 }
349 }
350}
351
352impl From<Range<(upos_type, upos_type)>> for TextRange {
353 fn from(value: Range<(upos_type, upos_type)>) -> Self {
354 Self {
355 start: TextPosition::from(value.start),
356 end: TextPosition::from(value.end),
357 }
358 }
359}
360
361impl From<TextRange> for Range<TextPosition> {
362 fn from(value: TextRange) -> Self {
363 value.start..value.end
364 }
365}
366
367impl TextRange {
368 pub const MAX: TextRange = TextRange {
370 start: TextPosition {
371 y: upos_type::MAX,
372 x: upos_type::MAX,
373 },
374 end: TextPosition {
375 y: upos_type::MAX,
376 x: upos_type::MAX,
377 },
378 };
379
380 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
385 let start = start.into();
386 let end = end.into();
387
388 assert!(start <= end);
389
390 TextRange { start, end }
391 }
392
393 #[inline]
395 pub fn is_empty(&self) -> bool {
396 self.start == self.end
397 }
398
399 #[inline]
401 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
402 let pos = pos.into();
403 pos >= self.start && pos < self.end
404 }
405
406 #[inline]
408 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
409 let pos = pos.into();
410 pos >= self.end
411 }
412
413 #[inline]
415 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
416 let pos = pos.into();
417 pos < self.start
418 }
419
420 #[inline(always)]
422 pub fn contains(&self, other: TextRange) -> bool {
423 other.start >= self.start && other.end <= self.end
424 }
425
426 #[inline(always)]
428 pub fn before(&self, other: TextRange) -> bool {
429 other.start > self.end
430 }
431
432 #[inline(always)]
434 pub fn after(&self, other: TextRange) -> bool {
435 other.end < self.start
436 }
437
438 #[inline(always)]
440 pub fn intersects(&self, other: TextRange) -> bool {
441 other.start <= self.end && other.end >= self.start
442 }
443
444 #[inline]
447 pub fn expand(&self, range: TextRange) -> TextRange {
448 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
449 }
450
451 #[inline]
454 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
455 let delta_lines = self.end.y - self.start.y;
456
457 if pos < self.start {
459 pos
460 } else if pos == self.start {
461 self.end
462 } else {
463 if pos.y > self.start.y {
464 TextPosition::new(pos.x, pos.y + delta_lines)
465 } else if pos.y == self.start.y {
466 if pos.x >= self.start.x {
467 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
468 } else {
469 pos
470 }
471 } else {
472 pos
473 }
474 }
475 }
476
477 #[inline]
480 pub fn shrink(&self, range: TextRange) -> TextRange {
481 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
482 }
483
484 #[inline]
487 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
488 let delta_lines = self.end.y - self.start.y;
489
490 if pos < self.start {
492 pos
493 } else if pos >= self.start && pos <= self.end {
494 self.start
495 } else {
496 if pos.y > self.end.y {
498 TextPosition::new(pos.x, pos.y - delta_lines)
499 } else if pos.y == self.end.y {
500 if pos.x >= self.end.x {
501 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
502 } else {
503 pos
504 }
505 } else {
506 pos
507 }
508 }
509 }
510}
511
512pub trait Cursor: Iterator {
517 fn prev(&mut self) -> Option<Self::Item>;
519
520 fn peek_next(&mut self) -> Option<Self::Item> {
522 let v = self.next();
523 self.prev();
524 v
525 }
526
527 fn peek_prev(&mut self) -> Option<Self::Item> {
529 let v = self.prev();
530 self.next();
531 v
532 }
533
534 fn text_offset(&self) -> usize;
536}
537
538mod _private {
539 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
540 pub struct NonExhaustive;
541}