pub struct Editor { /* private fields */ }Expand description
The editing state of one input line: its text, the cursor, a kill buffer, and a bounded history of submitted lines.
Editor is the “line editing” part of a REPL with the terminal taken out.
It holds no file descriptors and draws nothing. The host translates key
events into Edit commands, calls apply, and redraws
from line and column when apply
reports a change. On Enter it calls submit, which hands
back the finished line and records it in the history. That split makes the
editor identical on every platform and testable without a terminal.
Positions are byte offsets into line and always fall on a
char boundary, so slicing the line at cursor is
always valid. The cursor moves in display units (a visible character plus
any combining marks attached to it), so it never separates a letter from its
accent.
§Memory
The line, the kill buffer, and every history slot are reused rather than reallocated. Once the history has filled to capacity, submitting a line recycles the oldest slot’s buffer, so a long session settles into editing and submitting without touching the allocator except to grow a buffer past its previous high-water mark.
§Examples
use repl_lang::{Edit, Editor};
let mut editor = Editor::new();
editor.insert("let x = 1");
assert_eq!(editor.submit(), "let x = 1");
assert_eq!(editor.line(), "");
// The submitted line is one step back in the history.
assert!(editor.apply(Edit::HistoryPrev));
assert_eq!(editor.line(), "let x = 1");Implementations§
Source§impl Editor
impl Editor
Sourcepub const DEFAULT_HISTORY: usize = 1000
pub const DEFAULT_HISTORY: usize = 1000
The number of history entries Editor::new keeps.
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates an empty editor that keeps the last
DEFAULT_HISTORY submitted lines.
§Examples
use repl_lang::Editor;
let editor = Editor::new();
assert_eq!(editor.line(), "");
assert_eq!(editor.cursor(), 0);
assert_eq!(editor.history().len(), 0);Sourcepub fn with_history(capacity: usize) -> Self
pub fn with_history(capacity: usize) -> Self
Creates an empty editor that keeps at most capacity history entries.
When the history is full, submitting a line drops the oldest entry. A
capacity of 0 disables history: nothing is recorded and the history
commands do nothing. No history memory is reserved up front; slots are
allocated as lines are submitted, then recycled once the limit is
reached.
§Examples
use repl_lang::Editor;
let mut editor = Editor::with_history(2);
for line in ["a", "b", "c"] {
editor.insert(line);
editor.submit();
}
// Only the two most recent lines are kept, oldest first.
assert!(editor.history().eq(["b", "c"]));Sourcepub fn line(&self) -> &str
pub fn line(&self) -> &str
The text of the line being edited.
§Examples
use repl_lang::Editor;
let mut editor = Editor::new();
editor.insert("1 + 2");
assert_eq!(editor.line(), "1 + 2");Sourcepub fn cursor(&self) -> usize
pub fn cursor(&self) -> usize
The cursor position as a byte offset into line.
The offset is always on a char boundary, so &line[..cursor] and
&line[cursor..] are always valid. Use it to find the word under the
cursor for completion; use column to place the
terminal cursor.
§Examples
use repl_lang::{Edit, Editor};
let mut editor = Editor::new();
editor.insert("pri");
assert_eq!(editor.cursor(), 3);
// The text before the cursor is the prefix to complete.
let prefix = &editor.line()[..editor.cursor()];
assert_eq!(prefix, "pri");
assert!(editor.apply(Edit::Home));
assert_eq!(editor.cursor(), 0);Sourcepub fn column(&self) -> usize
pub fn column(&self) -> usize
The cursor’s position in terminal columns, counted from the start of the line.
This is the display width of the text before the cursor: wide characters
(CJK ideographs, most emoji) count two columns and combining marks count
none. After drawing the prompt and the line, a host moves the terminal
cursor to prompt_width + column().
The width is computed on each call, in time linear in the text before the cursor.
§Examples
use repl_lang::Editor;
let mut editor = Editor::new();
editor.insert("x = 世界");
assert_eq!(editor.cursor(), 10); // bytes: 4 ASCII + 2 × 3
assert_eq!(editor.column(), 8); // columns: 4 ASCII + 2 × 2Sourcepub fn apply(&mut self, edit: Edit) -> bool
pub fn apply(&mut self, edit: Edit) -> bool
Applies one editing command and reports whether the line or the cursor changed.
A command that cannot do anything — moving left at the start of the
line, deleting at the end, yanking an empty kill buffer, inserting a
control character — leaves the editor untouched and returns false, so
the host can skip a redraw or ring the bell.
The kill commands replace the kill buffer with the text they remove; a kill that removes nothing leaves the buffer as it was.
§Examples
use repl_lang::{Edit, Editor};
let mut editor = Editor::new();
editor.insert("hello world");
assert!(editor.apply(Edit::KillWordLeft));
assert_eq!(editor.line(), "hello ");
assert!(editor.apply(Edit::Home));
assert!(editor.apply(Edit::Yank));
assert_eq!(editor.line(), "worldhello ");
assert_eq!(editor.cursor(), 5); // just past the yanked text
// Commands with nothing to do report `false`.
assert!(editor.apply(Edit::Home));
assert!(!editor.apply(Edit::Left));
assert!(!editor.apply(Edit::Insert('\t'))); // control characters are refusedSourcepub fn insert(&mut self, text: &str) -> bool
pub fn insert(&mut self, text: &str) -> bool
Inserts text at the cursor, as a paste would, and reports whether
anything was inserted.
Control characters in text are dropped, exactly as
Edit::Insert refuses them one at a time; everything else is
inserted in a single pass. A host that receives a multi-line paste
should split it into lines and submit them one by one.
§Examples
use repl_lang::{Edit, Editor};
let mut editor = Editor::new();
editor.insert("fn main() {}");
assert!(editor.apply(Edit::Left));
editor.insert(" body ");
assert_eq!(editor.line(), "fn main() { body }");
// Control characters are dropped from pasted text.
let mut editor = Editor::new();
editor.insert("a\tb\u{1b}c");
assert_eq!(editor.line(), "abc");
assert!(!editor.insert("\n"));Sourcepub fn clear(&mut self) -> bool
pub fn clear(&mut self) -> bool
Empties the line and moves the cursor to the start, keeping the kill
buffer and the history. Returns true if the line was not already
empty.
This is the editor half of an interrupt (Ctrl-C): abandon what is
being typed and start over. If the history was being browsed, browsing
ends and the line parked when it began is dropped as well.
§Examples
use repl_lang::Editor;
let mut editor = Editor::new();
editor.insert("half-typed");
assert!(editor.clear());
assert_eq!(editor.line(), "");
assert!(!editor.clear());Sourcepub fn submit(&mut self) -> &str
pub fn submit(&mut self) -> &str
Finishes the line: returns its text, records it in the history, and leaves the editor empty for the next line.
A line is recorded unless it is blank (empty or only whitespace) or
identical to the most recent history entry, and only if the history
capacity is not 0.
Submitting while browsing the history submits the recalled line as
edited; the line parked when browsing began is dropped. Either way the
next HistoryPrev starts from the newest entry.
The returned text stays borrowed from the editor, which keeps the buffer for reuse: the next submit recycles it as the following line.
§Examples
use repl_lang::Editor;
let mut editor = Editor::new();
editor.insert("1 + 1");
assert_eq!(editor.submit(), "1 + 1");
// Blank lines and immediate repeats are not recorded.
editor.insert(" ");
assert_eq!(editor.submit(), " ");
editor.insert("1 + 1");
editor.submit();
assert_eq!(editor.history().len(), 1);Sourcepub fn add_history(&mut self, line: &str)
pub fn add_history(&mut self, line: &str)
Records line in the history as if it had been submitted, without
touching the line being edited.
Use it to restore a history saved from an earlier session. The same
rules as submit apply: blank lines and repeats of
the newest entry are skipped, and the oldest entry is dropped once the
history is full.
Control characters are removed first, except the two a multi-line entry
is made of: newlines and tabs are kept, so an entry such as
"let x =\n\t42" comes back as it was entered instead of with its lines
run together. A "\r\n" or a lone '\r' is kept as one '\n'.
Everything else — escape (ESC), the other C0 and C1 controls, and
DEL — is removed, so a recalled entry can never put a terminal
control sequence into the line.
§Examples
use repl_lang::{Edit, Editor};
let saved = "let x = 1\nx * 2\n";
let mut editor = Editor::new();
for line in saved.lines() {
editor.add_history(line);
}
assert!(editor.apply(Edit::HistoryPrev));
assert_eq!(editor.line(), "x * 2");
// A multi-line entry keeps its lines; escape sequences lose their `ESC`.
editor.add_history("fn f() {\r\n\treturn 1\r\n}\u{1b}[0m");
assert_eq!(editor.history().next_back(), Some("fn f() {\n\treturn 1\n}[0m"));Sourcepub fn history(
&self,
) -> impl DoubleEndedIterator<Item = &str> + ExactSizeIterator + '_
pub fn history( &self, ) -> impl DoubleEndedIterator<Item = &str> + ExactSizeIterator + '_
The history, oldest entry first.
Iterate it in reverse for newest-first, or write it out line by line to persist the history between sessions.
§Examples
use repl_lang::Editor;
let mut editor = Editor::new();
for line in ["first", "second"] {
editor.insert(line);
editor.submit();
}
assert_eq!(editor.history().next_back(), Some("second"));
let saved: Vec<&str> = editor.history().collect();
assert_eq!(saved, ["first", "second"]);