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 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 #[default]
109 None,
110 Overwrite,
113 SelectAll,
115}
116
117#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
122pub enum TextFocusLost {
123 #[default]
125 None,
126 Position0,
129}
130
131#[derive(Debug, Clone)]
133pub struct TextStyle {
134 pub style: Style,
135 pub focus: Option<Style>,
136 pub select: Option<Style>,
137 pub invalid: Option<Style>,
138
139 pub on_focus_gained: Option<TextFocusGained>,
141 pub on_focus_lost: Option<TextFocusLost>,
143
144 pub scroll: Option<ScrollStyle>,
145 pub block: Option<Block<'static>>,
146 pub border_style: Option<Style>,
147
148 pub non_exhaustive: NonExhaustive,
149}
150
151impl Default for TextStyle {
152 fn default() -> Self {
153 Self {
154 style: Default::default(),
155 focus: None,
156 select: None,
157 invalid: None,
158 on_focus_gained: None,
159 on_focus_lost: None,
160 scroll: None,
161 block: None,
162 border_style: None,
163 non_exhaustive: NonExhaustive,
164 }
165 }
166}
167
168pub mod core {
169 pub use crate::text_core::TextCore;
175 pub use crate::text_core::core_op;
176 pub use crate::text_store::SkipLine;
177 pub use crate::text_store::TextStore;
178 pub use crate::text_store::text_rope::TextRope;
179 pub use crate::text_store::text_string::TextString;
180}
181
182#[derive(Debug, PartialEq)]
183pub enum TextError {
184 InvalidText(String),
186 Clipboard,
188 TextRangeOutOfBounds(TextRange),
190 TextPositionOutOfBounds(TextPosition),
192 LineIndexOutOfBounds(upos_type, upos_type),
197 ColumnIndexOutOfBounds(upos_type, upos_type),
199 ByteIndexOutOfBounds(usize, usize),
204 CharIndexOutOfBounds(usize, usize),
209 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
215 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
222 ByteIndexNotCharBoundary(usize),
226 ByteRangeNotCharBoundary(
233 Option<usize>, Option<usize>, ),
236 ByteRangeInvalid(
241 usize, usize, ),
244 CharRangeInvalid(
249 usize, usize, ),
252 InvalidSearch,
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)]
309pub struct TextRange {
310 pub start: TextPosition,
312 pub end: TextPosition,
314}
315
316impl Debug for TextRange {
317 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
318 write!(
319 f,
320 "{}|{}-{}|{}",
321 self.start.x, self.start.y, self.end.x, self.end.y
322 )
323 }
324}
325
326impl From<Range<TextPosition>> for TextRange {
327 fn from(value: Range<TextPosition>) -> Self {
328 assert!(value.start <= value.end);
329 Self {
330 start: value.start,
331 end: value.end,
332 }
333 }
334}
335
336impl From<Range<(upos_type, upos_type)>> for TextRange {
337 fn from(value: Range<(upos_type, upos_type)>) -> Self {
338 Self {
339 start: TextPosition::from(value.start),
340 end: TextPosition::from(value.end),
341 }
342 }
343}
344
345impl From<TextRange> for Range<TextPosition> {
346 fn from(value: TextRange) -> Self {
347 value.start..value.end
348 }
349}
350
351impl TextRange {
352 pub const MAX: TextRange = TextRange {
354 start: TextPosition {
355 y: upos_type::MAX,
356 x: upos_type::MAX,
357 },
358 end: TextPosition {
359 y: upos_type::MAX,
360 x: upos_type::MAX,
361 },
362 };
363
364 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
369 let start = start.into();
370 let end = end.into();
371
372 assert!(start <= end);
373
374 TextRange { start, end }
375 }
376
377 #[inline]
379 pub fn is_empty(&self) -> bool {
380 self.start == self.end
381 }
382
383 #[inline]
385 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
386 let pos = pos.into();
387 pos >= self.start && pos < self.end
388 }
389
390 #[inline]
392 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
393 let pos = pos.into();
394 pos >= self.end
395 }
396
397 #[inline]
399 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
400 let pos = pos.into();
401 pos < self.start
402 }
403
404 #[inline(always)]
406 pub fn contains(&self, other: TextRange) -> bool {
407 other.start >= self.start && other.end <= self.end
408 }
409
410 #[inline(always)]
412 pub fn before(&self, other: TextRange) -> bool {
413 other.start > self.end
414 }
415
416 #[inline(always)]
418 pub fn after(&self, other: TextRange) -> bool {
419 other.end < self.start
420 }
421
422 #[inline(always)]
424 pub fn intersects(&self, other: TextRange) -> bool {
425 other.start <= self.end && other.end >= self.start
426 }
427
428 #[inline]
431 pub fn expand(&self, range: TextRange) -> TextRange {
432 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
433 }
434
435 #[inline]
438 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
439 let delta_lines = self.end.y - self.start.y;
440
441 if pos < self.start {
443 pos
444 } else if pos == self.start {
445 self.end
446 } else {
447 if pos.y > self.start.y {
448 TextPosition::new(pos.x, pos.y + delta_lines)
449 } else if pos.y == self.start.y {
450 if pos.x >= self.start.x {
451 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
452 } else {
453 pos
454 }
455 } else {
456 pos
457 }
458 }
459 }
460
461 #[inline]
464 pub fn shrink(&self, range: TextRange) -> TextRange {
465 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
466 }
467
468 #[inline]
471 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
472 let delta_lines = self.end.y - self.start.y;
473
474 if pos < self.start {
476 pos
477 } else if pos >= self.start && pos <= self.end {
478 self.start
479 } else {
480 if pos.y > self.end.y {
482 TextPosition::new(pos.x, pos.y - delta_lines)
483 } else if pos.y == self.end.y {
484 if pos.x >= self.end.x {
485 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
486 } else {
487 pos
488 }
489 } else {
490 pos
491 }
492 }
493 }
494}
495
496pub trait Cursor: Iterator {
501 fn prev(&mut self) -> Option<Self::Item>;
503
504 fn peek_next(&mut self) -> Option<Self::Item> {
506 let v = self.next();
507 self.prev();
508 v
509 }
510
511 fn peek_prev(&mut self) -> Option<Self::Item> {
513 let v = self.prev();
514 self.next();
515 v
516 }
517
518 fn text_offset(&self) -> usize;
520}
521
522mod _private {
523 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
524 pub struct NonExhaustive;
525}