首页 > 其他分享 >如何用C写一个简易的基于终端的编辑器(2)

如何用C写一个简易的基于终端的编辑器(2)

时间:2024-08-15 19:28:10浏览次数:6  
标签:字符 简易 编辑器 终端 缓冲区 屏幕 就是 我们 光标

读取输入并返回自定义的键值

根据上一章的原始模式的介绍,我们知道终端读取键盘的输入和我们想象的并不一样

普通字符很正常,读到啥就是啥,但对于一些功能键,比如说组合键和组合键则稍有差异,终端读取这些键其实是读取多个字节,比如方向键其实就是由\x1b[A/B/C/D这三个字节组合而成的

原始字符

如果输入的是普通字符,我们直接返回对应的整数值就好了

<Ctrl>组合键

定义一个宏函数#define CTRL_KEY(k) ((k) & 0x1f),将字符k的高3位清0,就可以将对应的字符转化成控制序列了

比如说CTRL_KEY(q)其实就等于Ctrl+Q,Ctrl+A其实就是1,Ctrl+B其实就是2,以此类推,26个字符,就是1到26

因为控制序列 Ctrl+字母 的组合就只有26个,所以我们将字符k的高3位清0,只保留第5位就已经可以表示0-31了,甚至还超过了

<Esc>组合键

这个也叫作转义序列,一般来说开头两个字节就是 \x1b[,然后跟着字母或者数字就是键盘上对应的一些键了,比如说方向键、导航键等等

然后这些的键值我们是自定义的,向下面这样:

enum editorKey {
	ARROW_LEFT 	= 1000,
	ARROW_RIGHT,
	ARROW_UP,
	ARROW_DOWN,
	DEL_KEY,
	HOME_KEY,
	END_KEY,
	PAGE_UP, 
	PAGE_DOWN
};

只要不与键盘上的这些普通字符、控制序列冲突就没事了,其实就是大于127就好了,因为8位比特最多表示到127。我们这里设的大一点也没事

总结

读取的输入类型,主要就是这三类了,我们需要注意的就是根据终端读取到的输入(1个字节或两个字节或三个字节或以上)进行判断,然后返回对应的键值就好了

(普通字符直接返回对应的整数值,控制序列返回宏函数的值,转义序列返回我们自定义的枚举值)

处理输入,根据键值进行功能映射

首先这是存储编辑器信息的结构体

struct editorConfig { 				/* 存储编辑器的信息 */
	int cx, cy; 					/* 存储光标位置 */
	int screenrows; 				/* 存储屏幕行数 */
	int screencols; 				/* 存储屏幕列数 */
	struct termios orig_termios;    /* 存储终端原来的属性 */
};
extern struct editorConfig E;

光标移动

  • 光标位置的改变:
    在我们的编辑器中,所谓光标的移动其实就是光标的重新定位啦,我们根据 E.cxE.cy 的值在每次刷新屏幕的时候重新定位光标就可以啦

    所以我们只需根据按下的键来改编cx和cy两个变量的值,在重定位就能实现光标的移动啦

  • 光标移动限制
    我们的光标的坐标值是不能超过整个屏幕的范围的哦,除非将屏幕进行滑动,不过目前还没到这一步,所以我们要限制光标坐标的范围

然后对于那些导航键而言,其实就是多次的单位移动啦,比如说屏幕有20行,我们按下 Home 键就能将光标定位到最后一行,实现起来就是一个循环,只要光标没到达最后一行,就往下移动一格,相当于按了方向键中的下键一样

屏幕滚动

屏幕的滚动分为垂直和水平,要实现这个功能我们主要分两步走

  • 光标定位
    这一步是说,当我们进行滚动的时候,本质上是光标移动到屏幕之外,然后我们通过光标重新对屏幕进行校正,所以这个时候就需要两个变量 rowoff和coloff,分别记录行偏移量和列偏移量,简单来说就是记录屏幕顶部和屏幕左部具体是哪一行
  • 更新屏幕显示内容
    有了rowoff和coloff这两个变量后,我们就能在屏幕上正确的输出信息了,比如屏幕顶部就是 0+rowoff 行的内容,第2行就是 1+rowoff 行的内容等等

屏幕的滚动也是要设置限制的

  • 水平滚动的限制就是不超过当前行的最后一个字符的下一个字符,这个位置方便我们进行插入字符操作
    • 我们可以通过光标的纵坐标知道我们当前在哪一行,然后限制光标水平移动时不能超过当前行的字符数
      • 也可以在此基础上增加相邻行之间的跳转,比如当前行的末尾继续向右移动就跳转到下一行的开头,当前行的开头继续向左移动就跳转到上一行的末尾,注意正确处理边界限制
  • 垂直滚动的限制就是不超过最后一个可编辑行的下一行,这个位置方便我们进行插入新行操作
    • 我们可以增加一个变量用于 记录编辑器可编辑的行数,然后以这个作为界限垂直滚动就可以了

字符的删除与插入

插入和删除字符其实就是对行缓冲区中的内容进行调整,插入就是一部分内容向后移动,留出一个空位,删除就是一部分内容向前移动

进一步就是可以通过删除进行行的减少,比如在行的开头继续退格能够跳到上一行末尾,这个需要注意的就是要注意字符串的拼接

行的增减

首先我们需要注意的是行缓冲区的分配与释放,这对应着行的增加和删除

其次还需要注意字符串的拼接,因为我们不一定是直接把一行清空,还可能是在一行的中间某部分一直退格从而跳转到上一行

特殊功能

  • Ctrl+Q 退出编辑器,也就是退出程序
  • Ctrl+S 将编辑内容保存到磁盘,实现的关键是先将行缓冲区中的内容转变为单个字符串,然后通过write函数写到指定文件中

缓冲区

追加缓冲区

其实就是考虑到调用wirte的开销,事实上我们可以将大的内容交给write一次性刷新到屏幕上,而不是多次调用write,一次只写一点内容

然后就创建了一个追加缓冲区的结构体,其实就是动态数组、字符串啦,指针指向分配内存的地址,然后还有一个长度变量,但创建这个的原因还是在于 追加 的特性,因为编辑器总是会时不时地添加或者删去内容,所以这个缓冲区必须是动态的

实现也很简单,就一个添加函数和释放内存的函数,添加函数内就是用realloc函数动态的扩充缓冲区大小来实现追加特性

行缓冲区

其实和追加缓冲区很像,只不过存储的带你为是一行的内容,可以将理解为多个行缓冲区组合成追加缓冲区

刷新屏幕

就是将 追加缓冲区 里的内容 循环 write到屏幕上即可

屏幕渲染问题

首先我们要先区分 文本缓冲区渲染后的文本缓冲区 这两个概念

举个例子,比如说我在文本缓冲区中添加了\t这个制表符,那么它在其中也只是占用1个字节的位置,然后通过终端对其进行渲染,但这样的问题是编辑器对制表符的定义与可能与终端不符合,所以我们要自己进行渲染。那么在渲染后的文本缓冲区里,我们就能自主的将\t渲染成4个或8个空格这样

文本缓冲区是否渲染,对普通字符没什么影响,只不过增加个渲染后的文本缓冲区会更加符合我们的心意,让我们更加自主

除此之外,我们还需要 修复光标在处理制表符(tab)时的显示问题。当前,光标位置计算假设每个字符在屏幕上只占一个列位置,但制表符会占用多个列,也就是说当我们用方向键移动光标若是遇到制表符(未渲染),光标会直接跨过制表符(可能是8列,看终端如何解释),而不是向我们预想中的那样只移动一列

因此,需要引入一个新的水平坐标变量 E.rx 来解决这个问题,也就是说文本缓冲区和渲染后的文本缓冲区要用不同的水平坐标变量来定位,没有制表符时,两者相等,有制表符时,后者比前者大(因为我们将制表符渲染成空格,也就是相当于多了几个字节)

屏幕分区

  • 编辑区:显示的是输入或读取的内容
  • 状态栏:显示文本编辑过程中的一些状态,比如所在行的行号,文件名等等
  • 消息栏:编辑器通知用户的消息,比如文件已保存等等

对屏幕进行分区也很简单,最开始的时候,我们将获取到的窗口大小存储在两个变量中,E.screerows存储的是长,也就是行数,然后后续的一系列操作都是在这个窗口中进行的,当我们要分区的时候直接减少 E.screenrows 的值就行了,这就是减少编辑区的行数,将剩下的行交给状态栏和消息栏

分区后,相应的绘制函数也要定义哦,所以我们这里要定义三个绘制函数(最终都是输送到追加缓冲区中,然后在刷新到屏幕上),并将其放到刷新屏幕函数中执行,这样才能正确显示

屏幕闪烁问题,优化体验

  • 不频繁调用 write --->使用追加缓冲区
  • 刷新前将光标隐藏,刷新后再显示光标

刷新屏幕思路解析

  1. 计算当前rowoff和coloff的值,因为输出到屏幕上的内容取决于这两个界定的范围
  editorScroll();
  1. 创建一个追加缓冲区用于存储当前屏幕的输出内容
  struct abuf ab = ABUF_INIT; 	/* 构造一个空缓冲区 */
  1. 刷新屏幕前隐藏光标,为了流畅性
  abAppend(&ab, "\x1b[?25l", 6); 	/* 刷新屏幕前隐藏光标 */
				                    /* l命令是关闭终端特性 */
				                    /* 参数?25是光标控制码 */
  1. 将要输出的文本内容写入缓冲区中
  editorDrawRows(&ab);             /* 行 */
  editorDrawStatusBar(&ab);        /* 状态栏 */
  editorDrawMessageBar(&ab);       /* 消息栏 */
  1. 重新定位移动后的光标
  char buf[32]; 				    /* 存储写入终端的指令 */
  snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1);
  abAppend(&ab, buf, strlen(buf)); 	/* 定位光标 */
  1. 最后要添加到缓冲区的是,显示光标
  abAppend(&ab, "\x1b[?25h", 6); 	/* 刷新屏幕后显示光标 */
						            /* h命令是关闭终端特性 */
							        /* 参数?25是光标控制码 */
  1. 将缓冲区中的内容写到屏幕上
  write(STDOUT_FILENO, ab.b, ab.len); 	/* 这里才是真正的把波浪号画在屏幕上 */
  1. 释放缓冲区
  abFree(&ab);

刷新屏幕就是不断的删除之前内容,在显示当前内容,就一直不断地重复,不论你有没有进行操作

补充

向终端发送指令

我们可以将特定的转移序列写入标准输出,这可以向终端发送指令

获取窗口大小

  • 调用 带有 TIOCGWINSZ 请求的 ioctl()
  • 通过将光标移动到最右下角,然后询问终端,返回的值就是窗口的长和宽

最后一行的波浪线的绘制

我们若是循环 画一个波浪线然后换行 ,这会导致画到最后一行后还是换行,使得屏幕上滑,新的空行成为了最后一行,看起来就像是最后一行没画一样,所以我们要特殊处理最后一行,就是不添加换行

清屏转变为循环清除一行

刷新屏幕其实就是先清屏,然后在把内容重新绘制上去,而我们用循环地清除每行和清屏的效果是一样的,只是这样我们会更有选择性

标签:字符,简易,编辑器,终端,缓冲区,屏幕,就是,我们,光标
From: https://www.cnblogs.com/winter-z/p/18354008

相关文章

  • 使用微信小程序开发制作一个简易的在线问卷调查应用
    微信小程序是一种基于微信平台的应用程序,可以在微信中进行使用,无需下载安装即可使用。在本项目中,我们将使用微信小程序开发一个简易的在线问卷调查应用。界面设计首先,我们需要设计一个用户界面,用于显示问卷列表和调查结果。在小程序中,界面设计使用的是WXML和WXSS,类似于HTML和......
  • Unity编辑器批量设置图片格式
    在游戏开发中,经常需要批量设置图片的格式为Sprite类型,手动设置太麻烦,下面的编辑器脚本实现选中文件夹右键/Texture/SetAllImagesToSpriteType实现批量设置图片格式,具体格式参数可自行定义usingSystem;usingSystem.IO;usingUnityEngine;usingUnityEditor;///<summary>......
  • windows核心编程 第三章,跨越进程边界共享内核对象,对象句柄的继承性,改变句柄的标志,命名
    windows核心编程3.3跨越进程边界共享内核对象3.3.1对象句柄的继承性3.3.2改变句柄的标志3.3.3命名对象3.3.4终端服务器的名字空间3.3.5复制对象句柄文章目录windows核心编程3.3跨越进程边界共享内核对象3.3.1对象句柄的继承性3.3.2改变句柄的标志3.3.3命名......
  • 破防了!加班狗常用的TOP4 PDF编辑器2024年集锦
    我们天天都得跟一堆文件打交道,尤其是PDF文件。PDF的好处是,不管在哪个设备上打开,内容和格式都保持原样,而且不容易被改。不过,有时候这也让人挺头疼的,因为想改点什么就麻烦了。幸好,技术一直在进步,现在市面上有很多好用的PDF编辑器,它们就像魔法棒,让编辑文档变得轻松又快速。今天,我......
  • pbootcms去除ueditor编辑器图片自动添加的title和alt属性
    pbootcms后台使用的是百度ueditor编辑器,ueditor上传图片会自动添加title、alt属性,属性值为源图的文件名,pbootcms模板中title为图片上传后的一串日期数字名称,从SEO和用户体验角度来说都不好。我们如果想去掉这个属性要怎么操作呢?接下来准备改造成默认图片上传后只带alt="",一个空al......
  • 了解VSCode:一款功能强大的开源代码编辑器
    VisualStudioCode(简称VSCode)是由微软开发的一款免费、开源的源代码编辑器。它以其强大的功能、丰富的插件生态系统、跨平台兼容性以及出色的用户体验,成为了广大开发者的首选工具。以下是对VSCode的详细介绍,涵盖其特点、功能、安装与配置、以及扩展生态等方面。一、VSCode的......
  • DzzOffice 编辑器篇
    百度编辑器工具栏模式文件:dzz\system\ueditor\ueditor.config.js代码UEDITOR_CONFIG.mode={};内的表示不同模式显示不同的工具,其中full段表示完整的工具。工具代码'anchor':'锚点','undo':'撤销','redo':'重做','bold':'加粗',&......
  • 远程终端 FinalShell 下载安装配置
    今天给伙伴们分享一下远程终端FinalShell下载安装配置,希望看了有所收获。我是公众号「想吃西红柿」「云原生运维实战派」作者,对云原生运维感兴趣,也保持时刻学习,后续会分享工作中用到的运维技术,在运维的路上得到支持和共同进步!如果伙伴们看了文档觉得有用,欢迎大家关注我......
  • PDF编辑不求人!这三款免费版编辑器助你轻松搞定!
    作为一名办公室文员,每天和PDF文件打交道那是家常便饭。打印合同、整理报告、编辑资料,PDF文件简直就是我的工作小伙伴。不过,说起编辑PDF,那可真是个技术活。以前,我总是为这事儿头疼,直到遇见了几款pdf编辑器免费版,简直是让我大开眼界,工作效率也噌噌往上涨!1.福昕PDF编辑器网址:ht......
  • windows 终端美化
    效果图:1.安装windowsterminal打开windows自带的应用商店,安装windowsterminal1.1设置windowsterminal✨注意:以下设置完成后要点击”保存”1.1.1设置启动快捷方式将windowsterminal创建一个桌面快捷方式右键点开「属性」修改快捷方式为ctrl+alt+......