郑州做网站优化公,购物网站排名第一的有哪些,爱站seo,笔记本电脑可以做网站服务器这是一个使用Rust实现的轻量化文本编辑器。学过Rust的都知道#xff0c;Rust 从入门到实践中间还隔着好几个Go语言的难度#xff0c;因此#xff0c;如果你也正在学习Rust,那么恭喜你#xff0c;这个项目被你捡到了。本项目内容较多#xff0c;大概会分三期左右陆续发布Rust 从入门到实践中间还隔着好几个Go语言的难度因此如果你也正在学习Rust,那么恭喜你这个项目被你捡到了。本项目内容较多大概会分三期左右陆续发布欢迎关注 1. 第一篇 本系列教程默认你已经配置了Rust开发环境并具有一定的rust基础。所以直接从项目创建开始讲解 使用下面的命令创建项目
项目创建
cargo new xed运行程序
cargo run如果成功输出Hello World表示项目基本功能正常本章节完 2. 第二篇
2.1 读取用户输入
现在修改main.rs尝试读取用户的输入你可以随时按下Ctrl c终止程序
use std::io;
use std::io::Read;
fn main() {let mut buf [0; 1];while io::stdin().read(mut buf).expect(Failed to read line) 1 {}
}这里的内容不多主要涉及到io的基本操作所以导包是必要的第4行创建了一个可变的buf数组长度为1初始值为0;io::stdin().read(mut buf) 尝试从标准输入流中读取数据并将其存储在 buf 中。read 方法返回一个 Result 类型其中包含读取的字节数或一个错误。所以expect(Failed to read line) 用于处理可能出现的错误情况。如果读取失败程序将打印出 “Failed to read line” 作为错误信息并终止程序。最后的1检查读取的字节数是否为1否则结束循环 2.2 实现q命令
本小节实现基本功能:用户输入q按下回车执行退出程序的操作。
use std::io;
use std::io::Read;
fn main() {let mut buf [0; 1];while io::stdin().read(mut buf).expect(Failed to read line) 1 buf ![bq] {}
}程序会检查buf中输入的每一个字符如果与q相同就会结束程序 在 Rust 中[bq] 是一个字节字符串字面量表示一个包含单个字节 q 的字节数组。 [bq] bq 是 Rust 中的字节字面量表示一个字节即 ASCII 字符 q 对应的字节值。在 Rust 中使用 b 前缀可以将字符转换为对应的字节值。这种表示方式常用于处理字节数据。 字节值和字符映射 在 ASCII 编码中每个字符都有一个对应的字节值。在 ASCII 编码中字符 q 对应的字节值是 113。使用 bq 可以直接表示这个字节值而 [bq] 则将这个字节值包装在一个长度为 1 的字节数组中。 因此[bq] 表示一个包含单个字节值为 113即 ASCII 字符 q 对应的字节值的字节数组。在上下文中buf ! [bq] 的条件判断将检查 buf 中存储的字节是否不等于 q 对应的字节值即检查输入的数据是否不是 q。 等价写法:buf[0] ! bq 2.3 常规模式与原始模式
上面的情况就是常规模式也就是程序启动后终端可以正常监听并回显你输入的内容
而这里说的原始模式的作用和常规模式相反我们这里可以直接使用crossterm库来实现添加依赖
cargo add crosstermuse std::io;
use std::io::Read;
use crossterm::terminal; // 添加依赖
fn main() {terminal::enable_raw_mode().expect(Could not run on Raw mode); // 开启原始模式let mut buf [0; 1];while io::stdin().read(mut buf).expect(Failed to read line) 1 buf ! [bq] {}
}现在如果你运行程序你的输入在终端并没有任何回显并且当你输入q的时候也是直接无提示的退出程序这就是crossterm帮我们实现的原始模式的基本功能
如果要禁用原始模式考虑下面的代码最后一行就是禁用这个模式的逻辑
use crossterm::terminal; /* add this line */
use std::io;
use std::io::Read;
fn main() {terminal::enable_raw_mode().expect(Could not turn on Raw mode);let mut buf [0; 1];while io::stdin().read(mut buf).expect(Failed to read line) 1 buf ! [bq] {}terminal::disable_raw_mode().expect(Could not turn off raw mode); /* add this line */
}但是这样运行后会出现一个错误 当在 terminal::enable_raw_mode() 之后的函数中发生错误并导致 panic 时disable_raw_mode() 将不会被调用导致终端保持在原始模式。这种情况可能会导致程序结束时终端状态不正确用户体验受到影响。 所以为了解决这个问题让我们创建 一个 名为 CleanUp的struct;
struct CleanUp;impl Drop for CleanUp {fn drop(mut self) {terminal::disable_raw_mode().expect(Could not disable raw mode);}
}然后修改原来的代码
use crossterm::terminal; // 添加依赖
use std::io;
use std::io::Read;
struct CleanUp;impl Drop for CleanUp {fn drop(mut self) {terminal::disable_raw_mode().expect(Could not disable raw mode);}
}fn main() {let _clean_up CleanUp; // 看这里terminal::enable_raw_modde().expect(Could not run on Raw mode); // 开启原始模式let mut buf [0; 1];while io::stdin().read(mut buf).expect(Failed to read line) 1 buf ! [bq] {}// terminal::disable_raw_mode().expect(Could not turn off raw mode); /* add this line */panic!(); // 看这里
}现在我们新增了一个struct并实现了Drop这个trait;此时drop()函数会在我们的struct实例也就是_clean_up超出作用域或者该实例出现panic时候执行 一旦上面的情况发生drop()被执行那么将成功禁用原始模式 但是现在还有问题此时使用Ctrl c 无法退出程序不妨看看当我们按下这些按键的时候输出了什么东西
fn main() {let _clean_up CleanUp;terminal::enable_raw_mode().expect(Could not run on Raw mode); // 开启原始模式let mut buf [0; 1];while io::stdin().read(mut buf).expect(Failed to read line) 1 buf ! [bq] {let character buf[0] as char;if character.is_control() {println!({}\r, character as u8)} else {println!({}\r, character)}}
}is_control()判断按下的是否为控制键位在正常情况下控制键位输入的字符我们并不需要ASCII的0-31都是控制字符127也是所以32-126就是可打印的字符也是我们在编辑文本时需要进行输入回显的另外请注意我们在打印信息的时候使用的是\r而不是\n;此时我们在终端输入数据之后光标会自动调整到屏幕的左侧。 现在请运行程序并尝试按下控制键位例如方向键、 或 Escape 、 或 Page Up Page Down 、 Home End Backspace Delete 或 Enter 或 。尝试使用 Ctrl 组合键如 Ctrl-A、Ctrl-B 等。你会发现 方向键Page Up、Page Down、Home 和 End 都向终端输入 3 或 4 个字节 27 、、 [ 然后是一两个其他字符。这称为转义序列。所有转义序列都以 27 字节开头。按 Escape 键发送单个 27 字节作为输入。 Backspace 是字节 127 。Delete 是一个 4 字节的转义序列。 Enter 是 byte 10 这是一个换行符也称为 \n 或 byte 13 这是回车符也称为 \r 。 另外Ctrl-A 是 1 Ctrl-B 是 2 Ctrl-C 是3…这确实有效的 将Ctrl 组合键将字母 A-Z 映射到代码 1-26 通过上面的步骤我们基本了解了按键是如何转为字节的。 2.4 crossterm提供的事件抽象
crossterm 还提供了对各种关键事件的抽象因此我们不必记住上面那一堆映射关系而是使用这个crate带来的实现方法
下面是使用这些抽象重构之火的main.rs:
use crossterm::event::{Event, KeyCode, KeyEvent};
use crossterm::{event, terminal}; // 添加依赖
use std::io;
use std::io::Read;
struct CleanUp;impl Drop for CleanUp {fn drop(mut self) {terminal::disable_raw_mode().expect(Could not disable raw mode);}
}fn main() {let _clean_up CleanUp;terminal::enable_raw_mode().expect(Could not run on Raw mode); // 开启原始模式let mut buf [0; 1];// 从这里开始重构loop {if let Event::Key(event) event::read().expect(Failed to read line) {match event {KeyEvent {code: KeyCode::Char(q),modifiers: event::KeyModifiers::NONE,kind: event::KeyEventKind::Press,state: event::KeyEventState::NONE,} break,_ {// todo}}println!({:?}\r, event);};}
}Event 是一个 enum 。由于我们目前只对按键感兴趣因此我们检查返回的 Event 键是否为 Key .然后我们检查按下的键是否为 q 。如果用户按下 q 我们就会中断 loop 程序将终止。当然枚举中其他几个字段也是必须的参考下文档中枚举的定义如下: pub struct KeyEvent {pub code: KeyCode,pub modifiers: KeyModifiers,pub kind: KeyEventKind,pub state: KeyEventState,
}其中的kind也是枚举 pub enum KeyEventKind {Press,Repeat,Release,
}sate的定义 pub struct KeyEventState: u8 {/// The key event origins from the keypad.const KEYPAD 0b0000_0001;/// Caps Lock was enabled for this key event.////// **Note:** this is set for the initial press of Caps Lock itself.const CAPS_LOCK 0b0000_1000;/// Num Lock was enabled for this key event.////// **Note:** this is set for the initial press of Num Lock itself.const NUM_LOCK 0b0000_1000;const NONE 0b0000_0000;}看着有点怕但是不要怕当下只需要理解代码中按下q执行程序退出的逻辑就可以。 下面是一个示例输出它会在你按下按键的时候记录并打印相关的事件信息。你可以测试一下按下q是否正常退出程序。 2.4 超时处理
现在的情况是read()会无限期的在等待我们的键盘输入后返回。如果我们一直没有输入那它就已知等待这是个问题。因此我们需要有一个超时处理的逻辑比如超过一定时间没用户没有任何操作就执行超时对应的处理逻辑。
use crossterm::event::{Event, KeyCode, KeyEvent};
use crossterm::{event, terminal}; // 添加依赖
use std::io;
use std::io::Read;
use std::time::Duration; // 新增依赖
struct CleanUp;impl Drop for CleanUp {fn drop(mut self) {terminal::disable_raw_mode().expect(Could not disable raw mode);}
}fn main() {let _clean_up CleanUp;terminal::enable_raw_mode().expect(Could not run on Raw mode); // 开启原始模式let mut buf [0; 1];// 从这里开始重构loop {if event::poll(Duration::from_millis(500)).expect(Program timed out) { // 超时处理if let Event::Key(event) event::read().expect(Failed to read line) {match event {KeyEvent {code: KeyCode::Char(q),modifiers: event::KeyModifiers::NONE,kind: event::KeyEventKind::Press,state: event::KeyEventState::NONE,} break,_ {// todo}}println!({:?}\r, event);};}}
}上面的代码中新增的超时处理中用到了crossterm::event::poll这个方法如果在给定时间内没有 Event 可用 poll 则返回 false ,具体的函数定义信息如下 2.5 错误处理
一路走来我们对程序的错误处理都是使用expect()进行简单的捕获这显然并不是一个很好的选择和习惯下面通过使用Result来对错误进行进一步的处理修改main.rs:
use crossterm::event::{Event, KeyCode, KeyEvent};
use crossterm::{event, terminal};
use std::time::Duration; /* add this line */struct CleanUp;impl Drop for CleanUp {fn drop(mut self) {terminal::disable_raw_mode().expect(Unable to disable raw mode)}
}fn main() - std::result::Result(), std::io::Error {let _clean_up CleanUp;terminal::enable_raw_mode()?;loop {if event::poll(Duration::from_millis(500))? {if let Event::Key(event) event::read()? {match event {KeyEvent {code: KeyCode::Char(q),modifiers: event::KeyModifiers::NONE,kind: _,state: _,} break,_ {//todo}}println!({:?}\r, event);};} else {println!(No input yet\r);}}Ok(())
}修改部分如下,注意对于main方法本身也是指定了返回值类型这在下面的贴图中没有展现。 ? 算符只能用于返回 Result 的方法中因此 Option 我们必须修改 our main() 以返回 Result .可以 crossterm::ResultT 扩展为 std::result::ResultT, std::io::Error 因此对于我们的 main() 函数返回类型可以转换为 std::result::Result(), std::io::Error 。 本期完,下期内容抢先知:
CtrlQ退出键盘输入重构屏幕清理光标定位退出清屏波浪号占位符(类似于vim)追加缓冲区 写在最后 如果这篇内容跟下来你还是觉得比较难那么我推荐你暂时放一下这里推荐一个我之前写的开源项目untools,这也是一个使用Rust编写的工具库可以拿来练手顺手点个star的同时也欢迎有想法有能力的同学PR;