Xed編輯器開發第二期:使用Rust從0到1寫一個文本編輯器

第三篇

這部分接著處理用戶退出命令以及一些其他新功能;

3.1 使用Ctrl+Q退出

modifiers: event::KeyModifiers::CONTROL,

使用CONTROL替換之前的NONE值即可;


3.2 重構鍵盤輸入

讓我們重構我們的代碼,以便我們有一個用于低級按鍵讀取的函數,以及另一個用于將按鍵映射到編輯器操作的函數。

  • 首先,讓我們創建一個 struct 可以讀取各種按鍵的按鈕。我們將其命名為:Reader:
struct Reader;
  • 然后添加一個方法來讀取關鍵事件:
impl Reader {fn read_key(&self) -> crossterm::Result<KeyEvent> {loop {if event::poll(Duration::from_millis(500))? {if let Event::Key(event) = event::read()? {return Ok(event);}}}}
}
  • 現在讓我們創建一個新結構 Editor ,它將是我們項目的主要主腦。
struct Editor {reader: Reader,
}impl Editor {fn new() -> Self {Self { reader: Reader }}
}

我們還創建了一個 new 方法來創建 的新 Editor 實例。

  • 現在讓我們處理 返回 Reader 的事件并創建一個 run 函數:
struct Editor {reader: Reader,
}impl Editor {fn new() -> Self {Self { reader: Reader }}fn process_keypress(&self) -> crossterm::Result<bool> {match self.reader.read_key()? {KeyEvent {code: KeyCode::Char('q'),modifiers: event::KeyModifiers::CONTROL,} => return Ok(false),_ => {}}Ok(true)}fn run(&self) -> crossterm::Result<bool> {self.process_keypress()}
}

在函數process_keypress中 ,我們返回是否應該繼續讀取關鍵事件。如果返回 false,則表示程序應該終止,因為我們不想再次讀取關鍵事件。現在讓我們修改一下 main()方法來 改用 Editor.run()

fn main() -> crossterm::Result<()> {let _clean_up = CleanUp;terminal::enable_raw_mode()?;/* modify */let editor = Editor::new();while editor.run()? {}/* end */Ok(())
}

3.3 屏幕清理

在用戶輸入之前將屏幕清理干凈,這里使用一個Outputstruct來處理輸出相關的內容;

struct Output;impl Output {fn new() -> Self {Self}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))}fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()}
}

clear_screen 函數實際執行的操作是將轉義序列寫入終端。這些序列修改了終端的行為,并可用于執行其他操作,例如添加顏色等。

  • 修改調用關系:
use crossterm::event::{Event, KeyCode, KeyEvent};
use crossterm::{event, execute, terminal};
use std::io::stdout;
use std::time::Duration; /* add this line */struct CleanUp;
struct Reader;
struct Editor {reader: Reader,output:Output,
}
struct Output;impl Output {fn new() -> Self {Self}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(terminal::ClearType::All))}fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()}
}impl Editor {fn new() -> Self {Self {reader: Reader,output:Output::new(),    }}fn process_keypress(&self) -> crossterm::Result<bool> {match self.reader.read_key()? {KeyEvent {code: KeyCode::Char('q'),modifiers: event::KeyModifiers::CONTROL,kind: _,state: _,} => return Ok(false),_ => {}}Ok(true)}fn run(&self) -> crossterm::Result<bool> {self.output.refresh_screen()?;self.process_keypress()}
}impl Drop for CleanUp {fn drop(&mut self) {terminal::disable_raw_mode().expect("Unable to disable raw mode")}
}impl Reader {fn read_key(&self) -> crossterm::Result<KeyEvent> {loop {if event::poll(Duration::from_millis(500))? {if let Event::Key(event) = event::read()? {return Ok(event);}}}}
}/// main函數
fn main() -> std::result::Result<(), std::io::Error> {let _clean_up = CleanUp;terminal::enable_raw_mode()?;let editor = Editor::new();while editor.run()? {}Ok(())
}

image-20240515103320326


3.4 重新定位光標

