基于C语言实现一个文本编辑器

资源下载地址:https://download.csdn.net/download/sheziqiong/85697086
资源下载地址:https://download.csdn.net/download/sheziqiong/85697086

一、设计思路

本次大作业要求实现一个文本编辑器。VIM 是有三种不同的模式,通过在键盘上输入不同的字符键,就能切换到不同的功能,实现插入、移动光标、新建文件、保存文件等。但是要实现对键盘的控制以及屏幕光标的控制,就需要增加系统调用。所以第一步是先要了解如何增加一个系统调用。

新增系统调用包括两大部分,一方面,需要在内核中注册它,让系统认为它是一个系统调用(包括系统调用编号、syscalls[]中的注册等),另一方面要实现系统调用的具体功能代码,这一部分和普通函数无差别。(具体实现在下部分中说明)

了解了系统调用的过程之后,我们还需要了解,在原先 xv6 的系统中,如何确定光标位置和移动光标的,以及拿到对光标的“控制权”。这部分在 xv6 源码中有体现,我们仿照源码,包装了函数来得到光标的位置、设置光标位置和移动光标。

在前面的基础上,我们还需要完成对清屏和屏幕的备份和恢复。在 VIM 中,输入 VIM xxx 之后,原先控制台的内容就清空了,然后显示了文件的内容,最后退出时,又恢复了原来控制台的内容。所以我们参照之后设计,需要在屏幕显示文件内容之前,备份屏幕中的内容,然后清屏,在完成编辑之后,恢复控制台的内容。

完成以上内容之后,我们就可以完成特定的功能了,比如新建文件、插入内容、删除内容以及功能键的映射等等。

二、操作系统的准备

中断

用户按下键盘上的按键时,触发了键盘中断,执行对应的程序去执行中断。我们先是查阅相关的资料和阅读源码,了解了操作系统处理中断的流程。也在这个过程中,发现了一些新的思路。

在 trap.c 文件中,trap 函数用于处理中断,处理键盘中断的代码如下所示;

在这里插入图片描述

在 kdb.c 中,我们找到对应的函数,可以看到这个函数调用了另一个函数,其中参数是在同一个文件中的 kbdgetc

在这里插入图片描述

在这里插入图片描述

在 consoleintr 函数中,就是对输入的字符进行处理。红色框内,是对输入的普通字符的一个处理,我们可以看到调用了 consputc 往控制台屏幕上输出一个字符。

在这里插入图片描述

对应的 consputc 函数中主要是做一些判断之后,调用 cgaputc 函数来回显在屏幕中。

我们重点关注这个 cgaputc,这个函数中第一个红框内的代码,主要是用于得到光标的位置,第二个红框内的代码用于设置光标位置,以及显示字符。中间代码是判断输入是换行函数删除以及是否需要换行。

到这里,我们就了解了在键盘中按下字符之后,xv6 中是如何显示到屏幕上的。同时,也在 xv6 源码中知道了 xv6 系统是如何获取光标位置和设置光标位置。这也为我们后面实现光标移动提供了思路。

获取光标位置

前面提到,我们需需要对屏幕进行操作,所以我们需要知道光标的位置和设置光标的位置以及对屏幕的清空等。于是包装了几个函数来实现对屏幕的操作。

在 console.c 文件中,增加 getCursorPos()函数。在原本的 xv6 源码中,就有对光标的操作的代码,但是没有独立包装为一个函数。在这我们原先 xv6 源码中的代码把它封装为一个函数,这样子可以在其他需要获取光标位置的地方直接调用这个函数。

同样的,我们要完成移动光标的功能,所以就需要设置光标位置,所以也封装了一个函数来设置光标位置。函数代码也是借鉴 xv6 源码中对光标的操作。我们传入的参数是 x,y。x 是行号,y 是列号。这相当于是一个坐标位置,左上角位置为(0,0)。Xv6 的控制台屏幕是固定为一行 80 个字符的。所以我们需要把左边换算为当前位置在数组中是第几个位置。Pos=80*x+y;

