#![doc = include_str!("../readme.md")]
#![allow(clippy::uninlined_format_args)]
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::ops::Range;
pub mod clipboard;
pub mod cursor;
pub mod date_input;
pub mod line_number;
pub mod number_input;
pub mod text_area;
pub mod text_input;
pub mod text_input_mask;
pub mod undo_buffer;
mod derive;
mod cache;
mod glyph2;
mod grapheme;
mod range_map;
mod text_core;
mod text_store;
pub use grapheme::Grapheme;
use crate::_private::NonExhaustive;
pub use pure_rust_locales::Locale;
pub use rat_cursor::{HasScreenCursor, impl_screen_cursor, screen_cursor};
use rat_scrolled::ScrollStyle;
use ratatui_core::style::Style;
use ratatui_widgets::block::Block;
pub mod event {
pub use rat_event::*;
#[derive(Debug)]
pub struct ReadOnly;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TextOutcome {
Continue,
Unchanged,
Changed,
TextChanged,
}
impl ConsumedEvent for TextOutcome {
fn is_consumed(&self) -> bool {
*self != TextOutcome::Continue
}
}
impl From<bool> for TextOutcome {
fn from(value: bool) -> Self {
if value {
TextOutcome::Changed
} else {
TextOutcome::Unchanged
}
}
}
impl From<Outcome> for TextOutcome {
fn from(value: Outcome) -> Self {
match value {
Outcome::Continue => TextOutcome::Continue,
Outcome::Unchanged => TextOutcome::Unchanged,
Outcome::Changed => TextOutcome::Changed,
}
}
}
impl From<TextOutcome> for Outcome {
fn from(value: TextOutcome) -> Self {
match value {
TextOutcome::Continue => Outcome::Continue,
TextOutcome::Unchanged => Outcome::Unchanged,
TextOutcome::Changed => Outcome::Changed,
TextOutcome::TextChanged => Outcome::Changed,
}
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TextFocusGained {
None,
#[default]
Overwrite,
SelectAll,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TextFocusLost {
None,
#[default]
Position0,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TextTab {
#[default]
MoveToNextSection,
MoveToNextWidget,
}
#[derive(Debug, Clone)]
pub struct TextStyle {
pub style: Style,
pub scroll: Option<ScrollStyle>,
pub block: Option<Block<'static>>,
pub border_style: Option<Style>,
pub title_style: Option<Style>,
pub focus: Option<Style>,
pub select: Option<Style>,
pub invalid: Option<Style>,
pub cursor: Option<Style>,
pub on_focus_gained: Option<TextFocusGained>,
pub on_focus_lost: Option<TextFocusLost>,
pub on_tab: Option<TextTab>,
pub non_exhaustive: NonExhaustive,
}
impl Default for TextStyle {
fn default() -> Self {
Self {
style: Default::default(),
scroll: Default::default(),
block: Default::default(),
border_style: Default::default(),
title_style: Default::default(),
focus: Default::default(),
select: Default::default(),
invalid: Default::default(),
cursor: Default::default(),
on_focus_gained: Default::default(),
on_focus_lost: Default::default(),
on_tab: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
pub mod core {
pub use crate::text_core::TextCore;
pub use crate::text_core::core_op;
pub use crate::text_store::SkipLine;
pub use crate::text_store::TextStore;
pub use crate::text_store::text_rope::TextRope;
pub use crate::text_store::text_string::TextString;
}
#[derive(Debug, PartialEq)]
pub enum TextError {
InvalidText(String),
Clipboard,
TextRangeOutOfBounds(TextRange),
TextPositionOutOfBounds(TextPosition),
LineIndexOutOfBounds(upos_type, upos_type),
ColumnIndexOutOfBounds(upos_type, upos_type),
ByteIndexOutOfBounds(usize, usize),
CharIndexOutOfBounds(usize, usize),
ByteRangeOutOfBounds(Option<usize>, Option<usize>, usize),
CharRangeOutOfBounds(Option<usize>, Option<usize>, usize),
ByteIndexNotCharBoundary(usize),
ByteRangeNotCharBoundary(
Option<usize>, Option<usize>, ),
ByteRangeInvalid(
usize, usize, ),
CharRangeInvalid(
usize, usize, ),
InvalidSearch,
InvalidFmt,
InvalidValue,
}
impl Display for TextError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl From<std::fmt::Error> for TextError {
fn from(_: std::fmt::Error) -> Self {
TextError::InvalidFmt
}
}
impl Error for TextError {}
#[allow(non_camel_case_types)]
pub type upos_type = u32;
#[allow(non_camel_case_types)]
pub type ipos_type = i32;
#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub struct TextPosition {
pub y: upos_type,
pub x: upos_type,
}
impl TextPosition {
pub const fn new(x: upos_type, y: upos_type) -> TextPosition {
Self { y, x }
}
}
impl Debug for TextPosition {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}|{}", self.x, self.y)
}
}
impl From<(upos_type, upos_type)> for TextPosition {
fn from(value: (upos_type, upos_type)) -> Self {
Self {
y: value.1,
x: value.0,
}
}
}
impl From<TextPosition> for (upos_type, upos_type) {
fn from(value: TextPosition) -> Self {
(value.x, value.y)
}
}
#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub struct TextRange {
pub start: TextPosition,
pub end: TextPosition,
}
impl Debug for TextRange {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}|{}-{}|{}",
self.start.x, self.start.y, self.end.x, self.end.y
)
}
}
impl From<Range<TextPosition>> for TextRange {
fn from(value: Range<TextPosition>) -> Self {
assert!(value.start <= value.end);
Self {
start: value.start,
end: value.end,
}
}
}
impl From<Range<(upos_type, upos_type)>> for TextRange {
fn from(value: Range<(upos_type, upos_type)>) -> Self {
Self {
start: TextPosition::from(value.start),
end: TextPosition::from(value.end),
}
}
}
impl From<TextRange> for Range<TextPosition> {
fn from(value: TextRange) -> Self {
value.start..value.end
}
}
impl TextRange {
pub const MAX: TextRange = TextRange {
start: TextPosition {
y: upos_type::MAX,
x: upos_type::MAX,
},
end: TextPosition {
y: upos_type::MAX,
x: upos_type::MAX,
},
};
pub fn new(start: impl Into<TextPosition>, end: impl Into<TextPosition>) -> Self {
let start = start.into();
let end = end.into();
assert!(start <= end);
TextRange { start, end }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.start == self.end
}
#[inline]
pub fn contains_pos(&self, pos: impl Into<TextPosition>) -> bool {
let pos = pos.into();
pos >= self.start && pos < self.end
}
#[inline]
pub fn before_pos(&self, pos: impl Into<TextPosition>) -> bool {
let pos = pos.into();
pos >= self.end
}
#[inline]
pub fn after_pos(&self, pos: impl Into<TextPosition>) -> bool {
let pos = pos.into();
pos < self.start
}
#[inline(always)]
pub fn contains(&self, other: TextRange) -> bool {
other.start >= self.start && other.end <= self.end
}
#[inline(always)]
pub fn before(&self, other: TextRange) -> bool {
other.start > self.end
}
#[inline(always)]
pub fn after(&self, other: TextRange) -> bool {
other.end < self.start
}
#[inline(always)]
pub fn intersects(&self, other: TextRange) -> bool {
other.start <= self.end && other.end >= self.start
}
#[inline]
pub fn expand(&self, range: TextRange) -> TextRange {
TextRange::new(self.expand_pos(range.start), self.expand_pos(range.end))
}
#[inline]
pub fn expand_pos(&self, pos: TextPosition) -> TextPosition {
let delta_lines = self.end.y - self.start.y;
if pos < self.start {
pos
} else if pos == self.start {
self.end
} else {
if pos.y > self.start.y {
TextPosition::new(pos.x, pos.y + delta_lines)
} else if pos.y == self.start.y {
if pos.x >= self.start.x {
TextPosition::new(pos.x - self.start.x + self.end.x, pos.y + delta_lines)
} else {
pos
}
} else {
pos
}
}
}
#[inline]
pub fn shrink(&self, range: TextRange) -> TextRange {
TextRange::new(self.shrink_pos(range.start), self.shrink_pos(range.end))
}
#[inline]
pub fn shrink_pos(&self, pos: TextPosition) -> TextPosition {
let delta_lines = self.end.y - self.start.y;
if pos < self.start {
pos
} else if pos >= self.start && pos <= self.end {
self.start
} else {
if pos.y > self.end.y {
TextPosition::new(pos.x, pos.y - delta_lines)
} else if pos.y == self.end.y {
if pos.x >= self.end.x {
TextPosition::new(pos.x - self.end.x + self.start.x, pos.y - delta_lines)
} else {
pos
}
} else {
pos
}
}
}
}
pub trait Cursor: Iterator {
fn prev(&mut self) -> Option<Self::Item>;
fn peek_next(&mut self) -> Option<Self::Item> {
let v = self.next();
self.prev();
v
}
fn peek_prev(&mut self) -> Option<Self::Item> {
let v = self.prev();
self.next();
v
}
fn text_offset(&self) -> usize;
}
mod _private {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonExhaustive;
}