///|
fn Mask::render_input(self : Mask, text : String) -> Result[Render, InputError] {
match self.kind {
PatternKind(p) | DateKind(p) =>
match parse_pattern(p, text, 0) {
Ok(raw) => Ok(render_pattern(p, raw))
Err(e) => Err(e)
}
DecimalKind(precision, signed, grouped) =>
parse_decimal(text, precision, signed, grouped)
}
}
///|
/// Create a snapshot from raw or formatted text. Selection starts at the end.
pub fn Editor::new(
mask : Mask,
initial? : String = "",
) -> Result[Editor, InputError] {
match mask.render_input(initial) {
Err(e) => Err(e)
Ok(render) => {
let end = render.display.length()
Ok({ mask, render, selection: { start: end, end, }, })
}
}
}
///|
pub fn Editor::display(self : Editor) -> String {
self.render.display
}
///|
/// Unformatted editable content; numeric input is kept as an exact string.
pub fn Editor::raw(self : Editor) -> String {
String::from_array(self.render.raw)
}
///|
pub fn Editor::selection(self : Editor) -> Selection {
self.selection
}
///|
pub fn Editor::status(self : Editor) -> Status {
let n = self.render.raw.length()
if n == 0 {
return Empty
}
match self.mask.kind {
PatternKind(p) =>
if n == p.required || n == p.slots.length() {
Complete
} else {
Incomplete
}
DateKind(_) => date_status(self.render.raw)
DecimalKind(_, _, _) => decimal_status(self.render.raw)
}
}
///|
fn Render::snap(self : Render, offset : Int, upward : Bool) -> Int {
for i, start in self.starts {
let end = self.ends[i]
if offset > start && offset < end {
return if upward { end } else { start }
}
}
// Also snap inside a supplementary literal, not only inside editable content.
let mut previous = 0
for boundary, c in self.display.iter2() {
let next = boundary + c.utf16_len()
if offset > boundary && offset < next {
return if upward { next } else { boundary }
}
previous = next
}
offset.min(previous)
}
///|
/// Set a valid selection. A range expands to whole Unicode scalars;
/// a collapsed caret inside a surrogate pair snaps to its beginning.
pub fn Editor::select(
self : Editor,
start : Int,
end : Int,
) -> Result[Editor, InputError] {
if start < 0 || end < start || end > self.display().length() {
return Err(InvalidSelection)
}
let s = self.render.snap(start, false)
let e = self.render.snap(end, start != end)
Ok({ ..self, selection: { start: s, end: e, }, })
}
///|
/// Map a display UTF-16 offset to an editable scalar index.
pub fn Editor::raw_index(
self : Editor,
offset : Int,
) -> Result[Int, InputError] {
if offset < 0 || offset > self.display().length() {
return Err(InvalidSelection)
}
let mut result = 0
for end in self.render.ends {
if end <= offset {
result += 1
} else {
break
}
}
Ok(result)
}
///|
/// Map an editable scalar boundary to a display UTF-16 position.
pub fn Editor::display_offset(
self : Editor,
index : Int,
) -> Result[Int, InputError] {
if index < 0 || index > self.render.raw.length() {
return Err(InvalidSelection)
}
Ok(if index == 0 { 0 } else { self.render.ends[index - 1] })
}
///|
fn Editor::replace_raw(
self : Editor,
start : Int,
end : Int,
text : String,
) -> Result[Editor, InputError] {
let inserted = match self.mask.kind {
PatternKind(p) | DateKind(p) => parse_pattern(p, text, start)
DecimalKind(precision, signed, grouped) =>
match parse_decimal(text, precision, signed, grouped) {
Ok(render) => Ok(render.raw)
Err(e) => Err(e)
}
}
let inserted = match inserted {
Ok(chars) => chars
Err(e) => return Err(e)
}
let raw : Array[Char] = []
for i in 0..
match parse_raw(p, raw, 0) {
Ok(chars) => Ok(render_pattern(p, chars))
Err(e) => Err(e)
}
DecimalKind(precision, signed, grouped) =>
render_decimal(raw, precision, signed, grouped)
}
match rendered {
Err(e) => Err(e)
Ok(render) => {
let index = start + inserted.length()
let caret = if index == 0 { 0 } else { render.ends[index - 1] }
Ok({ mask: self.mask, render, selection: { start: caret, end: caret, }, })
}
}
}
///|
/// Replace the current selection or insert at the caret. Invalid edits are atomic.
pub fn Editor::insert(
self : Editor,
text : String,
) -> Result[Editor, InputError] {
let start = self.raw_index(self.selection.start).unwrap()
let end = self.raw_index(self.selection.end).unwrap()
self.replace_raw(start, end, text)
}
///|
/// Delete selected raw characters, or the editable scalar before the caret.
pub fn Editor::backspace(self : Editor) -> Result[Editor, InputError] {
let start = self.raw_index(self.selection.start).unwrap()
let end = self.raw_index(self.selection.end).unwrap()
if self.selection.start != self.selection.end {
return self.replace_raw(start, end, "")
}
if start == 0 {
return Ok(self)
}
self.replace_raw(start - 1, end, "")
}
///|
/// Delete selected raw characters, or the editable scalar after the caret.
pub fn Editor::delete_forward(self : Editor) -> Result[Editor, InputError] {
let start = self.raw_index(self.selection.start).unwrap()
let end = self.raw_index(self.selection.end).unwrap()
if self.selection.start != self.selection.end {
return self.replace_raw(start, end, "")
}
if end == self.render.raw.length() {
return Ok(self)
}
self.replace_raw(start, end + 1, "")
}
///|
/// Replace all content and position the caret at the end.
pub fn Editor::set_value(
self : Editor,
text : String,
) -> Result[Editor, InputError] {
Editor::new(self.mask, initial=text)
}
///|
/// Return a complete value, or a typed error. Empty is distinct from incomplete.
pub fn Editor::value(self : Editor) -> Result[String, InputError] {
match self.status() {
Empty => Err(InvalidValue("empty"))
Incomplete => Err(InvalidValue("incomplete"))
Invalid(reason) => Err(InvalidValue(reason))
Complete =>
match self.mask.kind {
DateKind(_) => Ok(self.display())
DecimalKind(_, _, _) => Ok(decimal_value(self.render.raw))
_ => Ok(self.raw())
}
}
}
///|
pub fn InputError::message(self : InputError) -> String {
match self {
InvalidPattern(reason) => "Invalid pattern: " + reason
InvalidCharacter(index, c) => "Invalid character at slot \{index}: " + c
TooLong(limit) => "Input exceeds limit \{limit}"
InvalidSelection => "Selection is outside the displayed value"
InvalidConfig(reason) => "Invalid configuration: " + reason
InvalidValue(reason) => "Value is " + reason
}
}