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 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 derive;
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 None,
109 #[default]
112 Overwrite,
113 SelectAll,
115}
116
117#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
122pub enum TextFocusLost {
123 None,
125 #[default]
128 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 scroll: Option<ScrollStyle>,
149 pub block: Option<Block<'static>>,
150 pub border_style: Option<Style>,
151 pub title_style: Option<Style>,
152 pub focus: Option<Style>,
153 pub select: Option<Style>,
154 pub invalid: Option<Style>,
155
156 pub on_focus_gained: Option<TextFocusGained>,
158 pub on_focus_lost: Option<TextFocusLost>,
160 pub on_tab: Option<TextTab>,
162
163 pub non_exhaustive: NonExhaustive,
164}
165
166impl Default for TextStyle {
167 fn default() -> Self {
168 Self {
169 style: Default::default(),
170 scroll: Default::default(),
171 block: Default::default(),
172 border_style: Default::default(),
173 title_style: Default::default(),
174 focus: Default::default(),
175 select: Default::default(),
176 invalid: Default::default(),
177 on_focus_gained: Default::default(),
178 on_focus_lost: Default::default(),
179 on_tab: Default::default(),
180 non_exhaustive: NonExhaustive,
181 }
182 }
183}
184
185pub mod core {
186 pub use crate::text_core::TextCore;
192 pub use crate::text_core::core_op;
193 pub use crate::text_store::SkipLine;
194 pub use crate::text_store::TextStore;
195 pub use crate::text_store::text_rope::TextRope;
196 pub use crate::text_store::text_string::TextString;
197}
198
199#[derive(Debug, PartialEq)]
200pub enum TextError {
201 InvalidText(String),
203 Clipboard,
205 TextRangeOutOfBounds(TextRange),
207 TextPositionOutOfBounds(TextPosition),
209 LineIndexOutOfBounds(upos_type, upos_type),
214 ColumnIndexOutOfBounds(upos_type, upos_type),
216 ByteIndexOutOfBounds(usize, usize),
221 CharIndexOutOfBounds(usize, usize),
226 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
232 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
239 ByteIndexNotCharBoundary(usize),
243 ByteRangeNotCharBoundary(
250 Option<usize>, Option<usize>, ),
253 ByteRangeInvalid(
258 usize, usize, ),
261 CharRangeInvalid(
266 usize, usize, ),
269 InvalidSearch,
271 InvalidFmt,
273 InvalidValue,
275}
276
277impl Display for TextError {
278 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
279 write!(f, "{:?}", self)
280 }
281}
282
283impl From<std::fmt::Error> for TextError {
284 fn from(_: std::fmt::Error) -> Self {
285 TextError::InvalidFmt
286 }
287}
288
289impl Error for TextError {}
290
291#[allow(non_camel_case_types)]
293pub type upos_type = u32;
294#[allow(non_camel_case_types)]
296pub type ipos_type = i32;
297
298#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
300pub struct TextPosition {
301 pub y: upos_type,
302 pub x: upos_type,
303}
304
305impl TextPosition {
306 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
308 Self { y, x }
309 }
310}
311
312impl Debug for TextPosition {
313 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
314 write!(f, "{}|{}", self.x, self.y)
315 }
316}
317
318impl From<(upos_type, upos_type)> for TextPosition {
319 fn from(value: (upos_type, upos_type)) -> Self {
320 Self {
321 y: value.1,
322 x: value.0,
323 }
324 }
325}
326
327impl From<TextPosition> for (upos_type, upos_type) {
328 fn from(value: TextPosition) -> Self {
329 (value.x, value.y)
330 }
331}
332
333#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
336pub struct TextRange {
337 pub start: TextPosition,
339 pub end: TextPosition,
341}
342
343impl Debug for TextRange {
344 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
345 write!(
346 f,
347 "{}|{}-{}|{}",
348 self.start.x, self.start.y, self.end.x, self.end.y
349 )
350 }
351}
352
353impl From<Range<TextPosition>> for TextRange {
354 fn from(value: Range<TextPosition>) -> Self {
355 assert!(value.start <= value.end);
356 Self {
357 start: value.start,
358 end: value.end,
359 }
360 }
361}
362
363impl From<Range<(upos_type, upos_type)>> for TextRange {
364 fn from(value: Range<(upos_type, upos_type)>) -> Self {
365 Self {
366 start: TextPosition::from(value.start),
367 end: TextPosition::from(value.end),
368 }
369 }
370}
371
372impl From<TextRange> for Range<TextPosition> {
373 fn from(value: TextRange) -> Self {
374 value.start..value.end
375 }
376}
377
378impl TextRange {
379 pub const MAX: TextRange = TextRange {
381 start: TextPosition {
382 y: upos_type::MAX,
383 x: upos_type::MAX,
384 },
385 end: TextPosition {
386 y: upos_type::MAX,
387 x: upos_type::MAX,
388 },
389 };
390
391 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
396 let start = start.into();
397 let end = end.into();
398
399 assert!(start <= end);
400
401 TextRange { start, end }
402 }
403
404 #[inline]
406 pub fn is_empty(&self) -> bool {
407 self.start == self.end
408 }
409
410 #[inline]
412 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
413 let pos = pos.into();
414 pos >= self.start && pos < self.end
415 }
416
417 #[inline]
419 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
420 let pos = pos.into();
421 pos >= self.end
422 }
423
424 #[inline]
426 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
427 let pos = pos.into();
428 pos < self.start
429 }
430
431 #[inline(always)]
433 pub fn contains(&self, other: TextRange) -> bool {
434 other.start >= self.start && other.end <= self.end
435 }
436
437 #[inline(always)]
439 pub fn before(&self, other: TextRange) -> bool {
440 other.start > self.end
441 }
442
443 #[inline(always)]
445 pub fn after(&self, other: TextRange) -> bool {
446 other.end < self.start
447 }
448
449 #[inline(always)]
451 pub fn intersects(&self, other: TextRange) -> bool {
452 other.start <= self.end && other.end >= self.start
453 }
454
455 #[inline]
458 pub fn expand(&self, range: TextRange) -> TextRange {
459 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
460 }
461
462 #[inline]
465 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
466 let delta_lines = self.end.y - self.start.y;
467
468 if pos < self.start {
470 pos
471 } else if pos == self.start {
472 self.end
473 } else {
474 if pos.y > self.start.y {
475 TextPosition::new(pos.x, pos.y + delta_lines)
476 } else if pos.y == self.start.y {
477 if pos.x >= self.start.x {
478 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
479 } else {
480 pos
481 }
482 } else {
483 pos
484 }
485 }
486 }
487
488 #[inline]
491 pub fn shrink(&self, range: TextRange) -> TextRange {
492 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
493 }
494
495 #[inline]
498 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
499 let delta_lines = self.end.y - self.start.y;
500
501 if pos < self.start {
503 pos
504 } else if pos >= self.start && pos <= self.end {
505 self.start
506 } else {
507 if pos.y > self.end.y {
509 TextPosition::new(pos.x, pos.y - delta_lines)
510 } else if pos.y == self.end.y {
511 if pos.x >= self.end.x {
512 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
513 } else {
514 pos
515 }
516 } else {
517 pos
518 }
519 }
520 }
521}
522
523pub trait Cursor: Iterator {
528 fn prev(&mut self) -> Option<Self::Item>;
530
531 fn peek_next(&mut self) -> Option<Self::Item> {
533 let v = self.next();
534 self.prev();
535 v
536 }
537
538 fn peek_prev(&mut self) -> Option<Self::Item> {
540 let v = self.prev();
541 self.next();
542 v
543 }
544
545 fn text_offset(&self) -> usize;
547}
548
549mod _private {
550 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
551 pub struct NonExhaustive;
552}