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 cache;
17mod glyph;
18mod glyph2;
19mod grapheme;
20mod range_map;
21mod text_core;
22mod text_mask_core;
23mod text_store;
24
25#[allow(deprecated)]
26pub use glyph::Glyph;
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 #[default]
111 None,
112 Overwrite,
115 SelectAll,
117}
118
119#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
124pub enum TextFocusLost {
125 #[default]
127 None,
128 Position0,
131}
132
133#[derive(Debug, Clone)]
135pub struct TextStyle {
136 pub style: Style,
137 pub focus: Option<Style>,
138 pub select: Option<Style>,
139 pub invalid: Option<Style>,
140
141 pub on_focus_gained: Option<TextFocusGained>,
143 pub on_focus_lost: Option<TextFocusLost>,
145
146 pub scroll: Option<ScrollStyle>,
147 pub block: Option<Block<'static>>,
148 pub border_style: Option<Style>,
149
150 pub non_exhaustive: NonExhaustive,
151}
152
153impl Default for TextStyle {
154 fn default() -> Self {
155 Self {
156 style: Default::default(),
157 focus: None,
158 select: None,
159 invalid: None,
160 on_focus_gained: None,
161 on_focus_lost: None,
162 scroll: None,
163 block: None,
164 border_style: None,
165 non_exhaustive: NonExhaustive,
166 }
167 }
168}
169
170pub mod core {
171 pub use crate::text_core::TextCore;
177 pub use crate::text_mask_core::MaskedCore;
178 pub use crate::text_store::SkipLine;
179 pub use crate::text_store::TextStore;
180 pub use crate::text_store::text_rope::TextRope;
181 pub use crate::text_store::text_string::TextString;
182}
183
184#[derive(Debug, PartialEq)]
185pub enum TextError {
186 InvalidText(String),
188 Clipboard,
190 TextRangeOutOfBounds(TextRange),
192 TextPositionOutOfBounds(TextPosition),
194 LineIndexOutOfBounds(upos_type, upos_type),
199 ColumnIndexOutOfBounds(upos_type, upos_type),
201 ByteIndexOutOfBounds(usize, usize),
206 CharIndexOutOfBounds(usize, usize),
211 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
217 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
224 ByteIndexNotCharBoundary(usize),
228 ByteRangeNotCharBoundary(
235 Option<usize>, Option<usize>, ),
238 ByteRangeInvalid(
243 usize, usize, ),
246 CharRangeInvalid(
251 usize, usize, ),
254}
255
256impl Display for TextError {
257 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
258 write!(f, "{:?}", self)
259 }
260}
261
262impl Error for TextError {}
263
264#[allow(non_camel_case_types)]
266pub type upos_type = u32;
267#[allow(non_camel_case_types)]
269pub type ipos_type = i32;
270
271#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
273pub struct TextPosition {
274 pub y: upos_type,
275 pub x: upos_type,
276}
277
278impl TextPosition {
279 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
281 Self { y, x }
282 }
283}
284
285impl Debug for TextPosition {
286 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287 write!(f, "{}|{}", self.x, self.y)
288 }
289}
290
291impl From<(upos_type, upos_type)> for TextPosition {
292 fn from(value: (upos_type, upos_type)) -> Self {
293 Self {
294 y: value.1,
295 x: value.0,
296 }
297 }
298}
299
300impl From<TextPosition> for (upos_type, upos_type) {
301 fn from(value: TextPosition) -> Self {
302 (value.x, value.y)
303 }
304}
305
306#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
308pub struct TextRange {
309 pub start: TextPosition,
311 pub end: TextPosition,
313}
314
315impl Debug for TextRange {
316 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
317 write!(
318 f,
319 "{}|{}-{}|{}",
320 self.start.x, self.start.y, self.end.x, self.end.y
321 )
322 }
323}
324
325impl From<Range<TextPosition>> for TextRange {
326 fn from(value: Range<TextPosition>) -> Self {
327 assert!(value.start <= value.end);
328 Self {
329 start: value.start,
330 end: value.end,
331 }
332 }
333}
334
335impl From<Range<(upos_type, upos_type)>> for TextRange {
336 fn from(value: Range<(upos_type, upos_type)>) -> Self {
337 Self {
338 start: TextPosition::from(value.start),
339 end: TextPosition::from(value.end),
340 }
341 }
342}
343
344impl From<TextRange> for Range<TextPosition> {
345 fn from(value: TextRange) -> Self {
346 value.start..value.end
347 }
348}
349
350impl TextRange {
351 pub const MAX: TextRange = TextRange {
353 start: TextPosition {
354 y: upos_type::MAX,
355 x: upos_type::MAX,
356 },
357 end: TextPosition {
358 y: upos_type::MAX,
359 x: upos_type::MAX,
360 },
361 };
362
363 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
368 let start = start.into();
369 let end = end.into();
370
371 assert!(start <= end);
372
373 TextRange { start, end }
374 }
375
376 #[inline]
378 pub fn is_empty(&self) -> bool {
379 self.start == self.end
380 }
381
382 #[inline]
384 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
385 let pos = pos.into();
386 pos >= self.start && pos < self.end
387 }
388
389 #[inline]
391 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
392 let pos = pos.into();
393 pos >= self.end
394 }
395
396 #[inline]
398 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
399 let pos = pos.into();
400 pos < self.start
401 }
402
403 #[inline(always)]
405 pub fn contains(&self, other: TextRange) -> bool {
406 other.start >= self.start && other.end <= self.end
407 }
408
409 #[inline(always)]
411 pub fn before(&self, other: TextRange) -> bool {
412 other.start > self.end
413 }
414
415 #[inline(always)]
417 pub fn after(&self, other: TextRange) -> bool {
418 other.end < self.start
419 }
420
421 #[inline(always)]
423 pub fn intersects(&self, other: TextRange) -> bool {
424 other.start <= self.end && other.end >= self.start
425 }
426
427 #[inline]
430 pub fn expand(&self, range: TextRange) -> TextRange {
431 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
432 }
433
434 #[inline]
437 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
438 let delta_lines = self.end.y - self.start.y;
439
440 if pos < self.start {
442 pos
443 } else if pos == self.start {
444 self.end
445 } else {
446 if pos.y > self.start.y {
447 TextPosition::new(pos.x, pos.y + delta_lines)
448 } else if pos.y == self.start.y {
449 if pos.x >= self.start.x {
450 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
451 } else {
452 pos
453 }
454 } else {
455 pos
456 }
457 }
458 }
459
460 #[inline]
463 pub fn shrink(&self, range: TextRange) -> TextRange {
464 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
465 }
466
467 #[inline]
470 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
471 let delta_lines = self.end.y - self.start.y;
472
473 if pos < self.start {
475 pos
476 } else if pos >= self.start && pos <= self.end {
477 self.start
478 } else {
479 if pos.y > self.end.y {
481 TextPosition::new(pos.x, pos.y - delta_lines)
482 } else if pos.y == self.end.y {
483 if pos.x >= self.end.x {
484 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
485 } else {
486 pos
487 }
488 } else {
489 pos
490 }
491 }
492 }
493}
494
495pub trait Cursor: Iterator {
500 fn prev(&mut self) -> Option<Self::Item>;
502
503 fn rev_cursor(self) -> impl Cursor<Item = Self::Item>
506 where
507 Self: Sized;
508
509 fn text_offset(&self) -> usize;
511}
512
513mod _private {
514 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
515 pub struct NonExhaustive;
516}