你可能已經注意到光標未位于屏幕的左上角。這樣我們就可以從上到下繪制我們的編輯器。

use crossterm::event::*;
use crossterm::terminal::ClearType;
use crossterm::{cursor, event, execute, terminal}; /* add import*/
use std::io::stdout;
use std::time::Duration;struct CleanUp;impl Drop for CleanUp {fn drop(&mut self) {terminal::disable_raw_mode().expect("Unable to disable raw mode")}
}struct Output;impl Output {fn new() -> Self {Self}/* modify */fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}/* end */fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()}
}

3.5 退出時清屏

讓我們清除屏幕并在程序退出時重新定位光標。

如果在渲染屏幕的過程中發生錯誤,我們不希望程序的輸出留在屏幕上,也不希望將錯誤打印在光標恰好位于該點的任何位置。

所以當我們的程序成功或失敗退出時,我們會將 Cleanup 該函數用于清除屏幕:

Drop中新增: Output::clear_screen().expect("Error");

struct CleanUp;impl Drop for CleanUp {fn drop(&mut self) {terminal::disable_raw_mode().expect("Unable to disable raw mode");Output::clear_screen().expect("Error"); /* add this line*/}
}struct Output;impl Output {fn new() -> Self {Self}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()}
}

3.6 添加波浪號

讓我們在屏幕的左側畫一列波浪號 ( ~ ),就像 vim 一樣。在我們的文本編輯器中,我們將在正在編輯的文件末尾之后的任何行的開頭繪制一個波浪號。

struct Output;impl Output {fn new() -> Self {Self}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}/* add this function */fn draw_rows(&self) {for _ in 0..24 {println!("~\r");}}fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()?;/* add the following lines*/self.draw_rows();execute!(stdout(), cursor::MoveTo(0, 0))/* end */}
}

draw_rows() 將處理繪制正在編輯的文本緩沖區的每一行。現在,它在每行中繪制一個波浪號,這意味著該行不是文件的一部分,不能包含任何文本。繪制后,我們將光標發送回屏幕的左上角。

  • 現在讓我們修改代碼以繪制正確數量的波浪號:
/* modify */
struct Output {win_size: (usize, usize),
}impl Output {fn new() -> Self {/* add this variable */let win_size = terminal::size().map(|(x, y)| (x as usize, y as usize)).unwrap(); Self { win_size }}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}fn draw_rows(&self) {let screen_rows = self.win_size.1; /* add this line */for _ in 0..screen_rows { /* modify */println!("~\r");}}fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()?;self.draw_rows();execute!(stdout(), cursor::MoveTo(0, 0))}
}

首先,我們修改 Output 以保留窗口大小,因為我們將使用窗口的大小進行多次計算。然后設置創建輸出實例時的 win_size 值。 type 中的 win_size 整數是 usize but terminal::size() 返回一個類型 (u16,16) 為 的元組,因此我們必須轉換為 u16 usize

也許您注意到屏幕的最后一行似乎沒有波浪號。這是因為我們的代碼中有一個小錯誤。當我們打印最終的波浪號時,我們會像在任何其他行上一樣打印一個 "\r\n"println!() 添加一個新行),但這會導致終端滾動以便為新的空白行騰出空間。

impl Output {fn new() -> Self {let win_size = terminal::size().map(|(x, y)| (x as usize, y as usize)).unwrap();Self { win_size }}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}fn draw_rows(&self) {let screen_rows = self.win_size.1;/* modify */for i in 0..screen_rows {print!("~");if i < screen_rows - 1 {println!("\r")}stdout().flush();}/* end */}fn refresh_screen(&self) -> crossterm::Result<()> {Self::clear_screen()?;self.draw_rows();execute!(stdout(), cursor::MoveTo(0, 0))}
}

3.7 追加緩沖區

由于在屏幕每次刷新時都會進行繪制,導致有閃頻的問題。

