5 个版本
0.4.1 | 2024 年 1 月 22 日 |
---|---|
0.4.0 | 2023 年 10 月 27 日 |
0.3.2 | 2023 年 9 月 17 日 |
0.3.1 | 2023 年 9 月 15 日 |
0.3.0 | 2023 年 9 月 15 日 |
4 在 #textarea 中
每月 36 次下载
在 2 crates 中使用
135KB
2K SLoC
ratatui-textarea
[!warning] 此项目不再维护,tui-textarea 现在支持最新的
ratatui
版本
ratatui-textarea 是一个简单而强大的文本编辑小部件,类似于 HTML 中的 <textarea>
,用于 ratatui(ratatui 是从 tui-textarea 分支出来的)。多行文本编辑器可以轻松地集成到您的 ratatui 应用程序中。
功能
- 带基本操作(插入/删除字符、自动滚动等)的多行文本编辑器小部件
- 类似 Emacs 的快捷键(
C-n
/C-p
/C-f
/C-b
,M-f
/M-b
,C-a
/C-e
,C-h
/C-d
,C-k
,M<
/M->
,...) - 撤销/重做
- 行号
- 光标行高亮显示
- 使用正则表达式搜索
- 鼠标滚动
- 支持剪切。使用
C-k
,C-j
等删除的文本可以粘贴 - 后端无关。支持 crossterm、termion 以及您自己的后端
- 同一屏幕上的多个 textarea 小部件
示例
在当前仓库中运行 cargo run --example
可以展示 ratatui-textarea 的用法。
最小化
cargo run --example minimal
带有 crossterm 支持的最小化用法。
编辑器
cargo run --example editor --features search file.txt
简单的文本编辑器,用于编辑多个文件。
单行
cargo run --example single_line
具有浮点数验证的单行输入表单。
分割
cargo run --example split
屏幕上两个分割的文本区域,并在它们之间切换。这是多个文本区域实例的示例。
termion
cargo run --example termion --features=termion
带有 termion 支持的最小化用法。
变量
cargo run --example variable
具有根据行数变化的简单文本区域高度。
模式
cargo run --example modal
类似于 vi
的简单模式文本编辑器。
安装
将 ratatui-textarea
crate 添加到 Cargo.toml
中的依赖项。这默认启用 crossterm 后端支持。
[dependencies]
ratatui = "*"
ratatui-textarea = "*"
如果您需要使用正则表达式进行文本搜索,请启用 search
功能。它将 regex crate crate 作为依赖项添加。
[dependencies]
ratatui = "*"
ratatui-textarea = { version = "*", features = ["search"] }
如果您正在使用 ratatui 与 termion 一起使用,请启用 termion
功能而不是 crossterm
功能。
[dependencies]
ratatui = { version = "*", default-features = false, features = ["termion"] }
ratatui-textarea = { version = "*", default-features = false, features = ["termion"] }
除了上述依赖项之外,您还需要安装 crossterm 或 termion 来初始化您的应用程序并接收按键输入。有关更多详细信息,请参阅 crossterm 或 termion 文档。
最小化用法
use ratatui_textarea::TextArea;
use crossterm::event::{Event, read};
let mut term = ratatui::Terminal::new(...);
// Create an empty `TextArea` instance which manages the editor state
let mut textarea = TextArea::default();
// Event loop
loop {
term.draw(|f| {
// Get `ratatui::layout::Rect` where the editor should be rendered
let rect = ...;
// `TextArea::widget` builds a widget to render the editor with ratatui
let widget = textarea.widget();
// Render the widget in terminal screen
f.render_widget(widget, rect);
})?;
if let Event::Key(key) = read()? {
// Your own key mapping to break the event loop
if key.code == KeyCode::Esc {
break;
}
// `TextArea::input` can directly handle key events from backends and update the editor state
textarea.input(key);
}
}
// Get text lines as `&[String]`
println!("Lines: {:?}", textarea.lines());
TextArea
是一个管理编辑器状态的实例。默认情况下,它禁用行号并以下划线突出显示光标行。
TextArea::widget()
构建一个小部件以渲染编辑器的当前状态。在每个事件循环的tick上创建小部件并渲染它。
TextArea::input()
接收来自 ratatui 后端的输入。如果启用了功能,此方法可以直接从后端(如 crossterm::event::KeyEvent
或 termion::event::Key
)获取按键事件。该方法还处理默认按键映射。
默认按键映射如下
映射 | 描述 |
---|---|
Ctrl+H ,Backspace |
删除光标前的一个字符 |
Ctrl+D ,Delete |
删除光标旁边的一个字符 |
Ctrl+M ,Enter |
插入换行符 |
Ctrl+K |
从光标处删除到行尾 |
Ctrl+J |
从光标处删除到行首 |
Ctrl+W ,Alt+H ,Alt+Backspace |
删除光标前的一个单词 |
Alt+D ,Alt+Delete |
删除光标旁边的一个单词 |
Ctrl+U |
撤销 |
Ctrl+R |
重做 |
Ctrl+Y |
粘贴已复制的文本 |
Ctrl+F ,→ |
将光标向前移动一个字符 |
Ctrl+B ,← |
将光标向后移动一个字符 |
Ctrl+P ,↑ |
将光标向上移动一行 |
Ctrl+N ,↓ |
将光标向下移动一行 |
Alt+F ,Ctrl+→ |
将光标向前移动一个单词 |
Alt+B ,Ctrl+← |
将光标向后移动一个单词 |
Alt+] ,Alt+P ,Ctrl+↑ |
将光标向上移动一个段落 |
Alt+[ ,Alt+N ,Ctrl+↓ |
将光标向下移动一个段落 |
Ctrl+E ,End ,Ctrl+Alt+F ,Ctrl+Alt+→ |
将光标移动到行尾 |
Ctrl+A ,Home ,Ctrl+Alt+B ,Ctrl+Alt+← |
将光标移动到行首 |
Alt+< ,Ctrl+Alt+P ,Ctrl+Alt+↑ |
将光标移动到文本开头 |
Alt+> ,Ctrl+Alt+N ,Ctrl+Alt+↓ |
将光标移动到文本结尾 |
Ctrl+V ,PageDown |
向下滚动一页 |
Alt+V ,PageUp |
向上滚动一页 |
一次删除多个字符时,会将删除的文本保存到剪切板。之后可以用 Ctrl+Y
粘贴。
如果您不想使用默认的键映射,请参阅“高级用法”部分。
基本用法
使用文本创建 TextArea
实例
TextArea
实现了 Default
特性,以创建一个包含空文本的编辑器实例。
let mut textarea = TextArea::default();
TextArea::new()
创建一个包含通过 Vec<String>
传递的文本行作为文本的编辑器实例。
let mut lines: Vec<String> = ...;
let mut textarea = TextArea::new(lines);
TextArea
实现了From<实现Iterator<Item=实现Into<String>>>
。 TextArea::from()
可以从任何可以转换为String
的迭代器中创建编辑器实例。
// Create `TextArea` from from `[&str]`
let mut textarea = TextArea::from([
"this is first line",
"this is second line",
"this is third line",
]);
// Create `TextArea` from `String`
let mut text: String = ...;
let mut textarea = TextARea::from(text.lines());
TextArea
还实现了FromIterator<实现Into<String>>
。 Iterator::collect()
可以将字符串收集为编辑器实例。这允许使用io::BufReader
高效地从文件中读取行来创建TextArea
。
let file = fs::File::open(path)?;
let mut textarea: TextArea = io::BufReader::new(file).lines().collect::<io::Result<_>>()?;
从TextArea
获取文本内容
TextArea::lines()
返回文本行作为&[String]
。它临时借用文本内容。
let text: String = textarea.lines().join("\n");
TextArea::into_lines()
将TextArea
实例移动到文本行作为Vec<String>
。这样可以检索文本内容而不进行任何复制。
let lines: Vec<String> = textarea.into_lines();
请注意,TextArea
始终包含至少一行。例如,空文本表示一行空行。这是因为任何文本文件都必须以换行符结束。
let textarea = TextArea::default();
assert_eq!(textarea.into_lines(), [""]);
显示行号
默认情况下,TextArea
不显示行号。要启用,请通过TextArea::set_line_number_style()
设置渲染行号的样式。例如,以下在深灰色背景色中渲染行号。
use ratatui::style::{Style, Color};
let style = Style::default().bg(Color::DarkGray);
textarea.set_line_number_style(style);
配置光标行样式
默认情况下,TextArea
使用下划线渲染光标所在的行,以便用户可以轻松地注意到当前行。要更改光标行的样式,请使用TextArea::set_cursor_line_style()
。例如,以下使用加粗文本样式化光标行。
use ratatui::style::{Style, Modifier};
let style = Style::default().add_modifier(Modifier::BOLD);
textarea.set_line_number_style(style);
要禁用光标行样式,请按照以下方式设置默认样式
use ratatui::style::{Style, Modifier};
textarea.set_line_number_style(Style::default());
配置制表符宽度
默认制表符宽度为4。要更改它,请使用TextArea::set_tab_length()
方法。以下设置制表符宽度为2。按制表符键插入2个空格。
textarea.set_tab_length(2);
配置最大历史记录大小
默认情况下,过去50次修改被存储为编辑历史记录。历史记录用于撤销/重做。要更改记住多少过去编辑,请使用TextArea::set_max_histories()
方法。以下记住过去1000次更改。
textarea.set_max_histories(1000);
设置0将禁用撤销/重做。
textarea.set_max_histories(0);
使用正则表达式进行文本搜索
在文本区域中搜索文本,使用TextArea::set_search_pattern()
设置正则表达式模式,并通过TextArea::search_forward()
进行正向搜索或使用TextArea::search_back()
进行反向搜索。正则表达式由regex
crate处理。
文本搜索会环绕文本区域。在正向搜索中,如果没有找到匹配项直到文本区域末尾,它将从文件开头搜索该模式。
匹配项在文本区域中被突出显示。可以使用TextArea::set_search_style()
更改用于突出显示匹配项的文本样式。将空字符串设置到TextArea::set_search_pattern()
将停止文本搜索。
// Start text search matching to "hello" or "hi". This highlights matches in textarea but does not move cursor.
// `regex::Error` is returned on invalid pattern.
textarea.set_search_pattern("(hello|hi)").unwrap();
textarea.search_forward(false); // Move cursor to the next match
textarea.search_back(false); // Move cursor to the previous match
// Setting empty string stops the search
textarea.set_search_pattern("").unwrap();
没有提供文本搜索的UI。您需要提供自己的UI来输入搜索查询。建议使用另一个TextArea
作为搜索表单。要构建单行输入表单,请参阅下文“高级用法”部分的“HTML中的单行输入如<input>
”。
editor
示例实现了基于TextArea
构建的搜索表单的文本搜索。请参阅实现以获取工作示例。
要使用文本搜索,需要在您的Cargo.toml
中启用search
功能。默认情况下它是禁用的,以避免在不需要时依赖regex
crate。
ratatui-textarea = { version = "*", features = ["search"] }
高级用法
HTML中的单行输入如<input>
要在HTML中使用TextArea
作为单行输入小部件(如<input>
),请忽略所有插入换行符的关键映射。
use crossterm::event::{Event, read};
use ratatui_textarea::{Input, Key};
let default_text: &str = ...;
let default_text = default_text.replace(&['\n', '\r'], " "); // Ensure no new line is contained
let mut textarea = TextArea::new(vec![default_text]);
// Event loop
loop {
// ...
// Using `Input` is not mandatory, but it's useful for pattern match
// Ignore Ctrl+m and Enter. Otherwise handle keys as usual
match read()?.into() {
Input { key: Key::Char('m'), ctrl: true, alt: false }
| Input { key: Key::Enter, .. } => continue,
input => {
textarea.input(key);
}
}
}
let text = textarea.into_lines().remove(0); // Get input text
请参阅single_line
示例以获取工作示例。
定义您自己的键映射
所有编辑器操作都定义为TextArea
的公共方法。要移动光标,使用ratatui_textarea::CursorMove
来通知如何移动光标。
方法 | 操作 |
---|---|
textarea.delete_char() |
删除光标前的一个字符 |
textarea.delete_next_char() |
删除光标旁边的一个字符 |
textarea.insert_newline() |
插入换行符 |
textarea.delete_line_by_end() |
从光标处删除到行尾 |
textarea.delete_line_by_head() |
从光标处删除到行首 |
textarea.delete_word() |
删除光标前的一个单词 |
textarea.delete_next_word() |
删除光标旁边的一个单词 |
textarea.undo() |
撤销 |
textarea.redo() |
重做 |
textarea.paste() |
粘贴已复制的文本 |
textarea.move_cursor(CursorMove::Forward) |
将光标向前移动一个字符 |
textarea.move_cursor(CursorMove::Back) |
将光标向后移动一个字符 |
textarea.move_cursor(CursorMove::Up) |
将光标向上移动一行 |
textarea.move_cursor(CursorMove::Down) |
将光标向下移动一行 |
textarea.move_cursor(CursorMove::WordForward) |
将光标向前移动一个单词 |
textarea.move_cursor(CursorMove::WordBack) |
将光标向后移动一个单词 |
textarea.move_cursor(CursorMove::ParagraphForward) |
将光标向上移动一个段落 |
textarea.move_cursor(CursorMove::ParagraphBack) |
将光标向下移动一个段落 |
textarea.move_cursor(CursorMove::End) |
将光标移动到行尾 |
textarea.move_cursor(CursorMove::Head) |
将光标移动到行首 |
textarea.move_cursor(CursorMove::Top) |
将光标移动到文本开头 |
textarea.move_cursor(CursorMove::Bottom) |
将光标移动到文本结尾 |
textarea.move_cursor(CursorMove::Jump(row,col)) |
将光标移动到(行,列)位置 |
textarea.move_cursor(CursorMove::InViewport) |
将光标移动到视口中 |
textarea.set_search_pattern(pattern) |
设置文本搜索的模式 |
textarea.search_forward(match_cursor) |
将光标移动到文本搜索的下一个匹配项 |
textarea.search_back(match_cursor) |
将光标移动到文本搜索的上一匹配项 |
textarea.滚动(滚动中::PageDown) |
将视口向下滚动一页 |
textarea.滚动(滚动中::PageUp) |
将视口向上滚动一页 |
textarea.滚动(滚动中::HalfPageDown) |
将视口向下滚动半页 |
textarea.滚动(滚动中::HalfPageUp) |
将视口向上滚动半页 |
textarea.滚动((row,col)) |
将视口向下滚动到(行,列)位置 |
要定义自己的键映射,只需在代码中调用上述方法,而不是使用 TextArea::input()
方法。以下示例定义了类似于 Vim 的模式键映射。
use crossterm::event::{Event, read};
use ratatui_textarea::{Input, Key, CursorMove, Scrolling};
let mut textarea = ...;
enum Mode {
Normal,
Insert,
}
let mut mode = Mode::Normal;
// Event loop
loop {
// ...
match mode {
Mode::Normal => match read()?.into() {
Input { key: Key::Char('h'), .. } => textarea.move_cursor(CursorMove::Back),
Input { key: Key::Char('j'), .. } => textarea.move_cursor(CursorMove::Down),
Input { key: Key::Char('k'), .. } => textarea.move_cursor(CursorMove::Up),
Input { key: Key::Char('l'), .. } => textarea.move_cursor(CursorMove::Forward),
Input { key: Key::Char('i'), .. } => mode = Mode::Insert, // Enter insert mode
// ...Add more mappings
_ => {},
},
Mode::Insert => match read()?.into() {
Input { key: Key::Esc, .. } => {
mode = Mode::Normal; // Back to normal mode with Esc or Ctrl+C
}
input => {
textarea.input(input); // Use default key mappings in insert mode
}
},
}
}
参见 modal
示例 了解工作示例。它实现了更多类似 Vim 的键映射。
如果您不想使用默认的键映射,可以使用 TextArea::input_without_shortcuts()
方法代替 TextArea::input()
。该方法仅处理非常基本的操作,如插入/删除单个字符、制表符、换行符。
match read()?.into() {
// Handle your own key mappings here
// ...
input => textarea.input_without_shortcuts(input),
}
使用自己的后端
ratatui 允许您通过实现 ratatui::backend::Backend
trait 来创建自己的后端。ratatui-textarea 也支持它。在这种情况下,请为 ratatui 使用 your-backend
功能。它们避免添加后端 crate(crossterm 和 termion),因为您正在使用自己的后端。
[dependencies]
ratatui = { version = "*", default-features = false }
ratatui-textarea = { version = "*", default-features = false, features = ["your-backend"] }
ratatui_textarea::Input
是一个不依赖于后端的关键输入类型。您需要做的是将您自己的后端中的键事件转换为 ratatui_textarea::Input
实例。然后 TextArea::input()
方法可以像其他后端一样处理输入。
以下示例中,假设 your_backend::KeyDown
是您后端的一个键事件类型,而 your_backend::read_next_key()
返回下一个键事件。
// In your backend implementation
pub enum KeyDown {
Char(char),
BS,
Del,
Esc,
// ...
}
// Return tuple of (key, ctrlkey, altkey)
pub fn read_next_key() -> (KeyDown, bool, bool) {
// ...
}
然后您可以实现将 your_backend::KeyDown
值转换为 ratatui_textarea::Input
值的逻辑。
use ratatui_textarea::{Input, Key};
use your_backend::KeyDown;
fn keydown_to_input(key: KeyDown, ctrl: bool, alt: bool) -> Input {
match key {
KeyDown::Char(c) => Input { key: Key::Char(c), ctrl, alt },
KeyDown::BS => Input { key: Key::Backspace, ctrl, alt },
KeyDown::Del => Input { key: Key::Delete, ctrl, alt },
KeyDown::Esc => Input { key: Key::Esc, ctrl, alt },
// ...
_ => Input::default(),
}
}
对于 ratatui-textarea 无法处理的键,可以使用 ratatui_textarea::Input::default()
。它返回 'null' 键。编辑器将对此键不采取任何操作。
最后,将您自己的后端的键输入类型转换为 ratatui_textarea::Input
并将其传递给 TextArea::input()
。
let mut textarea = ...;
// Event loop
loop {
// ...
let (key, ctrl, alt) = your_backend::read_next_key();
if key == your_backend::KeyDown::Esc {
break; // For example, quit your app on pressing Esc
}
textarea.input(keydown_to_input(key, ctrl, alt));
}
在屏幕上放置多个 TextArea
实例
您不需要做任何特别的事情。创建多个 TextArea
实例,并渲染从每个实例构建的小部件。
以下是一个示例,将两个 textarea 小部件放入应用程序并管理焦点。
use ratatui_textarea::{TextArea, Input, Key};
use crossterm::event::{Event, read};
let editors = &mut [
TextArea::default(),
TextArea::default(),
];
let mut focused = 0;
loop {
term.draw(|f| {
let rects = ...;
for (editor, rect) in editors.iter_mut().zip(rects.into_iter()) {
let widget = editor.widget();
f.render_widget(widget, rect);
}
})?;
match read()?.into() {
// Switch focused textarea by Ctrl+S
Input { key: Key::Char('s'), ctrl: true, .. } => focused = (focused + 1) % 2;
// Handle input by the focused editor
input => editors[focused].input(input),
}
}
最低支持的 Rust 版本
本 crate 的最低支持 Rust 版本依赖于ratatui
crate。当前最低支持版本为 1.56.1。
版本控制
本 crate 尚未达到 v1.0.0。目前没有计划提升主版本。当前的版本控制策略如下
- 主版本:固定为 0
- 次版本:在发生重大更改时提升
- 补丁版本:在添加新功能或修复错误时提升
为 ratatui-textarea 贡献
此项目在GitHub上开发。
对于功能请求或错误报告,请创建一个问题。提交补丁时,请创建一个拉取请求。
在提交拉取请求之前,请参阅CONTRIBUTING.md。
许可证
ratatui-textarea 在MIT 许可证下分发。
依赖关系
~0.8–7.5MB
~45K SLoC