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)]
105pub enum TextFocusGained {
106 #[default]
108 None,
109 Overwrite,
111 SelectAll,
113}
114
115#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
117pub enum TextFocusLost {
118 #[default]
120 None,
121 Position0,
123}
124
125#[derive(Debug, Clone)]
127pub struct TextStyle {
128 pub style: Style,
129 pub focus: Option<Style>,
130 pub select: Option<Style>,
131 pub invalid: Option<Style>,
132
133 pub on_focus_gained: Option<TextFocusGained>,
135 pub on_focus_lost: Option<TextFocusLost>,
137
138 pub scroll: Option<ScrollStyle>,
139 pub block: Option<Block<'static>>,
140 pub border_style: Option<Style>,
141
142 pub non_exhaustive: NonExhaustive,
143}
144
145impl Default for TextStyle {
146 fn default() -> Self {
147 Self {
148 style: Default::default(),
149 focus: None,
150 select: None,
151 invalid: None,
152 on_focus_gained: None,
153 on_focus_lost: None,
154 scroll: None,
155 block: None,
156 border_style: None,
157 non_exhaustive: NonExhaustive,
158 }
159 }
160}
161
162pub mod core {
163 pub use crate::text_core::TextCore;
169 pub use crate::text_mask_core::MaskedCore;
170 pub use crate::text_store::SkipLine;
171 pub use crate::text_store::TextStore;
172 pub use crate::text_store::text_rope::TextRope;
173 pub use crate::text_store::text_string::TextString;
174}
175
176#[derive(Debug, PartialEq)]
177pub enum TextError {
178 InvalidText(String),
180 Clipboard,
182 TextRangeOutOfBounds(TextRange),
184 TextPositionOutOfBounds(TextPosition),
186 LineIndexOutOfBounds(upos_type, upos_type),
191 ColumnIndexOutOfBounds(upos_type, upos_type),
193 ByteIndexOutOfBounds(usize, usize),
198 CharIndexOutOfBounds(usize, usize),
203 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
209 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
216 ByteIndexNotCharBoundary(usize),
220 ByteRangeNotCharBoundary(
227 Option<usize>, Option<usize>, ),
230 ByteRangeInvalid(
235 usize, usize, ),
238 CharRangeInvalid(
243 usize, usize, ),
246}
247
248impl Display for TextError {
249 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
250 write!(f, "{:?}", self)
251 }
252}
253
254impl Error for TextError {}
255
256#[allow(non_camel_case_types)]
258pub type upos_type = u32;
259#[allow(non_camel_case_types)]
261pub type ipos_type = i32;
262
263#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
265pub struct TextPosition {
266 pub y: upos_type,
267 pub x: upos_type,
268}
269
270impl TextPosition {
271 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
273 Self { y, x }
274 }
275}
276
277impl Debug for TextPosition {
278 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
279 write!(f, "{}|{}", self.x, self.y)
280 }
281}
282
283impl From<(upos_type, upos_type)> for TextPosition {
284 fn from(value: (upos_type, upos_type)) -> Self {
285 Self {
286 y: value.1,
287 x: value.0,
288 }
289 }
290}
291
292impl From<TextPosition> for (upos_type, upos_type) {
293 fn from(value: TextPosition) -> Self {
294 (value.x, value.y)
295 }
296}
297
298#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
300pub struct TextRange {
301 pub start: TextPosition,
303 pub end: TextPosition,
305}
306
307impl Debug for TextRange {
308 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
309 write!(
310 f,
311 "{}|{}-{}|{}",
312 self.start.x, self.start.y, self.end.x, self.end.y
313 )
314 }
315}
316
317impl From<Range<TextPosition>> for TextRange {
318 fn from(value: Range<TextPosition>) -> Self {
319 assert!(value.start <= value.end);
320 Self {
321 start: value.start,
322 end: value.end,
323 }
324 }
325}
326
327impl From<Range<(upos_type, upos_type)>> for TextRange {
328 fn from(value: Range<(upos_type, upos_type)>) -> Self {
329 Self {
330 start: TextPosition::from(value.start),
331 end: TextPosition::from(value.end),
332 }
333 }
334}
335
336impl From<TextRange> for Range<TextPosition> {
337 fn from(value: TextRange) -> Self {
338 value.start..value.end
339 }
340}
341
342impl TextRange {
343 pub const MAX: TextRange = TextRange {
345 start: TextPosition {
346 y: upos_type::MAX,
347 x: upos_type::MAX,
348 },
349 end: TextPosition {
350 y: upos_type::MAX,
351 x: upos_type::MAX,
352 },
353 };
354
355 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
360 let start = start.into();
361 let end = end.into();
362
363 assert!(start <= end);
364
365 TextRange { start, end }
366 }
367
368 #[inline]
370 pub fn is_empty(&self) -> bool {
371 self.start == self.end
372 }
373
374 #[inline]
376 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
377 let pos = pos.into();
378 pos >= self.start && pos < self.end
379 }
380
381 #[inline]
383 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
384 let pos = pos.into();
385 pos >= self.end
386 }
387
388 #[inline]
390 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
391 let pos = pos.into();
392 pos < self.start
393 }
394
395 #[inline(always)]
397 pub fn contains(&self, other: TextRange) -> bool {
398 other.start >= self.start && other.end <= self.end
399 }
400
401 #[inline(always)]
403 pub fn before(&self, other: TextRange) -> bool {
404 other.start > self.end
405 }
406
407 #[inline(always)]
409 pub fn after(&self, other: TextRange) -> bool {
410 other.end < self.start
411 }
412
413 #[inline(always)]
415 pub fn intersects(&self, other: TextRange) -> bool {
416 other.start <= self.end && other.end >= self.start
417 }
418
419 #[inline]
422 pub fn expand(&self, range: TextRange) -> TextRange {
423 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
424 }
425
426 #[inline]
429 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
430 let delta_lines = self.end.y - self.start.y;
431
432 if pos < self.start {
434 pos
435 } else if pos == self.start {
436 self.end
437 } else {
438 if pos.y > self.start.y {
439 TextPosition::new(pos.x, pos.y + delta_lines)
440 } else if pos.y == self.start.y {
441 if pos.x >= self.start.x {
442 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
443 } else {
444 pos
445 }
446 } else {
447 pos
448 }
449 }
450 }
451
452 #[inline]
455 pub fn shrink(&self, range: TextRange) -> TextRange {
456 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
457 }
458
459 #[inline]
462 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
463 let delta_lines = self.end.y - self.start.y;
464
465 if pos < self.start {
467 pos
468 } else if pos >= self.start && pos <= self.end {
469 self.start
470 } else {
471 if pos.y > self.end.y {
473 TextPosition::new(pos.x, pos.y - delta_lines)
474 } else if pos.y == self.end.y {
475 if pos.x >= self.end.x {
476 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
477 } else {
478 pos
479 }
480 } else {
481 pos
482 }
483 }
484 }
485}
486
487pub trait Cursor: Iterator {
492 fn prev(&mut self) -> Option<Self::Item>;
494
495 fn rev_cursor(self) -> impl Cursor<Item = Self::Item>
498 where
499 Self: Sized;
500
501 fn text_offset(&self) -> usize;
503}
504
505mod _private {
506 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
507 pub struct NonExhaustive;
508}