增加系统调用

我们在 console.c 中增加了上面那个函数之后,还只是一个普通函数,必须把这些函数变成系统调用。为什么要增加系统调用呢?因为我们的程序需要控制屏幕光标的移动和屏幕内容的显示等,这些功能是由内核来控制的,用户态下是不能有控制权的,所以我们就需要增加系统调用,在系统调用中拿到对屏幕的控制器,然后在用户态通过接口去拿到对屏幕光标的控制权。

下面介绍一下我们添加系统调用的整个流程,以 getCursorPos 函数为例。

先是在 console.c 函数中,实现该函数,即完成函数的功能的代码。

在 user.h 和 defs.h 中,定义该函数

在 usys.S 的最后,加上:SYSCALL(getCursorPos)

至此,完成了该函数的原型实现和用户系统调用接口。然而现在它还不是一个系统调用,需要进一步配置将其加入系统调用的范围。

在 syscall.h 的最后,加入系统调用号的定义。按照默认的写法,以 SYS_开头加函数名

在 syscall.c 中,注册该函数:(一共有两处)

第一处:extern 引入外部定义

第二处:在 static int (*syscalls[])(void)的大括号中)

将系统调用标识 SYS_getCursorPos 和系统调用函数 sys_getCursorPos 关联起来了。

最后一步,也是最关键的,那就是如何将系统调用 sys_getCursorPos 和功能函数 getCursorPos 关联起来,也就是说,只有通过这一步,getCursorPos 才能成为系统调用。

在正式进入编辑页面之前,需要备份屏幕的内容。Xv6 中自带了一个拷贝复制的函数 memmove。

void *memmove( void* dest, const void* src, size_t count );由src所指内存区域复制count个字节到dest所指内存区域。

所以我们只需要调用这个函数,并指定 src 源地址和 dest 拷贝到的目的地址,以及字节数。以及 size 的大小就是屏幕中字符数和每个字符的大小

在主函数中,在进入显示文件的内容之前,先得到屏幕光标的位置,然后计算需要保存的字节大小 screen_size。以及开辟一个 savescreen 数组,大小是刚刚得到的 screen_size,然后把他们作为参数传给 saveScreen,来进行备份

恢复屏幕内容

前面备份的时候,我们开辟了一个数组来备份原来屏幕中的内容,以及保存了原来内容的字节长度。所以恢复的时候,只需要将备份数组中的内容拷贝到 crt 显存数组中。

这里还涉及到清屏

  • 我们只需要把 CRT 数组中的内容全部赋值为 0,并且设置光标位置在(0,0)的位置
  • 字体改变颜色
  • 我们查阅资料,得知

“在 CGA 的文本模式编程中,每一个屏幕位置需要占用 2 个字节的 Video-RAM。第一个字节(偶数的偏移地址)为字符所对应的 ASCII 码,第二个字节(奇数的偏移地址)为定义前景色、后景

色以及闪烁。”

所以我们可以通过设置第二个字节的值,来控制字体的颜色。

我们定义了一个颜色数组,来记录各种颜色的对应的数值

在 cgaputc 函数中,我们也可以看到设置颜色的代码。我们看到,输入的 c 是先和 0xff 做&运算,保留低 8 位,再与 0x0700 做或运算,设置前 8 为位为 0x07,即 00000111,其中第一个 0 代表背景色,为黑色,第一个 7 代表字符色,为淡灰色。即高八位是颜色,低八位是字符 ASII 码。在前 8 位里面,前 4 位代表背景色,后 4 位代表字符色。

到此,我们就大概明白了如何对字符上色,可以通过控制高 8 位的值来显色。

我们写了 getColor 函数来调色,参数 tcolor 为前景色,bcolor 为背景色。因为前 4 位是背景色,所以我们需要将 bcolor 左移 4 位,再或上前景色 tcolor。这样子我们就设置了控制字符颜色的前 8 位。

然后通过下面这行代码就可以设置显存中字符的颜色了。

三、程序设计

