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 glyph2;
18mod grapheme;
19mod range_map;
20mod text_core;
21mod text_store;
22
23pub use grapheme::Grapheme;
24
25use crate::_private::NonExhaustive;
26pub use pure_rust_locales::Locale;
27pub use rat_cursor::{HasScreenCursor, impl_screen_cursor, screen_cursor};
28use rat_scrolled::ScrollStyle;
29use ratatui::style::Style;
30use ratatui::widgets::Block;
31
32pub mod event {
33 pub use rat_event::*;
38
39 #[derive(Debug)]
41 pub struct ReadOnly;
42
43 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
45 pub enum TextOutcome {
46 Continue,
48 Unchanged,
51 Changed,
56 TextChanged,
58 }
59
60 impl ConsumedEvent for TextOutcome {
61 fn is_consumed(&self) -> bool {
62 *self != TextOutcome::Continue
63 }
64 }
65
66 impl From<bool> for TextOutcome {
68 fn from(value: bool) -> Self {
69 if value {
70 TextOutcome::Changed
71 } else {
72 TextOutcome::Unchanged
73 }
74 }
75 }
76
77 impl From<Outcome> for TextOutcome {
78 fn from(value: Outcome) -> Self {
79 match value {
80 Outcome::Continue => TextOutcome::Continue,
81 Outcome::Unchanged => TextOutcome::Unchanged,
82 Outcome::Changed => TextOutcome::Changed,
83 }
84 }
85 }
86
87 impl From<TextOutcome> for Outcome {
88 fn from(value: TextOutcome) -> Self {
89 match value {
90 TextOutcome::Continue => Outcome::Continue,
91 TextOutcome::Unchanged => Outcome::Unchanged,
92 TextOutcome::Changed => Outcome::Changed,
93 TextOutcome::TextChanged => Outcome::Changed,
94 }
95 }
96 }
97}
98
99#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
104pub enum TextFocusGained {
105 #[default]
107 None,
108 Overwrite,
111 SelectAll,
113}
114
115#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
120pub enum TextFocusLost {
121 #[default]
123 None,
124 Position0,
127}
128
129#[derive(Debug, Clone)]
131pub struct TextStyle {
132 pub style: Style,
133 pub focus: Option<Style>,
134 pub select: Option<Style>,
135 pub invalid: Option<Style>,
136
137 pub on_focus_gained: Option<TextFocusGained>,
139 pub on_focus_lost: Option<TextFocusLost>,
141
142 pub scroll: Option<ScrollStyle>,
143 pub block: Option<Block<'static>>,
144 pub border_style: Option<Style>,
145
146 pub non_exhaustive: NonExhaustive,
147}
148
149impl Default for TextStyle {
150 fn default() -> Self {
151 Self {
152 style: Default::default(),
153 focus: None,
154 select: None,
155 invalid: None,
156 on_focus_gained: None,
157 on_focus_lost: None,
158 scroll: None,
159 block: None,
160 border_style: None,
161 non_exhaustive: NonExhaustive,
162 }
163 }
164}
165
166pub mod core {
167 pub use crate::text_core::TextCore;
173 pub use crate::text_core::core_op;
174 pub use crate::text_store::SkipLine;
175 pub use crate::text_store::TextStore;
176 pub use crate::text_store::text_rope::TextRope;
177 pub use crate::text_store::text_string::TextString;
178}
179
180#[derive(Debug, PartialEq)]
181pub enum TextError {
182 InvalidText(String),
184 Clipboard,
186 TextRangeOutOfBounds(TextRange),
188 TextPositionOutOfBounds(TextPosition),
190 LineIndexOutOfBounds(upos_type, upos_type),
195 ColumnIndexOutOfBounds(upos_type, upos_type),
197 ByteIndexOutOfBounds(usize, usize),
202 CharIndexOutOfBounds(usize, usize),
207 ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
213 CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
220 ByteIndexNotCharBoundary(usize),
224 ByteRangeNotCharBoundary(
231 Option<usize>, Option<usize>, ),
234 ByteRangeInvalid(
239 usize, usize, ),
242 CharRangeInvalid(
247 usize, usize, ),
250 InvalidSearch,
252}
253
254impl Display for TextError {
255 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
256 write!(f, "{:?}", self)
257 }
258}
259
260impl Error for TextError {}
261
262#[allow(non_camel_case_types)]
264pub type upos_type = u32;
265#[allow(non_camel_case_types)]
267pub type ipos_type = i32;
268
269#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
271pub struct TextPosition {
272 pub y: upos_type,
273 pub x: upos_type,
274}
275
276impl TextPosition {
277 pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
279 Self { y, x }
280 }
281}
282
283impl Debug for TextPosition {
284 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
285 write!(f, "{}|{}", self.x, self.y)
286 }
287}
288
289impl From<(upos_type, upos_type)> for TextPosition {
290 fn from(value: (upos_type, upos_type)) -> Self {
291 Self {
292 y: value.1,
293 x: value.0,
294 }
295 }
296}
297
298impl From<TextPosition> for (upos_type, upos_type) {
299 fn from(value: TextPosition) -> Self {
300 (value.x, value.y)
301 }
302}
303
304#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
307pub struct TextRange {
308 pub start: TextPosition,
310 pub end: TextPosition,
312}
313
314impl Debug for TextRange {
315 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
316 write!(
317 f,
318 "{}|{}-{}|{}",
319 self.start.x, self.start.y, self.end.x, self.end.y
320 )
321 }
322}
323
324impl From<Range<TextPosition>> for TextRange {
325 fn from(value: Range<TextPosition>) -> Self {
326 assert!(value.start <= value.end);
327 Self {
328 start: value.start,
329 end: value.end,
330 }
331 }
332}
333
334impl From<Range<(upos_type, upos_type)>> for TextRange {
335 fn from(value: Range<(upos_type, upos_type)>) -> Self {
336 Self {
337 start: TextPosition::from(value.start),
338 end: TextPosition::from(value.end),
339 }
340 }
341}
342
343impl From<TextRange> for Range<TextPosition> {
344 fn from(value: TextRange) -> Self {
345 value.start..value.end
346 }
347}
348
349impl TextRange {
350 pub const MAX: TextRange = TextRange {
352 start: TextPosition {
353 y: upos_type::MAX,
354 x: upos_type::MAX,
355 },
356 end: TextPosition {
357 y: upos_type::MAX,
358 x: upos_type::MAX,
359 },
360 };
361
362 pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
367 let start = start.into();
368 let end = end.into();
369
370 assert!(start <= end);
371
372 TextRange { start, end }
373 }
374
375 #[inline]
377 pub fn is_empty(&self) -> bool {
378 self.start == self.end
379 }
380
381 #[inline]
383 pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
384 let pos = pos.into();
385 pos >= self.start && pos < self.end
386 }
387
388 #[inline]
390 pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
391 let pos = pos.into();
392 pos >= self.end
393 }
394
395 #[inline]
397 pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
398 let pos = pos.into();
399 pos < self.start
400 }
401
402 #[inline(always)]
404 pub fn contains(&self, other: TextRange) -> bool {
405 other.start >= self.start && other.end <= self.end
406 }
407
408 #[inline(always)]
410 pub fn before(&self, other: TextRange) -> bool {
411 other.start > self.end
412 }
413
414 #[inline(always)]
416 pub fn after(&self, other: TextRange) -> bool {
417 other.end < self.start
418 }
419
420 #[inline(always)]
422 pub fn intersects(&self, other: TextRange) -> bool {
423 other.start <= self.end && other.end >= self.start
424 }
425
426 #[inline]
429 pub fn expand(&self, range: TextRange) -> TextRange {
430 TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
431 }
432
433 #[inline]
436 pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
437 let delta_lines = self.end.y - self.start.y;
438
439 if pos < self.start {
441 pos
442 } else if pos == self.start {
443 self.end
444 } else {
445 if pos.y > self.start.y {
446 TextPosition::new(pos.x, pos.y + delta_lines)
447 } else if pos.y == self.start.y {
448 if pos.x >= self.start.x {
449 TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
450 } else {
451 pos
452 }
453 } else {
454 pos
455 }
456 }
457 }
458
459 #[inline]
462 pub fn shrink(&self, range: TextRange) -> TextRange {
463 TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
464 }
465
466 #[inline]
469 pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
470 let delta_lines = self.end.y - self.start.y;
471
472 if pos < self.start {
474 pos
475 } else if pos >= self.start && pos <= self.end {
476 self.start
477 } else {
478 if pos.y > self.end.y {
480 TextPosition::new(pos.x, pos.y - delta_lines)
481 } else if pos.y == self.end.y {
482 if pos.x >= self.end.x {
483 TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
484 } else {
485 pos
486 }
487 } else {
488 pos
489 }
490 }
491 }
492}
493
494pub trait Cursor: Iterator {
499 fn prev(&mut self) -> Option<Self::Item>;
501
502 fn peek_next(&mut self) -> Option<Self::Item> {
504 let v = self.next();
505 self.prev();
506 v
507 }
508
509 fn peek_prev(&mut self) -> Option<Self::Item> {
511 let v = self.prev();
512 self.next();
513 v
514 }
515
516 fn text_offset(&self) -> usize;
518}
519
520mod _private {
521 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
522 pub struct NonExhaustive;
523}