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;
8pub mod cursor;
9pub mod date_input;
10pub mod line_number;
11pub mod number_input;
12pub mod text_area;
13pub mod text_input;
14pub mod text_input_mask;
15pub mod undo_buffer;
16
17mod derive;
18
19mod cache;
20mod glyph2;
21mod grapheme;
22mod range_map;
23mod text_core;
24mod text_store;
25
26pub use grapheme::Grapheme;
27
28use crate::_private::NonExhaustive;
29pub use pure_rust_locales::Locale;
30pub use rat_cursor::{HasScreenCursor, impl_screen_cursor, screen_cursor};
31use rat_scrolled::ScrollStyle;
32use ratatui::style::Style;
33use ratatui::widgets::Block;
34
35pub mod event {
36 pub use rat_event::*;
41
42 #[derive(Debug)]
44 pub struct ReadOnly;
45
46 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
48 pub enum TextOutcome {
49 Continue,
51 Unchanged,
54 Changed,
59 TextChanged,
61 }
62
63 impl ConsumedEvent for TextOutcome {
64 fn is_consumed(&self) -> bool {
65 *self != TextOutcome::Continue
66 }
67 }
68
69 impl From<bool> for TextOutcome {
71 fn from(value: bool) -> Self {
72 if value {
73 TextOutcome::Changed
74 } else {
75 TextOutcome::Unchanged
76 }
77 }
78 }
79
80 impl From<Outcome> for TextOutcome {
81 fn from(value: Outcome) -> Self {
82 match value {
83 Outcome::Continue => TextOutcome::Continue,
84 Outcome::Unchanged => TextOutcome::Unchanged,
85 Outcome::Changed => TextOutcome::Changed,
86 }
87 }
88 }
89
90 impl From<TextOutcome> for Outcome {
91 fn from(value: TextOutcome) -> Self {
92 match value {
93 TextOutcome::Continue => Outcome::Continue,
94 TextOutcome::Unchanged => Outcome::Unchanged,
95 TextOutcome::Changed => Outcome::Changed,
96 TextOutcome::TextChanged => Outcome::Changed,
97 }
98 }
99 }
100}
101
102#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
107pub enum TextFocusGained {
108 None,
110 #[default]
113 Overwrite,
114 SelectAll,
116}
117
118#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
123pub enum TextFocusLost {
124 None,
126 #[default]
129 Position0,
130}
131
132#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
137pub enum TextTab {
138 #[default]
140 MoveToNextSection,
141 MoveToNextWidget,
143}
144
145#[derive(Debug, Clone)]
147pub struct TextStyle {
148 pub style: Style,
150 pub scroll: Option<ScrollStyle>,
152 pub block: Option<Block<'static>>,
154 pub border_style: Option<Style>,
156 pub title_style: Option<Style>,
158 pub focus: Option<Style>,
160 pub select: Option<Style>,
162 pub invalid: Option<Style>,
164 pub cursor: Option<Style>,
166
167 pub on_focus_gained: Option<TextFocusGained>,
169 pub on_focus_lost: Option<TextFocusLost>,
171 pub on_tab: Option<TextTab>,
173
174 pub non_exhaustive: NonExhaustive,
175}
176
177impl Default for TextStyle {
178 fn default() -> Self {
179 Self {
180 style: Default::default(),
181 scroll: Default::default(),
182 block: Default::default(),
183 border_style: Default::default(),
184 title_style: Default::default(),
185 focus: Default::default(),
186 select: Default::default(),
187 invalid: Default::default(),
188 cursor: Default::default(),
189 on_focus_gained: Default::default(),
190 on_focus_lost: Default::default(),
191 on_tab: Default::default(),
192 non_exhaustive: NonExhaustive,
193 }
194 }
195}
196
197pub mod core {
198 pub use crate::text_core::TextCore;
204 pub use crate::text_core::core_op;
205 pub use crate::text_store::SkipLine;
206 pub use crate::text_store::TextStore;
207 pub use crate::text_store::text_rope::TextRope;
208 pub use crate::text_store::text_string::TextString;
209}
210
211#[derive(Debug, PartialEq)]
212pub enum TextError {
213 InvalidText(String),
215 Clipboard,
217 TextRangeOutOfBounds(TextRange),
219 TextPositionOutOfBounds(TextPosition),
221 LineIndexOutOfBounds(upos_type, upos_type),
226 ColumnIndexOutOfBounds(upos_type, upos_type),
228 ByteIndexOutOfBounds(usize, usize),
233 CharIndexOutOfBounds(usize, usize),
238 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
244 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
251 ByteIndexNotCharBoundary(usize),
255 ByteRangeNotCharBoundary(
262 Option<usize>, Option<usize>, ),
265 ByteRangeInvalid(
270 usize, usize, ),
273 CharRangeInvalid(
278 usize, usize, ),
281 InvalidSearch,
283 InvalidFmt,
285 InvalidValue,
287}
288
289impl Display for TextError {
290 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
291 write!(f, "{:?}", self)
292 }
293}
294
295impl From<std::fmt::Error> for TextError {
296 fn from(_: std::fmt::Error) -> Self {
297 TextError::InvalidFmt
298 }
299}
300
301impl Error for TextError {}
302
303#[allow(non_camel_case_types)]
305pub type upos_type = u32;
306#[allow(non_camel_case_types)]
308pub type ipos_type = i32;
309
310#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
312pub struct TextPosition {
313 pub y: upos_type,
314 pub x: upos_type,
315}
316
317impl TextPosition {
318 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
320 Self { y, x }
321 }
322}
323
324impl Debug for TextPosition {
325 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
326 write!(f, "{}|{}", self.x, self.y)
327 }
328}
329
330impl From<(upos_type, upos_type)> for TextPosition {
331 fn from(value: (upos_type, upos_type)) -> Self {
332 Self {
333 y: value.1,
334 x: value.0,
335 }
336 }
337}
338
339impl From<TextPosition> for (upos_type, upos_type) {
340 fn from(value: TextPosition) -> Self {
341 (value.x, value.y)
342 }
343}
344
345#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
348pub struct TextRange {
349 pub start: TextPosition,
351 pub end: TextPosition,
353}
354
355impl Debug for TextRange {
356 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
357 write!(
358 f,
359 "{}|{}-{}|{}",
360 self.start.x, self.start.y, self.end.x, self.end.y
361 )
362 }
363}
364
365impl From<Range<TextPosition>> for TextRange {
366 fn from(value: Range<TextPosition>) -> Self {
367 assert!(value.start <= value.end);
368 Self {
369 start: value.start,
370 end: value.end,
371 }
372 }
373}
374
375impl From<Range<(upos_type, upos_type)>> for TextRange {
376 fn from(value: Range<(upos_type, upos_type)>) -> Self {
377 Self {
378 start: TextPosition::from(value.start),
379 end: TextPosition::from(value.end),
380 }
381 }
382}
383
384impl From<TextRange> for Range<TextPosition> {
385 fn from(value: TextRange) -> Self {
386 value.start..value.end
387 }
388}
389
390impl TextRange {
391 pub const MAX: TextRange = TextRange {
393 start: TextPosition {
394 y: upos_type::MAX,
395 x: upos_type::MAX,
396 },
397 end: TextPosition {
398 y: upos_type::MAX,
399 x: upos_type::MAX,
400 },
401 };
402
403 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
408 let start = start.into();
409 let end = end.into();
410
411 assert!(start <= end);
412
413 TextRange { start, end }
414 }
415
416 #[inline]
418 pub fn is_empty(&self) -> bool {
419 self.start == self.end
420 }
421
422 #[inline]
424 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
425 let pos = pos.into();
426 pos >= self.start && pos < self.end
427 }
428
429 #[inline]
431 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
432 let pos = pos.into();
433 pos >= self.end
434 }
435
436 #[inline]
438 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
439 let pos = pos.into();
440 pos < self.start
441 }
442
443 #[inline(always)]
445 pub fn contains(&self, other: TextRange) -> bool {
446 other.start >= self.start && other.end <= self.end
447 }
448
449 #[inline(always)]
451 pub fn before(&self, other: TextRange) -> bool {
452 other.start > self.end
453 }
454
455 #[inline(always)]
457 pub fn after(&self, other: TextRange) -> bool {
458 other.end < self.start
459 }
460
461 #[inline(always)]
463 pub fn intersects(&self, other: TextRange) -> bool {
464 other.start <= self.end && other.end >= self.start
465 }
466
467 #[inline]
470 pub fn expand(&self, range: TextRange) -> TextRange {
471 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
472 }
473
474 #[inline]
477 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
478 let delta_lines = self.end.y - self.start.y;
479
480 if pos < self.start {
482 pos
483 } else if pos == self.start {
484 self.end
485 } else {
486 if pos.y > self.start.y {
487 TextPosition::new(pos.x, pos.y + delta_lines)
488 } else if pos.y == self.start.y {
489 if pos.x >= self.start.x {
490 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
491 } else {
492 pos
493 }
494 } else {
495 pos
496 }
497 }
498 }
499
500 #[inline]
503 pub fn shrink(&self, range: TextRange) -> TextRange {
504 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
505 }
506
507 #[inline]
510 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
511 let delta_lines = self.end.y - self.start.y;
512
513 if pos < self.start {
515 pos
516 } else if pos >= self.start && pos <= self.end {
517 self.start
518 } else {
519 if pos.y > self.end.y {
521 TextPosition::new(pos.x, pos.y - delta_lines)
522 } else if pos.y == self.end.y {
523 if pos.x >= self.end.x {
524 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
525 } else {
526 pos
527 }
528 } else {
529 pos
530 }
531 }
532 }
533}
534
535pub trait Cursor: Iterator {
540 fn prev(&mut self) -> Option<Self::Item>;
542
543 fn peek_next(&mut self) -> Option<Self::Item> {
545 let v = self.next();
546 self.prev();
547 v
548 }
549
550 fn peek_prev(&mut self) -> Option<Self::Item> {
552 let v = self.prev();
553 self.next();
554 v
555 }
556
557 fn text_offset(&self) -> usize;
559}
560
561mod _private {
562 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
563 pub struct NonExhaustive;
564}