主要的代码逻辑就是:当用户输入 vi file 的时候,进入 vi.c 的 main 函数中。在 main 函数中,先判断参数是否满足要求,之后判断是否存在文件,不存在就新建文件,存在就读取文件原来的内容到内存中。然后保存备份屏幕内容,更新屏幕内容,把内存中文件的内容显示到屏幕中。然后进入命令模式,利用 while 循环不断得到用户的输入,判断是命令还是字符,然后插入到内容和屏幕中。直到退出编辑,就恢复原来屏幕的内容,保存文件的内容。

整体的思路如下:

新建文件

当我们在控制台输入 vi 之后,会进到主函数中,判断输入的文件名是否存在,如果不存在,就需要新建一个文件,主程序会调用函数来创建文件。

  • 利用 open 函数来实现创建一个新的函数,并且原先是没有值的,我们初始化写入\0。
  • 读入文件
  • 如果 vi 打开的文件是存在的,只需要在屏幕中显示文件中的内容。我们利用 readFile 函数来实现从文件中读取一定数量的字节,插入到屏幕光标位置开始的显存中。
  • 读取的代码如下图所示。也是系统系统提供判断 open 函数打开文件,而且需要判断这个文件是否是可读的。在文件是可读入之后,利用 read 函数来读取内容,插入到指针到 p 处。

读取的内容放在哪里呢?我们开辟了一个 text 的空间,来存放打开的文件的内容。下面的代码就是初始化中缓存空间。

调用完这个函数,我们就得到了文件的内容,还需要把他显示到屏幕上,所以主函数中调用了 reDraw 函数来刷新显示器。在这个函数内,又调用了系统调用 copyFromTextToScreen 来把 text 的内容显示到屏幕中。

这个函数在 console.c 中,被包装成了系统调用,结合后面的高亮显示,我们在这部分也可以设置颜色。getcolor 函数就是来设置字符颜色(在后面字符颜色个高亮部分会仔细介绍)。利用 for 循环遍历每一个字符,然后设置对应的颜色,然后利用 showC 函数显示出来。

showC 函数主要是获取光标位置,判断当前字符是一些特殊字符还是正常的字符,然后对应的显示到屏幕上(crt 数组中,在前面的部分我们已经介绍了)以及当屏幕内容满屏了的时候,翻页换行。

光标移动

先是需要在进入 vi 之后,while 循环不断的利用 read 函数去读取键盘的输入,然后利用 doCmd 去对输入的字符进行判断和处理。先是判断输入的字符类型,比如有些是一些指令:像操作光标的移动。

先是定义了一些特殊按键对应的 int 值。

类似的,把光标移动到行首和行尾也是的

借助每行的行尾都是以\n 结尾的,来判断当前是否在行首和行尾,然后也是对光标进行加减操作来移动光标位置。

接受键盘输入,回显到屏幕中,然后保存到文件中。

如果是在键盘中输入 i、I、a、A 进入插入模式,这则对调用 insert 函数,来进入文本的插入。区别是这几个不同的模式下,进入编辑之后,光标的位置不同。比如 I 就是在行首进行插入,适应需要移动到行首,a 就是追加模式,需要先往右移动一个字符进行插入。

输入的是 I 的话,是需要先把光标移动到行首,再进入插入模式,所以调用了 dot_head 函数来移动光标到行首。

输入 a 的话,是进入追加模式,所以需要先把光标向右移动一个字符,利用 dot_right 函数来实现。A 对应的是移动到行尾再进入插入模式,所以需要调用 dot_tail 把光标设置到行尾,然后因为行尾有’\n’,所以需要再回退一个位置,不能在\n 的位置上编辑。所以调用了 dot_decrease 函数来实现。

在插入模式下,可以不断输入字符,所以需要利用 while 循环,不断读取键盘输入的字符。然后也是需要判断输入的字符的类型,因为在编辑模式下,可以输入一些命令退出编辑模式,而不是输入字符。利用 inPrintChar 函数判断完是可以显示在屏幕中的字符之后,利用 make_hole_add 函数来在当前位置空出位置来。

