基于C语言实现一个文本编辑器
資源下載地址:https://download.csdn.net/download/sheziqiong/85697086
資源下載地址:https://download.csdn.net/download/sheziqiong/85697086
一、設(shè)計(jì)思路
本次大作業(yè)要求實(shí)現(xiàn)一個(gè)文本編輯器。VIM 是有三種不同的模式,通過(guò)在鍵盤(pán)上輸入不同的字符鍵,就能切換到不同的功能,實(shí)現(xiàn)插入、移動(dòng)光標(biāo)、新建文件、保存文件等。但是要實(shí)現(xiàn)對(duì)鍵盤(pán)的控制以及屏幕光標(biāo)的控制,就需要增加系統(tǒng)調(diào)用。所以第一步是先要了解如何增加一個(gè)系統(tǒng)調(diào)用。
新增系統(tǒng)調(diào)用包括兩大部分,一方面,需要在內(nèi)核中注冊(cè)它,讓系統(tǒng)認(rèn)為它是一個(gè)系統(tǒng)調(diào)用(包括系統(tǒng)調(diào)用編號(hào)、syscalls[]中的注冊(cè)等),另一方面要實(shí)現(xiàn)系統(tǒng)調(diào)用的具體功能代碼,這一部分和普通函數(shù)無(wú)差別。(具體實(shí)現(xiàn)在下部分中說(shuō)明)
了解了系統(tǒng)調(diào)用的過(guò)程之后,我們還需要了解,在原先 xv6 的系統(tǒng)中,如何確定光標(biāo)位置和移動(dòng)光標(biāo)的,以及拿到對(duì)光標(biāo)的“控制權(quán)”。這部分在 xv6 源碼中有體現(xiàn),我們仿照源碼,包裝了函數(shù)來(lái)得到光標(biāo)的位置、設(shè)置光標(biāo)位置和移動(dòng)光標(biāo)。
在前面的基礎(chǔ)上,我們還需要完成對(duì)清屏和屏幕的備份和恢復(fù)。在 VIM 中,輸入 VIM xxx 之后,原先控制臺(tái)的內(nèi)容就清空了,然后顯示了文件的內(nèi)容,最后退出時(shí),又恢復(fù)了原來(lái)控制臺(tái)的內(nèi)容。所以我們參照之后設(shè)計(jì),需要在屏幕顯示文件內(nèi)容之前,備份屏幕中的內(nèi)容,然后清屏,在完成編輯之后,恢復(fù)控制臺(tái)的內(nèi)容。
完成以上內(nèi)容之后,我們就可以完成特定的功能了,比如新建文件、插入內(nèi)容、刪除內(nèi)容以及功能鍵的映射等等。
二、操作系統(tǒng)的準(zhǔn)備
中斷
用戶(hù)按下鍵盤(pán)上的按鍵時(shí),觸發(fā)了鍵盤(pán)中斷,執(zhí)行對(duì)應(yīng)的程序去執(zhí)行中斷。我們先是查閱相關(guān)的資料和閱讀源碼,了解了操作系統(tǒng)處理中斷的流程。也在這個(gè)過(guò)程中,發(fā)現(xiàn)了一些新的思路。
在 trap.c 文件中,trap 函數(shù)用于處理中斷,處理鍵盤(pán)中斷的代碼如下所示;
在 kdb.c 中,我們找到對(duì)應(yīng)的函數(shù),可以看到這個(gè)函數(shù)調(diào)用了另一個(gè)函數(shù),其中參數(shù)是在同一個(gè)文件中的 kbdgetc
在 consoleintr 函數(shù)中,就是對(duì)輸入的字符進(jìn)行處理。紅色框內(nèi),是對(duì)輸入的普通字符的一個(gè)處理,我們可以看到調(diào)用了 consputc 往控制臺(tái)屏幕上輸出一個(gè)字符。
對(duì)應(yīng)的 consputc 函數(shù)中主要是做一些判斷之后,調(diào)用 cgaputc 函數(shù)來(lái)回顯在屏幕中。
我們重點(diǎn)關(guān)注這個(gè) cgaputc,這個(gè)函數(shù)中第一個(gè)紅框內(nèi)的代碼,主要是用于得到光標(biāo)的位置,第二個(gè)紅框內(nèi)的代碼用于設(shè)置光標(biāo)位置,以及顯示字符。中間代碼是判斷輸入是換行函數(shù)刪除以及是否需要換行。
到這里,我們就了解了在鍵盤(pán)中按下字符之后,xv6 中是如何顯示到屏幕上的。同時(shí),也在 xv6 源碼中知道了 xv6 系統(tǒng)是如何獲取光標(biāo)位置和設(shè)置光標(biāo)位置。這也為我們后面實(shí)現(xiàn)光標(biāo)移動(dòng)提供了思路。
獲取光標(biāo)位置
前面提到,我們需需要對(duì)屏幕進(jìn)行操作,所以我們需要知道光標(biāo)的位置和設(shè)置光標(biāo)的位置以及對(duì)屏幕的清空等。于是包裝了幾個(gè)函數(shù)來(lái)實(shí)現(xiàn)對(duì)屏幕的操作。
在 console.c 文件中,增加 getCursorPos()函數(shù)。在原本的 xv6 源碼中,就有對(duì)光標(biāo)的操作的代碼,但是沒(méi)有獨(dú)立包裝為一個(gè)函數(shù)。在這我們?cè)?xv6 源碼中的代碼把它封裝為一個(gè)函數(shù),這樣子可以在其他需要獲取光標(biāo)位置的地方直接調(diào)用這個(gè)函數(shù)。
同樣的,我們要完成移動(dòng)光標(biāo)的功能,所以就需要設(shè)置光標(biāo)位置,所以也封裝了一個(gè)函數(shù)來(lái)設(shè)置光標(biāo)位置。函數(shù)代碼也是借鑒 xv6 源碼中對(duì)光標(biāo)的操作。我們傳入的參數(shù)是 x,y。x 是行號(hào),y 是列號(hào)。這相當(dāng)于是一個(gè)坐標(biāo)位置,左上角位置為(0,0)。Xv6 的控制臺(tái)屏幕是固定為一行 80 個(gè)字符的。所以我們需要把左邊換算為當(dāng)前位置在數(shù)組中是第幾個(gè)位置。Pos=80*x+y;
增加系統(tǒng)調(diào)用
我們?cè)?console.c 中增加了上面那個(gè)函數(shù)之后,還只是一個(gè)普通函數(shù),必須把這些函數(shù)變成系統(tǒng)調(diào)用。為什么要增加系統(tǒng)調(diào)用呢?因?yàn)槲覀兊某绦蛐枰刂破聊还鈽?biāo)的移動(dòng)和屏幕內(nèi)容的顯示等,這些功能是由內(nèi)核來(lái)控制的,用戶(hù)態(tài)下是不能有控制權(quán)的,所以我們就需要增加系統(tǒng)調(diào)用,在系統(tǒng)調(diào)用中拿到對(duì)屏幕的控制器,然后在用戶(hù)態(tài)通過(guò)接口去拿到對(duì)屏幕光標(biāo)的控制權(quán)。
下面介紹一下我們添加系統(tǒng)調(diào)用的整個(gè)流程,以 getCursorPos 函數(shù)為例。
先是在 console.c 函數(shù)中,實(shí)現(xiàn)該函數(shù),即完成函數(shù)的功能的代碼。
在 user.h 和 defs.h 中,定義該函數(shù)
在 usys.S 的最后,加上:SYSCALL(getCursorPos)
至此,完成了該函數(shù)的原型實(shí)現(xiàn)和用戶(hù)系統(tǒng)調(diào)用接口。然而現(xiàn)在它還不是一個(gè)系統(tǒng)調(diào)用,需要進(jìn)一步配置將其加入系統(tǒng)調(diào)用的范圍。
在 syscall.h 的最后,加入系統(tǒng)調(diào)用號(hào)的定義。按照默認(rèn)的寫(xiě)法,以 SYS_開(kāi)頭加函數(shù)名
在 syscall.c 中,注冊(cè)該函數(shù):(一共有兩處)
第一處:extern 引入外部定義
第二處:在 static int (*syscalls[])(void)的大括號(hào)中)
將系統(tǒng)調(diào)用標(biāo)識(shí) SYS_getCursorPos 和系統(tǒng)調(diào)用函數(shù) sys_getCursorPos 關(guān)聯(lián)起來(lái)了。
最后一步,也是最關(guān)鍵的,那就是如何將系統(tǒng)調(diào)用 sys_getCursorPos 和功能函數(shù) getCursorPos 關(guān)聯(lián)起來(lái),也就是說(shuō),只有通過(guò)這一步,getCursorPos 才能成為系統(tǒng)調(diào)用。
在正式進(jìn)入編輯頁(yè)面之前,需要備份屏幕的內(nèi)容。Xv6 中自帶了一個(gè)拷貝復(fù)制的函數(shù) memmove。
void *memmove( void* dest, const void* src, size_t count );由src所指內(nèi)存區(qū)域復(fù)制count個(gè)字節(jié)到dest所指內(nèi)存區(qū)域。所以我們只需要調(diào)用這個(gè)函數(shù),并指定 src 源地址和 dest 拷貝到的目的地址,以及字節(jié)數(shù)。以及 size 的大小就是屏幕中字符數(shù)和每個(gè)字符的大小
在主函數(shù)中,在進(jìn)入顯示文件的內(nèi)容之前,先得到屏幕光標(biāo)的位置,然后計(jì)算需要保存的字節(jié)大小 screen_size。以及開(kāi)辟一個(gè) savescreen 數(shù)組,大小是剛剛得到的 screen_size,然后把他們作為參數(shù)傳給 saveScreen,來(lái)進(jìn)行備份
恢復(fù)屏幕內(nèi)容
前面?zhèn)浞莸臅r(shí)候,我們開(kāi)辟了一個(gè)數(shù)組來(lái)備份原來(lái)屏幕中的內(nèi)容,以及保存了原來(lái)內(nèi)容的字節(jié)長(zhǎng)度。所以恢復(fù)的時(shí)候,只需要將備份數(shù)組中的內(nèi)容拷貝到 crt 顯存數(shù)組中。
這里還涉及到清屏
- 我們只需要把 CRT 數(shù)組中的內(nèi)容全部賦值為 0,并且設(shè)置光標(biāo)位置在(0,0)的位置
- 字體改變顏色
- 我們查閱資料,得知
“在 CGA 的文本模式編程中,每一個(gè)屏幕位置需要占用 2 個(gè)字節(jié)的 Video-RAM。第一個(gè)字節(jié)(偶數(shù)的偏移地址)為字符所對(duì)應(yīng)的 ASCII 碼,第二個(gè)字節(jié)(奇數(shù)的偏移地址)為定義前景色、后景
色以及閃爍。”
所以我們可以通過(guò)設(shè)置第二個(gè)字節(jié)的值,來(lái)控制字體的顏色。
我們定義了一個(gè)顏色數(shù)組,來(lái)記錄各種顏色的對(duì)應(yīng)的數(shù)值
在 cgaputc 函數(shù)中,我們也可以看到設(shè)置顏色的代碼。我們看到,輸入的 c 是先和 0xff 做&運(yùn)算,保留低 8 位,再與 0x0700 做或運(yùn)算,設(shè)置前 8 為位為 0x07,即 00000111,其中第一個(gè) 0 代表背景色,為黑色,第一個(gè) 7 代表字符色,為淡灰色。即高八位是顏色,低八位是字符 ASII 碼。在前 8 位里面,前 4 位代表背景色,后 4 位代表字符色。
到此,我們就大概明白了如何對(duì)字符上色,可以通過(guò)控制高 8 位的值來(lái)顯色。
我們寫(xiě)了 getColor 函數(shù)來(lái)調(diào)色,參數(shù) tcolor 為前景色,bcolor 為背景色。因?yàn)榍?4 位是背景色,所以我們需要將 bcolor 左移 4 位,再或上前景色 tcolor。這樣子我們就設(shè)置了控制字符顏色的前 8 位。
然后通過(guò)下面這行代碼就可以設(shè)置顯存中字符的顏色了。
三、程序設(shè)計(jì)
主要的代碼邏輯就是:當(dāng)用戶(hù)輸入 vi file 的時(shí)候,進(jìn)入 vi.c 的 main 函數(shù)中。在 main 函數(shù)中,先判斷參數(shù)是否滿(mǎn)足要求,之后判斷是否存在文件,不存在就新建文件,存在就讀取文件原來(lái)的內(nèi)容到內(nèi)存中。然后保存?zhèn)浞萜聊粌?nèi)容,更新屏幕內(nèi)容,把內(nèi)存中文件的內(nèi)容顯示到屏幕中。然后進(jìn)入命令模式,利用 while 循環(huán)不斷得到用戶(hù)的輸入,判斷是命令還是字符,然后插入到內(nèi)容和屏幕中。直到退出編輯,就恢復(fù)原來(lái)屏幕的內(nèi)容,保存文件的內(nèi)容。
整體的思路如下:
新建文件
當(dāng)我們?cè)诳刂婆_(tái)輸入 vi 之后,會(huì)進(jìn)到主函數(shù)中,判斷輸入的文件名是否存在,如果不存在,就需要新建一個(gè)文件,主程序會(huì)調(diào)用函數(shù)來(lái)創(chuàng)建文件。
- 利用 open 函數(shù)來(lái)實(shí)現(xiàn)創(chuàng)建一個(gè)新的函數(shù),并且原先是沒(méi)有值的,我們初始化寫(xiě)入\0。
- 讀入文件
- 如果 vi 打開(kāi)的文件是存在的,只需要在屏幕中顯示文件中的內(nèi)容。我們利用 readFile 函數(shù)來(lái)實(shí)現(xiàn)從文件中讀取一定數(shù)量的字節(jié),插入到屏幕光標(biāo)位置開(kāi)始的顯存中。
- 讀取的代碼如下圖所示。也是系統(tǒng)系統(tǒng)提供判斷 open 函數(shù)打開(kāi)文件,而且需要判斷這個(gè)文件是否是可讀的。在文件是可讀入之后,利用 read 函數(shù)來(lái)讀取內(nèi)容,插入到指針到 p 處。
讀取的內(nèi)容放在哪里呢?我們開(kāi)辟了一個(gè) text 的空間,來(lái)存放打開(kāi)的文件的內(nèi)容。下面的代碼就是初始化中緩存空間。
調(diào)用完這個(gè)函數(shù),我們就得到了文件的內(nèi)容,還需要把他顯示到屏幕上,所以主函數(shù)中調(diào)用了 reDraw 函數(shù)來(lái)刷新顯示器。在這個(gè)函數(shù)內(nèi),又調(diào)用了系統(tǒng)調(diào)用 copyFromTextToScreen 來(lái)把 text 的內(nèi)容顯示到屏幕中。
這個(gè)函數(shù)在 console.c 中,被包裝成了系統(tǒng)調(diào)用,結(jié)合后面的高亮顯示,我們?cè)谶@部分也可以設(shè)置顏色。getcolor 函數(shù)就是來(lái)設(shè)置字符顏色(在后面字符顏色個(gè)高亮部分會(huì)仔細(xì)介紹)。利用 for 循環(huán)遍歷每一個(gè)字符,然后設(shè)置對(duì)應(yīng)的顏色,然后利用 showC 函數(shù)顯示出來(lái)。
showC 函數(shù)主要是獲取光標(biāo)位置,判斷當(dāng)前字符是一些特殊字符還是正常的字符,然后對(duì)應(yīng)的顯示到屏幕上(crt 數(shù)組中,在前面的部分我們已經(jīng)介紹了)以及當(dāng)屏幕內(nèi)容滿(mǎn)屏了的時(shí)候,翻頁(yè)換行。
光標(biāo)移動(dòng)
先是需要在進(jìn)入 vi 之后,while 循環(huán)不斷的利用 read 函數(shù)去讀取鍵盤(pán)的輸入,然后利用 doCmd 去對(duì)輸入的字符進(jìn)行判斷和處理。先是判斷輸入的字符類(lèi)型,比如有些是一些指令:像操作光標(biāo)的移動(dòng)。
先是定義了一些特殊按鍵對(duì)應(yīng)的 int 值。
類(lèi)似的,把光標(biāo)移動(dòng)到行首和行尾也是的
借助每行的行尾都是以\n 結(jié)尾的,來(lái)判斷當(dāng)前是否在行首和行尾,然后也是對(duì)光標(biāo)進(jìn)行加減操作來(lái)移動(dòng)光標(biāo)位置。
接受鍵盤(pán)輸入,回顯到屏幕中,然后保存到文件中。
如果是在鍵盤(pán)中輸入 i、I、a、A 進(jìn)入插入模式,這則對(duì)調(diào)用 insert 函數(shù),來(lái)進(jìn)入文本的插入。區(qū)別是這幾個(gè)不同的模式下,進(jìn)入編輯之后,光標(biāo)的位置不同。比如 I 就是在行首進(jìn)行插入,適應(yīng)需要移動(dòng)到行首,a 就是追加模式,需要先往右移動(dòng)一個(gè)字符進(jìn)行插入。
輸入的是 I 的話(huà),是需要先把光標(biāo)移動(dòng)到行首,再進(jìn)入插入模式,所以調(diào)用了 dot_head 函數(shù)來(lái)移動(dòng)光標(biāo)到行首。
輸入 a 的話(huà),是進(jìn)入追加模式,所以需要先把光標(biāo)向右移動(dòng)一個(gè)字符,利用 dot_right 函數(shù)來(lái)實(shí)現(xiàn)。A 對(duì)應(yīng)的是移動(dòng)到行尾再進(jìn)入插入模式,所以需要調(diào)用 dot_tail 把光標(biāo)設(shè)置到行尾,然后因?yàn)樾形灿小痋n’,所以需要再回退一個(gè)位置,不能在\n 的位置上編輯。所以調(diào)用了 dot_decrease 函數(shù)來(lái)實(shí)現(xiàn)。
在插入模式下,可以不斷輸入字符,所以需要利用 while 循環(huán),不斷讀取鍵盤(pán)輸入的字符。然后也是需要判斷輸入的字符的類(lèi)型,因?yàn)樵诰庉嬆J较?#xff0c;可以輸入一些命令退出編輯模式,而不是輸入字符。利用 inPrintChar 函數(shù)判斷完是可以顯示在屏幕中的字符之后,利用 make_hole_add 函數(shù)來(lái)在當(dāng)前位置空出位置來(lái)。
如果當(dāng)前位置及其右邊有字符,則需要把字符往后挪 n 個(gè)位置,空出光標(biāo)所指位置開(kāi)始的 n 個(gè)位置。然后把讀取到的字符存放在當(dāng)前位置開(kāi)始的連續(xù) n 個(gè)位置中,光標(biāo)后移。再重新刷新屏幕,回顯剛剛輸入的字符。
同樣的,在編輯模式下,也可以移動(dòng)光標(biāo),所以在判斷是不是普通字符之后,需要判斷是否是移動(dòng)光標(biāo)的操作。
退出 vi,保存文件
在按下冒號(hào):之后,用戶(hù)輸入特定的指定來(lái)保存文件和退出 vi。
進(jìn)入 doColon 函數(shù),完成后續(xù)的判斷功能
先是利用 while 循環(huán),得到用戶(hù)在冒號(hào)之后的輸入。然后對(duì)命令進(jìn)行解析,分別有“q!”, “ q” , “wq”, “w”幾種指令。判斷用戶(hù)輸入的指令是哪一種之后執(zhí)行對(duì)應(yīng)的操作。如“q!”就是強(qiáng)制退出,不保存。 “wq”就是保存之后退出。我們利用 file_save 函數(shù)來(lái)執(zhí)行保存文件內(nèi)容的操作。
用戶(hù)所有的輸入,我們都保存在 text 內(nèi)存空間中,所有在保存時(shí),我們需要把這部分空間中的內(nèi)容輸入到文件中并保存。也是先打開(kāi)文件,然后利用系統(tǒng)自帶的 write 函數(shù)把 text 中的內(nèi)容寫(xiě)入到文件對(duì)象中。
- 功能鍵映射
- 在上面的介紹中,我們已經(jīng)提及了一部分功能鍵映射的實(shí)現(xiàn),包括光標(biāo)的上下左右的移動(dòng)和一些插入模式的指令以及輸入冒號(hào)之后不同的退出模式。
- 我們還完成了其他的功能鍵的映射
四、翻頁(yè)
- 上述代碼分別對(duì)應(yīng)了向上翻頁(yè)和向下翻頁(yè)。
- 向上翻頁(yè)是,一個(gè)屏幕是顯示 24 行的內(nèi)容,我們向上翻頁(yè)其實(shí)就是光標(biāo)向上移動(dòng) 24 行。利用一個(gè) for 循環(huán),不斷的向上移動(dòng)一行,直到到底最頂行或者已經(jīng)移動(dòng)了 24 行。
- 向下翻頁(yè)也是同樣的道理,利用 for 循環(huán)光標(biāo)向下移動(dòng) 24 行。
五、刪除
按下 x 鍵之后,調(diào)用 dot_delete 刪除光標(biāo)位置的字符,
在 dot_delete 函數(shù)中,是先調(diào)用 deleteText 函數(shù)來(lái)刪除從開(kāi)始位置到結(jié)尾的空間的,然后更新頁(yè)面的。
而 deleteText 函數(shù)中,也是利用 memmove 函數(shù),把 end+1 開(kāi)始的字符移動(dòng)到 start 開(kāi)始的位置,覆蓋原來(lái)的字符,實(shí)現(xiàn)刪除的功能。
六、查找
同樣的,在判斷用戶(hù)的輸入的 case 中,匹配到用戶(hù)輸入了“/”之后,就得到光標(biāo)位置,然后在最下面的一行寫(xiě)入用戶(hù)輸入的指令,同時(shí)利用 while 循環(huán),去讀取用戶(hù)在/之后的輸入,即要查找的內(nèi)容,把他放在了一個(gè)數(shù)組 currentFindString 里面,同時(shí)利用 writeAt 回顯在屏幕中。我們現(xiàn)在了存放用戶(hù)要查找的內(nèi)容的數(shù)組的長(zhǎng)度為 32,所以最多只能查找 31 個(gè)字符。最后是把我們剛剛保存了用戶(hù)輸入的數(shù)組 currentFindString 拷貝到另一個(gè)數(shù)組 lastFindString 中,對(duì)數(shù)組 lastFindString 的內(nèi)容進(jìn)行查找,相當(dāng)于對(duì)用戶(hù)輸入做了一個(gè)備份。上圖這部分代碼主要用于獲取用戶(hù)輸入的查找內(nèi)容,以及回顯到屏幕中。
在上圖中,都會(huì)判斷用戶(hù)輸入的查找內(nèi)容是否為 0,0 的話(huà)代表沒(méi)有用戶(hù)輸入的內(nèi)容,就不進(jìn)行查找。如果用戶(hù)輸入不為 0 的話(huà),就分別調(diào)用 findString 和 reverseFindString 進(jìn)行查找。返回值都是 dot,是光標(biāo)的地址指針。然后利用 synchronize 函數(shù)對(duì)光標(biāo)位置進(jìn)行同步顯示,把光標(biāo)設(shè)置到最新的位置。并且在底部輸入查找的內(nèi)容。
findString 函數(shù)的實(shí)現(xiàn)如上圖所示。s 是我們傳進(jìn)去的要查找的內(nèi)容,從 dot 所在的位置起(指針 p),利用 equal 函數(shù),判斷 p 和 s 所指的內(nèi)容是否相同。如果返回值是 1,代表是相同的,證明找到了匹配的內(nèi)容,p 所指向的位置就是我們找到的匹配的內(nèi)容的首地址,把這個(gè)地址返回。其他代碼是一些特殊情況的判斷,比如沒(méi)找到就輸出“pattern not found”,并且返回原來(lái)光標(biāo)的位置,光標(biāo)不改變位置。
反向查找類(lèi)似,只不過(guò)把上面的 p++,變成了 p–,查找在光標(biāo)位置之前的內(nèi)容。
七、高亮
關(guān)于高亮,我們目前是只實(shí)現(xiàn)了匹配.c 文件的詞法高亮,其他文件的高亮還有待完成。
我們?cè)?vi.c 的主函數(shù)中,先匹配下輸入的是不是.c 文件,如果是,就設(shè)置 flagCfile 的值為 1。然后在進(jìn)入 vi 的界面。
在 intoVi 函數(shù)中,我們通過(guò)判斷 flagCfile 是否為 1,來(lái)顯示不同的界面。
- 在 reDrawC 中,主要是調(diào)用了 highlightText 函數(shù)來(lái)顯示高亮。
- 重點(diǎn)就是在 highlightText 函數(shù)來(lái)做一些 c 文件的匹配規(guī)則。
- 先是開(kāi)創(chuàng)了一個(gè) ColorText 的新數(shù)組來(lái)存放帶有字符的顏色。代碼如下(部分)
我們主要是匹配 3 種 c 的關(guān)鍵字,一種是#include,#define 這種,我們用藍(lán)色來(lái)顯示。一種是 int 、void 之后變量類(lèi)型的聲明的,我們用綠色來(lái)顯示。還有一直是 while,if else 之后關(guān)鍵字,我們用黃色來(lái)顯示。我們是利用一個(gè) re.c 文件中的 re_compile 來(lái)做正則匹配的。對(duì)我們指定的一些關(guān)鍵字進(jìn)行匹配之后,利用 setColorC 函數(shù)給匹配的字符設(shè)置顏色。代碼如下。
然后包裝了一個(gè)新的系統(tǒng)調(diào)用 Toscreen,來(lái)把我們得到的帶有顏色的字符文本顯示帶屏幕上。
形參 color 對(duì)應(yīng)的值就是我們剛剛應(yīng)該正則匹配到的每個(gè)字符對(duì)應(yīng)的顏色。然后通過(guò) showC 設(shè)置每個(gè)字符的顏色,以及顯示在屏幕中。
showC 這段代碼是借鑒 xv6 源碼的,只不過(guò)我們把 color 換成了每個(gè)字符對(duì)應(yīng)的顏色,而不是固定的 0x0700。
到此,高亮的功能就算實(shí)現(xiàn)了。
八、顯示狀態(tài)欄
我們把屏幕最下面 25 行作為狀態(tài)欄,來(lái)顯示當(dāng)前操作的類(lèi)型(是插入還是命令還是錯(cuò)誤的指令)以及來(lái)顯示當(dāng)前光標(biāo)的位置。
代碼如下
參數(shù) s 是操作類(lèi)型,由調(diào)用的地方傳入,col 是對(duì)應(yīng)的顏色,比如如果是錯(cuò)誤的指令,就顯示紅色。也是利用 getCursorPos 獲取光標(biāo)的位置,然后轉(zhuǎn)換為坐標(biāo),即第幾行第幾列的值,利用兩個(gè) while 循環(huán),把他是幾行幾列轉(zhuǎn)換為字符,存進(jìn)數(shù)組中,最后存入模式狀態(tài)。把一整個(gè)數(shù)組的值,顯示到屏幕中。
九、具體效果
- 實(shí)現(xiàn)新建文件,讀入文件,修改文件,寫(xiě)入文件功能
- 新建文件
- 輸入 vi mynew.txt 之后,qemu 的窗口清空,因?yàn)檫@是一個(gè)新的文件,所以窗口并沒(méi)有值。
在退出登陸之后,可以看到原來(lái)屏幕中的內(nèi)容
- 輸入 ls,可以看到在目錄下生成了 mynew.txt 文件
- 讀入文件
- 再次打開(kāi) mynew.txt 文件我們可以看到,上次我們編輯的內(nèi)容保存了,并且在本次打開(kāi)后,正確的顯示在屏幕內(nèi)了,證明我們實(shí)現(xiàn)了讀入文件內(nèi)容到內(nèi)存以及顯示的功能了。
修改文件和寫(xiě)入文件
在鍵盤(pán)中按下 i(或者 a,I,A)進(jìn)入編輯模式之后,就可以對(duì)文件內(nèi)容進(jìn)行修改
我插入了以上內(nèi)容,可以看到在最下面一行,顯示 insert 代表是插入模式。按下 ESC 之后退出編輯模式,再鍵入:wq 就可以保存剛剛編輯的內(nèi)容了。
輸入 cat mynew.txt,可以看到剛剛編輯的內(nèi)容確實(shí)是保存了,寫(xiě)入文件成功。
同樣的,我們也可以刪除一些內(nèi)容,在下圖中,我利用按鍵 x 刪除其中一行的所有字符。
同樣可以看到
刪除之后的文件保存了,刪除成功。
實(shí)現(xiàn) VIM 或 Emacs 的基本功能鍵映射
十、插入模式,i,I,a,A 功能鍵映射
'i': 進(jìn)入插入模式 'I': dot移到行首再進(jìn)入插入模式 'a': 進(jìn)入追加模式 'A': dot移到行尾再進(jìn)入插入模式比如,現(xiàn)在的光標(biāo)在 hello 中 e 的位置
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-AVJQ0xIy-1655639490388)(https://www.writebug.com/myres/static/uploads/2022/6/19/bbeeabaa33b2bede6fca45653d659596.writebug)]
我按下 A 之后,光標(biāo)移動(dòng)到這行的行尾,進(jìn)入編輯模式,底線(xiàn)顯示 insert 表明可以插入
其他的插入模式的功能鍵映射類(lèi)似,我就不一一介紹了。
十一、保存以及退出的功能鍵映射
- 退出
- q! 強(qiáng)制退出
- 保存文件
- wq 保存再退出
- 這部分功能在第三部分程序設(shè)計(jì)中已經(jīng)做了詳細(xì)的展示,且比較難以直接通過(guò)截圖展示功能,我就不在這介紹了,詳細(xì)實(shí)現(xiàn)過(guò)程請(qǐng)看持程序設(shè)計(jì)中第四部分
十二、查找
在輸入/之后,可以輸入我們想要查找的內(nèi)容,按下回車(chē)之后,如果找到,就會(huì)把光標(biāo)移動(dòng)到查找到的內(nèi)容的第一個(gè)位置上,然后底線(xiàn)會(huì)顯示我們查找的內(nèi)容。
可以看到,光標(biāo)都在我們查找的單詞的第一個(gè)首字母下面。默認(rèn)情況下是匹配第一個(gè)找到的單詞,如果有多個(gè)匹配的單詞,可以再輸入 n,或者 N,移動(dòng)到下一個(gè)匹配的單詞上。n 是正向匹配,N 是反向匹配。
比如我們輸入 /new ,會(huì)匹配到第一個(gè)一個(gè) new ,光標(biāo)位置在(2,1)
按下 n,則會(huì)正向匹配到下一個(gè),即(2,5)的位置。再按下 N,則會(huì)再匹配回第一個(gè),N 是反向查找匹配的。
輸入 N 之后,匹配回上一個(gè)!
十三、翻頁(yè)
我們利用鍵盤(pán)上的 pgup 鍵來(lái)向上翻頁(yè),pgdn 向下翻頁(yè)。
我在文件里面增加了內(nèi)容,使它分布在兩頁(yè),如圖所示。然后按下 pgup 和 pgdn 是可以在這兩頁(yè)中切換的。
十四、刪除
我們是利用按鍵 x 來(lái)刪除字符的。
比如上面的文本,我在命令模式下,按下 x,就可以刪除光標(biāo)位置的字符,而且在最下面一行會(huì)提示 delete。可以看到,我們確實(shí)是刪除了一個(gè)字符。
根據(jù)打開(kāi)文件后綴名的不同,實(shí)現(xiàn)不同的語(yǔ)法高亮功能(C/html/…)(加 20% 完成度);
我們打開(kāi)了一個(gè) c 文件,可以看到確實(shí)是匹配出了一些關(guān)鍵字。實(shí)現(xiàn)了高亮功能。
- 但是還是存在一些缺陷,匹配規(guī)則不夠完善,完成的不是非常好。
- 實(shí)現(xiàn)光標(biāo)動(dòng)態(tài)移動(dòng)(加 30% 完成度)
- 我們首先了通過(guò)鍵盤(pán)的上下左右方向鍵來(lái)控制光標(biāo)的移動(dòng)。下圖紅色方框內(nèi)的小橫線(xiàn)就是光標(biāo)閃爍的位置。
通過(guò)鍵盤(pán)移動(dòng)光標(biāo),可以實(shí)現(xiàn)把光標(biāo)移動(dòng)到任意位置。
其他的零碎的部分
1)
我們?cè)谧钕旅嬉恍?#xff0c;輸出了一些指令
比如按下 i,進(jìn)入編輯模式,會(huì)輸出 Insert 的提示。
如果輸出了錯(cuò)誤的命令,我們會(huì)用紅色的字體提示輸入錯(cuò)誤的字符
在命令模式下,只是黃色的提醒
光標(biāo)位置
VIM 中也是有光標(biāo)位置顯示的,我們也是通過(guò) getcurosPos 來(lái)得到光標(biāo)的位置,把他顯示在最后一行的行尾。
動(dòng)。下圖紅色方框內(nèi)的小橫線(xiàn)就是光標(biāo)閃爍的位置。
資源下載地址:https://download.csdn.net/download/sheziqiong/85697086
資源下載地址:https://download.csdn.net/download/sheziqiong/85697086
總結(jié)
以上是生活随笔為你收集整理的基于C语言实现一个文本编辑器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Ubuntu中C语言编辑器
- 下一篇: c语言编辑器用什么字体颜色,几款小巧好用