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 derive;
19
20mod cache;
21mod glyph2;
22mod grapheme;
23mod range_map;
24mod text_core;
25mod text_store;
26
27pub use grapheme::Grapheme;
28
29use crate::_private::NonExhaustive;
30pub use pure_rust_locales::Locale;
31pub use rat_cursor::{HasScreenCursor, impl_screen_cursor, screen_cursor};
32use rat_scrolled::ScrollStyle;
33use ratatui::style::Style;
34use ratatui::widgets::Block;
35
36pub mod event {
37 pub use rat_event::*;
42
43 #[derive(Debug)]
45 pub struct ReadOnly;
46
47 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
49 pub enum TextOutcome {
50 Continue,
52 Unchanged,
55 Changed,
60 TextChanged,
62 }
63
64 impl ConsumedEvent for TextOutcome {
65 fn is_consumed(&self) -> bool {
66 *self != TextOutcome::Continue
67 }
68 }
69
70 impl From<bool> for TextOutcome {
72 fn from(value: bool) -> Self {
73 if value {
74 TextOutcome::Changed
75 } else {
76 TextOutcome::Unchanged
77 }
78 }
79 }
80
81 impl From<Outcome> for TextOutcome {
82 fn from(value: Outcome) -> Self {
83 match value {
84 Outcome::Continue => TextOutcome::Continue,
85 Outcome::Unchanged => TextOutcome::Unchanged,
86 Outcome::Changed => TextOutcome::Changed,
87 }
88 }
89 }
90
91 impl From<TextOutcome> for Outcome {
92 fn from(value: TextOutcome) -> Self {
93 match value {
94 TextOutcome::Continue => Outcome::Continue,
95 TextOutcome::Unchanged => Outcome::Unchanged,
96 TextOutcome::Changed => Outcome::Changed,
97 TextOutcome::TextChanged => Outcome::Changed,
98 }
99 }
100 }
101}
102
103#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
108pub enum TextFocusGained {
109 None,
111 #[default]
114 Overwrite,
115 SelectAll,
117}
118
119#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
124pub enum TextFocusLost {
125 None,
127 #[default]
130 Position0,
131}
132
133#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
138pub enum TextTab {
139 #[default]
141 MoveToNextSection,
142 MoveToNextWidget,
144}
145
146#[derive(Debug, Clone)]
148pub struct TextStyle {
149 pub style: Style,
150 pub scroll: Option<ScrollStyle>,
151 pub block: Option<Block<'static>>,
152 pub border_style: Option<Style>,
153 pub title_style: Option<Style>,
154 pub focus: Option<Style>,
155 pub select: Option<Style>,
156 pub invalid: Option<Style>,
157
158 pub on_focus_gained: Option<TextFocusGained>,
160 pub on_focus_lost: Option<TextFocusLost>,
162 pub on_tab: Option<TextTab>,
164
165 pub non_exhaustive: NonExhaustive,
166}
167
168impl Default for TextStyle {
169 fn default() -> Self {
170 Self {
171 style: Default::default(),
172 scroll: Default::default(),
173 block: Default::default(),
174 border_style: Default::default(),
175 title_style: Default::default(),
176 focus: Default::default(),
177 select: Default::default(),
178 invalid: Default::default(),
179 on_focus_gained: Default::default(),
180 on_focus_lost: Default::default(),
181 on_tab: Default::default(),
182 non_exhaustive: NonExhaustive,
183 }
184 }
185}
186
187pub mod core {
188 pub use crate::text_core::TextCore;
194 pub use crate::text_core::core_op;
195 pub use crate::text_store::SkipLine;
196 pub use crate::text_store::TextStore;
197 pub use crate::text_store::text_rope::TextRope;
198 pub use crate::text_store::text_string::TextString;
199}
200
201#[derive(Debug, PartialEq)]
202pub enum TextError {
203 InvalidText(String),
205 Clipboard,
207 TextRangeOutOfBounds(TextRange),
209 TextPositionOutOfBounds(TextPosition),
211 LineIndexOutOfBounds(upos_type, upos_type),
216 ColumnIndexOutOfBounds(upos_type, upos_type),
218 ByteIndexOutOfBounds(usize, usize),
223 CharIndexOutOfBounds(usize, usize),
228 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
234 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
241 ByteIndexNotCharBoundary(usize),
245 ByteRangeNotCharBoundary(
252 Option<usize>, Option<usize>, ),
255 ByteRangeInvalid(
260 usize, usize, ),
263 CharRangeInvalid(
268 usize, usize, ),
271 InvalidSearch,
273 InvalidFmt,
275 InvalidValue,
277}
278
279impl Display for TextError {
280 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
281 write!(f, "{:?}", self)
282 }
283}
284
285impl From<std::fmt::Error> for TextError {
286 fn from(_: std::fmt::Error) -> Self {
287 TextError::InvalidFmt
288 }
289}
290
291impl Error for TextError {}
292
293#[allow(non_camel_case_types)]
295pub type upos_type = u32;
296#[allow(non_camel_case_types)]
298pub type ipos_type = i32;
299
300#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
302pub struct TextPosition {
303 pub y: upos_type,
304 pub x: upos_type,
305}
306
307impl TextPosition {
308 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
310 Self { y, x }
311 }
312}
313
314impl Debug for TextPosition {
315 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
316 write!(f, "{}|{}", self.x, self.y)
317 }
318}
319
320impl From<(upos_type, upos_type)> for TextPosition {
321 fn from(value: (upos_type, upos_type)) -> Self {
322 Self {
323 y: value.1,
324 x: value.0,
325 }
326 }
327}
328
329impl From<TextPosition> for (upos_type, upos_type) {
330 fn from(value: TextPosition) -> Self {
331 (value.x, value.y)
332 }
333}
334
335#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
338pub struct TextRange {
339 pub start: TextPosition,
341 pub end: TextPosition,
343}
344
345impl Debug for TextRange {
346 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
347 write!(
348 f,
349 "{}|{}-{}|{}",
350 self.start.x, self.start.y, self.end.x, self.end.y
351 )
352 }
353}
354
355impl From<Range<TextPosition>> for TextRange {
356 fn from(value: Range<TextPosition>) -> Self {
357 assert!(value.start <= value.end);
358 Self {
359 start: value.start,
360 end: value.end,
361 }
362 }
363}
364
365impl From<Range<(upos_type, upos_type)>> for TextRange {
366 fn from(value: Range<(upos_type, upos_type)>) -> Self {
367 Self {
368 start: TextPosition::from(value.start),
369 end: TextPosition::from(value.end),
370 }
371 }
372}
373
374impl From<TextRange> for Range<TextPosition> {
375 fn from(value: TextRange) -> Self {
376 value.start..value.end
377 }
378}
379
380impl TextRange {
381 pub const MAX: TextRange = TextRange {
383 start: TextPosition {
384 y: upos_type::MAX,
385 x: upos_type::MAX,
386 },
387 end: TextPosition {
388 y: upos_type::MAX,
389 x: upos_type::MAX,
390 },
391 };
392
393 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
398 let start = start.into();
399 let end = end.into();
400
401 assert!(start <= end);
402
403 TextRange { start, end }
404 }
405
406 #[inline]
408 pub fn is_empty(&self) -> bool {
409 self.start == self.end
410 }
411
412 #[inline]
414 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
415 let pos = pos.into();
416 pos >= self.start && pos < self.end
417 }
418
419 #[inline]
421 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
422 let pos = pos.into();
423 pos >= self.end
424 }
425
426 #[inline]
428 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
429 let pos = pos.into();
430 pos < self.start
431 }
432
433 #[inline(always)]
435 pub fn contains(&self, other: TextRange) -> bool {
436 other.start >= self.start && other.end <= self.end
437 }
438
439 #[inline(always)]
441 pub fn before(&self, other: TextRange) -> bool {
442 other.start > self.end
443 }
444
445 #[inline(always)]
447 pub fn after(&self, other: TextRange) -> bool {
448 other.end < self.start
449 }
450
451 #[inline(always)]
453 pub fn intersects(&self, other: TextRange) -> bool {
454 other.start <= self.end && other.end >= self.start
455 }
456
457 #[inline]
460 pub fn expand(&self, range: TextRange) -> TextRange {
461 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
462 }
463
464 #[inline]
467 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
468 let delta_lines = self.end.y - self.start.y;
469
470 if pos < self.start {
472 pos
473 } else if pos == self.start {
474 self.end
475 } else {
476 if pos.y > self.start.y {
477 TextPosition::new(pos.x, pos.y + delta_lines)
478 } else if pos.y == self.start.y {
479 if pos.x >= self.start.x {
480 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
481 } else {
482 pos
483 }
484 } else {
485 pos
486 }
487 }
488 }
489
490 #[inline]
493 pub fn shrink(&self, range: TextRange) -> TextRange {
494 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
495 }
496
497 #[inline]
500 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
501 let delta_lines = self.end.y - self.start.y;
502
503 if pos < self.start {
505 pos
506 } else if pos >= self.start && pos <= self.end {
507 self.start
508 } else {
509 if pos.y > self.end.y {
511 TextPosition::new(pos.x, pos.y - delta_lines)
512 } else if pos.y == self.end.y {
513 if pos.x >= self.end.x {
514 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
515 } else {
516 pos
517 }
518 } else {
519 pos
520 }
521 }
522 }
523}
524
525pub trait Cursor: Iterator {
530 fn prev(&mut self) -> Option<Self::Item>;
532
533 fn peek_next(&mut self) -> Option<Self::Item> {
535 let v = self.next();
536 self.prev();
537 v
538 }
539
540 fn peek_prev(&mut self) -> Option<Self::Item> {
542 let v = self.prev();
543 self.next();
544 v
545 }
546
547 fn text_offset(&self) -> usize;
549}
550
551mod _private {
552 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
553 pub struct NonExhaustive;
554}