如果当前位置及其右边有字符,则需要把字符往后挪 n 个位置,空出光标所指位置开始的 n 个位置。然后把读取到的字符存放在当前位置开始的连续 n 个位置中,光标后移。再重新刷新屏幕,回显刚刚输入的字符。

同样的,在编辑模式下,也可以移动光标,所以在判断是不是普通字符之后,需要判断是否是移动光标的操作。

退出 vi,保存文件

在按下冒号:之后,用户输入特定的指定来保存文件和退出 vi。

进入 doColon 函数,完成后续的判断功能

先是利用 while 循环,得到用户在冒号之后的输入。然后对命令进行解析,分别有“q!”, “ q” , “wq”, “w”几种指令。判断用户输入的指令是哪一种之后执行对应的操作。如“q!”就是强制退出,不保存。 “wq”就是保存之后退出。我们利用 file_save 函数来执行保存文件内容的操作。

用户所有的输入,我们都保存在 text 内存空间中,所有在保存时,我们需要把这部分空间中的内容输入到文件中并保存。也是先打开文件,然后利用系统自带的 write 函数把 text 中的内容写入到文件对象中。

  • 功能键映射
  • 在上面的介绍中,我们已经提及了一部分功能键映射的实现,包括光标的上下左右的移动和一些插入模式的指令以及输入冒号之后不同的退出模式。
  • 我们还完成了其他的功能键的映射

四、翻页

  • 上述代码分别对应了向上翻页和向下翻页。
  • 向上翻页是,一个屏幕是显示 24 行的内容,我们向上翻页其实就是光标向上移动 24 行。利用一个 for 循环,不断的向上移动一行,直到到底最顶行或者已经移动了 24 行。
  • 向下翻页也是同样的道理,利用 for 循环光标向下移动 24 行。

五、删除

按下 x 键之后,调用 dot_delete 删除光标位置的字符,

在 dot_delete 函数中,是先调用 deleteText 函数来删除从开始位置到结尾的空间的,然后更新页面的。

而 deleteText 函数中,也是利用 memmove 函数,把 end+1 开始的字符移动到 start 开始的位置,覆盖原来的字符,实现删除的功能。

六、查找

同样的,在判断用户的输入的 case 中,匹配到用户输入了“/”之后,就得到光标位置,然后在最下面的一行写入用户输入的指令,同时利用 while 循环,去读取用户在/之后的输入,即要查找的内容,把他放在了一个数组 currentFindString 里面,同时利用 writeAt 回显在屏幕中。我们现在了存放用户要查找的内容的数组的长度为 32,所以最多只能查找 31 个字符。最后是把我们刚刚保存了用户输入的数组 currentFindString 拷贝到另一个数组 lastFindString 中,对数组 lastFindString 的内容进行查找,相当于对用户输入做了一个备份。上图这部分代码主要用于获取用户输入的查找内容,以及回显到屏幕中。

在上图中,都会判断用户输入的查找内容是否为 0,0 的话代表没有用户输入的内容,就不进行查找。如果用户输入不为 0 的话,就分别调用 findString 和 reverseFindString 进行查找。返回值都是 dot,是光标的地址指针。然后利用 synchronize 函数对光标位置进行同步显示,把光标设置到最新的位置。并且在底部输入查找的内容。

findString 函数的实现如上图所示。s 是我们传进去的要查找的内容,从 dot 所在的位置起(指针 p),利用 equal 函数,判断 p 和 s 所指的内容是否相同。如果返回值是 1,代表是相同的,证明找到了匹配的内容,p 所指向的位置就是我们找到的匹配的内容的首地址,把这个地址返回。其他代码是一些特殊情况的判断,比如没找到就输出“pattern not found”,并且返回原来光标的位置,光标不改变位置。

反向查找类似,只不过把上面的 p++,变成了 p–,查找在光标位置之前的内容。

七、高亮

关于高亮,我们目前是只实现了匹配.c 文件的词法高亮,其他文件的高亮还有待完成。
我们在 vi.c 的主函数中,先匹配下输入的是不是.c 文件,如果是,就设置 flagCfile 的值为 1。然后在进入 vi 的界面。

