Rust学习-制作一个文本编辑器: Reading User Input
本文最后更新于:2024年1月10日 下午
读取用户的输入
现在来尝试读取用户的输入操作,main.rs
如下:
1 |
|
其中use
类似于import
或include
,用来引入我们要使用的库。在这里我们使用io
用来进行输入输出操作use std::io::{self, Read};
,这一步相当于:
1 |
|
main
函数中主要包含一个循环,其中io::stdin().bytes()
将返回一个迭代器,在每一轮循环前赋值给b
后进入循环,直到没有可以读的内容了。我们可以通过Ctrl+D
来发出eof
,或者直接Ctrl+C
来终止程序。
在实际运行的时候,这个程序会将用户输入的字符串打印出来。然而,在默认情况下,仅当用户按下Enter
时输入才会发送给程序,这也允许用户通过·来编辑修复内容(canonical mode / cooked mode
),但是对于一个文本编辑器来说,我们希望在按下键盘时程序能立刻响应(raw mode
)。
输入Q
来退出
当程序收到用户按下Q
时,退出程序:
1 |
|
需要注意的是,在Rust
中,字符需要用单引号''
包裹,字符串是双引号""
,这和C++
是类似的。
现在当我们在程序中输入q
,循环将停止,程序就会退出了,而在q
后面的字符不会被打印,并在程序退出时一起被销毁。
Raw Mode
为了切换到Raw Mode
,我们需要使用termion
,可以在Cargo.toml
中引入:
1 |
|
我们再次运行cargo build
或run
,就会下载并编译termion
:
1 |
|
现在让我们把main.rs
中的代码改写成支持raw mode
的:
1 |
|
我们使用termion
提供stdout()
并调用into_raw_mode()
函数。为什么要在stdout
而不是stdin
呢?因为终端的状态是由写入者控制而不是读取者。writer
用于在屏幕上绘图或移动光标,同时也用于修改模式。
除此之外,我们还新建了_stdout
变量,用于储存into_raw_mode()
的返回,但是从未在之后的程序中使用过它。这涉及到Rust中所有权的概念,简单来说,终端可以拥有一些属性,不属于自己的东西将会被移除,而into_raw_mode()
修改了终端并返回了一个值,我们需要将它储存起来,通过绑定到_stdout
变量,一旦删除_stdout
,终端就又会变为canonical mode
。_stdout
的变量名中添加了前缀_
,这会告诉编译器我们希望保留这个变量即使没有被使用过,否则在编译时回返回一个warning
。
运行一下:
1 |
|
监视按键
为了更好的探索Raw Mode
的工作,我们让程序输出读到的所有byte
:
1 |
|
这段代码中对变量b
进行了二次声明,这是叫做变量遮蔽的特性:在新变量的作用域中,原先的变量会被屏蔽,这样就不必费心思想新的变量名了。函数is_control()
可以用来判断是否是控制字符。控制字符是不可被打印出来的,我们可以在ASCII
表中找到他们。
Non-printing ASCII control codes.
通过测试我们可以发现,Ctrl+A...Z
对应的就是ASCII
表中1-26的控制符。
按Ctrl+Q
退出
现在我们可以将Ctrl+Q
映射到退出操作:
1 |
|
函数to_ctrl_byte()
将返回字符和00011111
相与的二进制结果。
异常处理
现在是时候该旅一下如何处理错误了。我们给程序增加一个die()
函数打印错误并退出:
1 |
|
panic!
会导致程序关闭并输出错误信息。和其他语言不一样,Rust
没有类似于try...catch
的结构来捕获可能发生的错误,取而代之的是,Rust
采用一种将错误作为函数返回值的方式进行传递,这样我们就可以在程序的顶层统一处理这些错误。
在Rust
中,当函数可能发生错误时,它会返回一个名为Result
的类型。Result
是一个包装器,内含期望的结果或一个错误。在这种情况下,每一个值b
本质上都是一个Result
,它要么包含一个表示已读取字节的Ok
,要么包含一个Err
,后者包装了一个错误对象,表明在读取字节时出现了问题。为了得到我们需要的值,我们可以调用unwrap
方法:如果我们得到的是Ok
,就返回它包含的值;如果是Err
,则触发panic
。
我们希望在错误发生时自行控制程序崩溃,而不是让Rust直接触发panic
,因为后续我们想在程序崩溃前清除屏幕,避免给用户留下未完成绘制的输入信息。目前,我们只需检查错误然后调用die
函数,这个函数会替我们执行panic
。
我们通过枚举(match
)来实现这个流程,match
可以当成一个增强版的if-then-else
。由它处理变量b
,这个变量要么包含我们想要的值,这个值被Ok
包装,要么包含一个错误,这个错误被Err
包装:
1 |
|