struct EditorContents {content: String,
}impl EditorContents {fn new() -> Self {Self {content: String::new(),}}fn push(&mut self, ch: char) {self.content.push(ch)}fn push_str(&mut self, string: &str) {self.content.push_str(string)}
}
impl io::Write for EditorContents {fn write(&mut self, buf: &[u8]) -> io::Result<usize> {match std::str::from_utf8(buf) {Ok(s) => {self.content.push_str(s);Ok(s.len())}Err(_) => Err(io::ErrorKind::WriteZero.into()),}}fn flush(&mut self) -> io::Result<()> {let out = write!(stdout(), "{}", self.content);stdout().flush()?;self.content.clear();out}
}
  • 首先,我們將傳遞到 write 函數的字節轉換為 str,以便我們可以將其添加到 content
  • 如果字節可以轉換為字符串,則返回字符串的長度,否則返回錯誤。當我們在 EditorContents 上調用 flush() 時,我們希望它寫入 stdout,因此我們使用 write!() 宏,然后調用 stdout.flush()
  • 我們還必須清除 content,以便我們可以在下一次屏幕刷新時使用。
  • 使用EditorContents:
use crossterm::{cursor, event, execute, queue, terminal}; /* modify */struct Output {win_size: (usize, usize),editor_contents: EditorContents, /* add this line */
}impl Output {fn new() -> Self {let win_size = terminal::size().map(|(x, y)| (x as usize, y as usize)).unwrap();Self {win_size,editor_contents: EditorContents::new(),}}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}fn draw_rows(&mut self) { /* modify */let screen_rows = self.win_size.1;for i in 0..screen_rows {self.editor_contents.push('~'); /* modify */if i < screen_rows - 1 {self.editor_contents.push_str("\r\n"); /* modify */}}}fn refresh_screen(&mut self) -> crossterm::Result<()> { /* modify */queue!(self.editor_contents, terminal::Clear(ClearType::All), cursor::MoveTo(0, 0))?; /* add this line*/self.draw_rows();queue!(self.editor_contents, cursor::MoveTo(0, 0))?; /* modify */self.editor_contents.flush() /* add this line*/}
}

注意,我們已更改 draw_rows 為使用 &mut self ,因此我們需要對之前的部分代碼做一下調整:

fn run(&mut self) -> crossterm::Result<bool> { /* modify */self.output.refresh_screen()?;self.process_keypress()
}
fn main() -> crossterm::Result<()> {let _clean_up = CleanUp;terminal::enable_raw_mode()?;let mut editor = Editor::new(); /* modify */while editor.run()? {}Ok(())
}

煩人的閃爍效果還有另一個可能的來源。當終端繪制到屏幕時,光標可能會在屏幕中間的某個地方顯示一瞬間。

為確保不會發生這種情況,讓我們在刷新屏幕之前隱藏光標,并在刷新完成后立即再次顯示光標。

impl Output {fn new() -> Self {let win_size = terminal::size().map(|(x, y)| (x as usize, y as usize)).unwrap();Self {win_size,editor_contents: EditorContents::new(),}}fn clear_screen() -> crossterm::Result<()> {execute!(stdout(), terminal::Clear(ClearType::All))?;execute!(stdout(), cursor::MoveTo(0, 0))}fn draw_rows(&mut self) {let screen_rows = self.win_size.1;for i in 0..screen_rows {self.editor_contents.push('~');if i < screen_rows - 1 {self.editor_contents.push_str("\r\n");}}}fn refresh_screen(&mut self) -> crossterm::Result<()> {queue!(self.editor_contents,cursor::Hide, //add thisterminal::Clear(ClearType::All),cursor::MoveTo(0, 0))?;self.draw_rows();queue!(self.editor_contents,cursor::MoveTo(0, 0),/* add this */ cursor::Show)?;self.editor_contents.flush()}
}

本期完,下期內容搶先知:

  • 逐行清除
  • 添加歡迎和版本信息
  • 按鍵移動光標
  • 方向鍵移動光標
  • 光標移動溢出問題
  • 分頁和首尾頁

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/13548.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/13548.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/13548.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《Rust奇幻之旅:從Java和C++開啟》第1章Hello world 2/5

講動人的故事,寫懂人的代碼 很多程序員都在自學Rust。 ??但Rust的學習曲線是真的陡,讓人有點兒怵頭。 程序員工作壓力大,能用來自學新東西的時間簡直就是鳳毛麟角。 ??目前,在豆瓣上有7本Rust入門同類書。它們雖有高分評價,但仍存在不足。 首先,就是它們介紹的Rust新…

【前端面經】BFC

BFC BFC什么是 BFC&#xff1f;元素開啟 BDC 后的特殊布局效果元素開啟 BFC 的方式 BFC 什么是 BFC&#xff1f; 官方解釋&#xff1a;A block formatting context (BFC) is a part of a visual CSS rendering of a web page. It’s the region in which the layout of block…

什么是谷歌爬蟲?

其實就是谷歌用來瀏覽網絡信息的一個自動化程序&#xff0c;他們會在你的網站爬取&#xff0c;尋找和搜集信息&#xff0c;谷歌爬蟲可以說決定著一個網站在谷歌的生死 谷歌爬蟲的作用機制就在于發現新網站以及新網頁&#xff0c;然后他會把網頁的內容帶回去&#xff0c;更新到…

PikaUnsafe upfileupload

1.client check 客戶端檢測&#xff0c;前端js檢測&#xff0c;禁用js和修改后綴名即可。 php格式不能上傳&#xff0c;我們修改后綴上傳。 蟻劍成功連接。 2.MIME type 這個就是 content-type 規定上傳類型&#xff0c;上面的方法也能成功&#xff0c;也可以修改 conten-ty…

面試框架【面試準備】

前言 2023-9-12 12:12:04 2023-09-14 16:13:04 公開發布于 2024-5-22 00:16:21 以下內容源自《【面試準備】》 僅供學習交流使用 版權 禁止其他平臺發布時刪除以下此話 本文首次發布于CSDN平臺 作者是CSDN日星月云 博客主頁是https://blog.csdn.net/qq_51625007 禁止其他平…

奇偶數遞增遞減-第13屆藍橋杯選拔賽Python真題精選

[導讀]&#xff1a;超平老師的Scratch藍橋杯真題解讀系列在推出之后&#xff0c;受到了廣大老師和家長的好評&#xff0c;非常感謝各位的認可和厚愛。作為回饋&#xff0c;超平老師計劃推出《Python藍橋杯真題解析100講》&#xff0c;這是解讀系列的第70講。 奇偶數遞增遞減&a…

vite+ts+mock+vue-router+pinia實現vue的路由權限

0.權限管理 前端的權限管理主要分為如下&#xff1a; 接口權限路由權限菜單權限按鈕權限 權限是對特定資源的訪問許可&#xff0c;所謂權限控制&#xff0c;也就是確保用戶只能訪問到被分配的資源 1.項目搭建 創建vite項目 yarn create vite配置別名 npm install path -…

4. C++入門:內聯函數、auto關鍵字、范圍for及nullptr

內聯函數 概念 以inline修飾的函數叫做內聯函數&#xff0c;編譯時C編譯器會在調用內聯函數的地方展開&#xff0c;沒有函數調用建立棧幀的開銷&#xff0c;內聯函數提升程序運行的效率 對比C的宏 C語言不足&#xff1a;宏 #define ADD(x, y) ((x)(y))int main() {int ret…

python實現520表白圖案

今天是520哦&#xff0c;作為程序員有必要通過自己的專業知識來向你的愛人表達下你的愛意。那么python中怎么實現繪制520表白圖案呢&#xff1f;這里給出方法&#xff1a; 1、使用圖形庫&#xff08;如turtle&#xff09; 使用turtle模塊&#xff0c;你可以繪制各種形狀和圖案…

Docker 安裝kingbase V8r6

下載 官網下載&#xff0c;注意&#xff1a;這里下載 Docker 版本v8r6 安裝 # 導入鏡像 docker load -i kingbase.tar# 重命名 docker tag [image-name]:[tag] [new-image-name]:[new-tag]# 刪除 docker rmi [image-name]:[tag]# 創建容器 docker run -tid \ --privileged \…

python實現繪制煙花代碼

在Python中&#xff0c;我們可以使用多個庫來繪制煙花效果&#xff0c;例如turtle庫用于簡單的繪圖&#xff0c;或者更復雜的庫如pygame或matplotlib結合動畫。但是&#xff0c;由于turtle庫是Python自帶的&#xff0c;我們可以使用它來繪制一個簡單的煙花效果。 下面是一個使…

Stable Diffusion AMD加速方法-ZLUDA重出江湖

目前幾大開源的Stable Diffusion平臺&#xff0c;更新速度都慢了&#xff0c;一個是沒有太多新技術出現&#xff0c;新出的基礎模型也都不完整開源了&#xff08;API調用&#xff09;&#xff0c;能整的功能&#xff0c;也都整得差不多了。然后一群AMD死忠還在等著有一個能有一…

【前端】使用 Canvas 實現貪吃蛇小游戲

使用 Canvas 實現貪吃蛇小游戲 在這篇博客中&#xff0c;我們將介紹如何使用 HTML5 Canvas 和 JavaScript 實現一個簡單的貪吃蛇&#xff08;Snake&#xff09;小游戲。這個項目是一個基礎的游戲開發練習&#xff0c;它可以幫助你理解如何在 Canvas 上繪圖、如何處理用戶輸入以…

Laravel(Lumen8) + Supervisor 實現多進程redis消息隊列

相關文章:Supervisor守護進程工具安裝與使用 1、通用消息隊列 /App/Job/CommonJob.php: <?phpnamespace App\Jobs; use Illuminate\Support\Facades\Log; use Illuminate\Support\Str;class CommonJob extends Job {public $timeout; //超時時間protected $data; //隊列…

Android:OkHttp網絡請求框架的使用

目錄 一&#xff0c;OkHttp簡介 二&#xff0c;OkHttp請求處理流程 三&#xff0c;OkHttp環境配置 四&#xff0c;OkHttp的使用 1.get網絡請求 2.post上傳表單數據 3.post上傳json格式數據 4.文件上傳 5.文件下載 一&#xff0c;OkHttp簡介 OkHttp是square公司推出的一…

npm 源管理工具 nrm

npm 源管理工具 nrm 安裝(可能很慢, 多試幾次) npm install -g nrm查看可選擇源列表 nrm ls切換源 nrm use <registry>如: nrm use taobao

rust的版本問題,安裝問題,下載問題

rust的版本、安裝、下載問題 rust版本問題&#xff0c; 在使用rust的時候&#xff0c;應用rust的包&#xff0c;有時候包的使用和rust版本有關系。 error: failed to run custom build command for pear_codegen v0.1.2 Caused by: process didnt exit successfully: D:\rus…

鴻蒙全面開發指南:入門、生態安全與資源支持

鴻蒙全面開發指南&#xff1a;入門、生態安全與資源支持 本文全面梳理了鴻蒙操作系統的開發入門流程&#xff0c;涵蓋了開發環境準備、工具使用、項目創建、模擬器配置到應用安全設計的各個方面。通過本文&#xff0c;讀者不僅能夠了解鴻蒙開發的基本步驟&#xff0c;也能深入理…

Aspose.PDF功能演示:在 JavaScript 中將 TXT 轉換為 PDF

您是否正在尋找一種在 JavaScript 項目中將純文本文件從TXT無縫轉換為PDF格式的方法&#xff1f;您來對地方了&#xff01;無論您是要構建 Web 應用程序、創建生產力工具&#xff0c;還是只是希望簡化工作流程&#xff0c;直接從 JavaScript 代碼中將 TXT 轉換為 PDF 的功能都可…

第3天 Web源碼拓展_小迪網絡安全筆記

1.關于web源碼目錄結構 #數據庫配置文件 后臺目錄 模板目錄 數據庫目錄 1.1數據庫配置文件: 1.1就拿wordpress來說,先到官網下載源碼:Download – WordPress.org,解壓源碼之后: 2.2找到目錄下名為 wp-config-sample.php的文件,這就是數據庫配置文件: 設想: 我們在滲透…