在 intoVi 函数中,我们通过判断 flagCfile 是否为 1,来显示不同的界面。

  • 在 reDrawC 中,主要是调用了 highlightText 函数来显示高亮。
  • 重点就是在 highlightText 函数来做一些 c 文件的匹配规则。
  • 先是开创了一个 ColorText 的新数组来存放带有字符的颜色。代码如下(部分)
    我们主要是匹配 3 种 c 的关键字,一种是#include,#define 这种,我们用蓝色来显示。一种是 int 、void 之后变量类型的声明的,我们用绿色来显示。还有一直是 while,if else 之后关键字,我们用黄色来显示。我们是利用一个 re.c 文件中的 re_compile 来做正则匹配的。对我们指定的一些关键字进行匹配之后,利用 setColorC 函数给匹配的字符设置颜色。代码如下。

然后包装了一个新的系统调用 Toscreen,来把我们得到的带有颜色的字符文本显示带屏幕上。

形参 color 对应的值就是我们刚刚应该正则匹配到的每个字符对应的颜色。然后通过 showC 设置每个字符的颜色,以及显示在屏幕中。

showC 这段代码是借鉴 xv6 源码的,只不过我们把 color 换成了每个字符对应的颜色,而不是固定的 0x0700。

到此,高亮的功能就算实现了。

八、显示状态栏

我们把屏幕最下面 25 行作为状态栏,来显示当前操作的类型(是插入还是命令还是错误的指令)以及来显示当前光标的位置。

代码如下

参数 s 是操作类型,由调用的地方传入,col 是对应的颜色,比如如果是错误的指令,就显示红色。也是利用 getCursorPos 获取光标的位置,然后转换为坐标,即第几行第几列的值,利用两个 while 循环,把他是几行几列转换为字符,存进数组中,最后存入模式状态。把一整个数组的值,显示到屏幕中。

九、具体效果

  • 实现新建文件,读入文件,修改文件,写入文件功能
  • 新建文件
  • 输入 vi mynew.txt 之后,qemu 的窗口清空,因为这是一个新的文件,所以窗口并没有值。

在退出登陆之后,可以看到原来屏幕中的内容

  • 输入 ls,可以看到在目录下生成了 mynew.txt 文件
  • 读入文件
  • 再次打开 mynew.txt 文件我们可以看到,上次我们编辑的内容保存了,并且在本次打开后,正确的显示在屏幕内了,证明我们实现了读入文件内容到内存以及显示的功能了。

修改文件和写入文件

在键盘中按下 i(或者 a,I,A)进入编辑模式之后,就可以对文件内容进行修改

我插入了以上内容,可以看到在最下面一行,显示 insert 代表是插入模式。按下 ESC 之后退出编辑模式,再键入:wq 就可以保存刚刚编辑的内容了。

输入 cat mynew.txt,可以看到刚刚编辑的内容确实是保存了,写入文件成功。

同样的,我们也可以删除一些内容,在下图中,我利用按键 x 删除其中一行的所有字符。

同样可以看到

删除之后的文件保存了,删除成功。

实现 VIM 或 Emacs 的基本功能键映射

十、插入模式,i,I,a,A 功能键映射

'i':
进入插入模式
'I':
dot移到行首再进入插入模式
'a':
进入追加模式
'A':
dot移到行尾再进入插入模式

比如,现在的光标在 hello 中 e 的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AVJQ0xIy-1655639490388)(https://www.writebug.com/myres/static/uploads/2022/6/19/bbeeabaa33b2bede6fca45653d659596.writebug)]

我按下 A 之后,光标移动到这行的行尾,进入编辑模式,底线显示 insert 表明可以插入

其他的插入模式的功能键映射类似,我就不一一介绍了。

十一、保存以及退出的功能键映射

  • 退出
  • q! 强制退出
  • 保存文件
  • wq 保存再退出
  • 这部分功能在第三部分程序设计中已经做了详细的展示,且比较难以直接通过截图展示功能,我就不在这介绍了,详细实现过程请看持程序设计中第四部分

十二、查找

在输入/之后,可以输入我们想要查找的内容,按下回车之后,如果找到,就会把光标移动到查找到的内容的第一个位置上,然后底线会显示我们查找的内容。

可以看到,光标都在我们查找的单词的第一个首字母下面。默认情况下是匹配第一个找到的单词,如果有多个匹配的单词,可以再输入 n,或者 N,移动到下一个匹配的单词上。n 是正向匹配,N 是反向匹配。

比如我们输入 /new ,会匹配到第一个一个 new ,光标位置在(2,1)
按下 n,则会正向匹配到下一个,即(2,5)的位置。再按下 N,则会再匹配回第一个,N 是反向查找匹配的。

输入 N 之后,匹配回上一个!

十三、翻页

我们利用键盘上的 pgup 键来向上翻页,pgdn 向下翻页。

我在文件里面增加了内容,使它分布在两页,如图所示。然后按下 pgup 和 pgdn 是可以在这两页中切换的。

十四、删除

我们是利用按键 x 来删除字符的。

比如上面的文本,我在命令模式下,按下 x,就可以删除光标位置的字符,而且在最下面一行会提示 delete。可以看到,我们确实是删除了一个字符。

根据打开文件后缀名的不同,实现不同的语法高亮功能(C/html/…)(加 20% 完成度);

我们打开了一个 c 文件,可以看到确实是匹配出了一些关键字。实现了高亮功能。

  • 但是还是存在一些缺陷,匹配规则不够完善,完成的不是非常好。
  • 实现光标动态移动(加 30% 完成度)
  • 我们首先了通过键盘的上下左右方向键来控制光标的移动。下图红色方框内的小横线就是光标闪烁的位置。

通过键盘移动光标,可以实现把光标移动到任意位置。

其他的零碎的部分

1)

我们在最下面一行,输出了一些指令

比如按下 i,进入编辑模式,会输出 Insert 的提示。

如果输出了错误的命令,我们会用红色的字体提示输入错误的字符
在命令模式下,只是黄色的提醒

光标位置

VIM 中也是有光标位置显示的,我们也是通过 getcurosPos 来得到光标的位置,把他显示在最后一行的行尾。


动。下图红色方框内的小横线就是光标闪烁的位置。

资源下载地址:https://download.csdn.net/download/sheziqiong/85697086
资源下载地址:https://download.csdn.net/download/sheziqiong/85697086

  • 8
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这是一个比较复杂的问题,我会尽可能给你一个较为全面的答案。 首先,文本编辑器的核心功能是对文本进行编辑,因此我们需要一个文本输入框,用于输入和显示文本。此外,我们还需要对文本进行存储和读取,因此需要一个文件管理器,用于保存和打开文本文件。 接下来,我们需要实现一些基本的文本编辑功能,包括: 1. 新建文本:创建一个新的文本文件,清空输入框的内容。 2. 打开文本:从文件管理器中选择一个文本文件,将文件内容读取到输入框中。 3. 保存文本:将输入框中的文本保存到当前打开的文本文件中。 4. 插入文本:在当前光标位置插入新的文本。 5. 删除文本:删除当前光标位置的文本。 6. 定位光标:移动光标到指定的位置。 7. 替换文本:将输入框中指定的文本替换为新的文本。 其中,插入、删除和替换文本的操作都需要考虑光标的位置和选中的文本范围。因此,我们需要实现一个光标控制器和一个文本选择器,用于操作和控制光标和选中文本。 此外,我们还可以考虑一些高级的功能,如撤销和重做、查找和替换、自动完成等。 最后,为了让用户更加方便地使用文本编辑器,我们可以添加一些界面元素,如菜单栏、工具栏、状态栏等,用于显示当前编辑器的状态和提供一些常用的操作。 以上是一个简单的文本编辑器的设计思路,具体实现细节还需要根据具体的需求进行调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shejizuopin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值