[Text_Editor项目]Raw mode

笔记1:Raw mode

自己写的代码Gitee链接(持续更新中):
https://gitee.com/lakmiu/text-editor
源地址链接:
https://viewsourcecode.org/snaptoken/kilo/index.html

零,基础功能

#include <unistd.h>
int main() {
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q');
  return 0;
}

STDIN_FILENO:标准输入的文件描述符,从键盘上获取数据。

一,关闭回显

#include <termios.h>
#include <unistd.h>
void enableRawMode() {
  struct termios raw;//存储终端的当前设置
  tcgetattr(STDIN_FILENO, &raw);//读取标准输入终端的属性存储到raw中
  raw.c_lflag &= ~(ECHO);//修改本地标志字段,关闭回显功能
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);//将修改后的传入标准输入终端
  //tcsaflush在更改属性前刷新所有的输入输出
}
int main() {
  enableRawMode();//进入原始模式
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q');
  return 0;
}

知识:

c_lflag是local flags

c_iflag (input flags),

c_oflag (output flags),

c_cflag (control flags)

ECHO bitflag 00000000000000000000000000001000

               &11111111111111111111111111110111

               =00000000000000000000000000000000         

tip:

1.离开了程序之后还是不显示,输入reset按enter

二,退回原始功能

#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
struct termios orig_termios;
void disableRawMode() {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}//将终端设置为一开始保存的设置
void enableRawMode() {
  tcgetattr(STDIN_FILENO, &orig_termios);
  atexit(disableRawMode);//在退出时执行这个disableRawMode函数
  // called automatically
  struct termios raw = orig_termios;
  raw.c_lflag &= ~(ECHO);
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);//设置
}
int main() {
  enableRawMode();
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q');
  return 0;
}

知识:

  1. atexit(disableRawMode) 可以确保在程序退出时,我们将保留终端属性。

三,关闭标准模式

更改逐行读入为逐字节读入,这样程序在读入到q的时候就会立即退出

#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
struct termios orig_termios;
void disableRawMode() {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}//将终端设置为一开始保存的设置
void enableRawMode() {
  tcgetattr(STDIN_FILENO, &orig_termios);
  atexit(disableRawMode);//在退出时执行这个disableRawMode函数
  // called automatically
  struct termios raw = orig_termios;
  raw.c_lflag &= ~(ECHO | ICANON);//
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);//设置
}
int main() {
  enableRawMode();
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q');
  return 0;
}

但是现在不显示什么东西

知识:

  1. C语言标准输入立即读取(ICANON)

raw.c_lflag &= ~(ECHO | ICANON);

四,展示键盘相应

先来展示一下ASCII码

  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
    if (iscntrl(c)) {
      printf("%d\n", c);
    } else {
      printf("%d ('%c')\n", c, c);
    }
  }

#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include<stdio.h>
#include<ctype.h>
struct termios orig_termios;
void disableRawMode() {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}//将终端设置为一开始保存的设置
void enableRawMode() {
  tcgetattr(STDIN_FILENO, &orig_termios);
  atexit(disableRawMode);//在退出时执行这个disableRawMode函数
  // called automatically
  struct termios raw = orig_termios;
  raw.c_lflag &= ~(ECHO | ICANON);//
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);//设置
}
int main() {
  enableRawMode();
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
    if (iscntrl(c)) {
      printf("%d\n", c);
    } else {
      printf("%d ('%c')\n", c, c);
    }
  }
  
  return 0;
}

知识:

https://www.runoob.com/w3cnote/ascii.html

ASCII控制字符的编号范围是0-31和127(0x00-0x1F和0x7F),共33个字符。

这里iscntrl()测试是不是控制字符

printf()可以打印很多但是

image

image

image

现象:

image

image

五,关闭ctrl -z -c -s -q -v 修复-M

关闭c z

raw.c_lflag &= ~(ECHO | ICANON | ISIG);

关闭 s q

raw.c_iflag &= ~(IXON);

有的操作系统键入ctrl -v的时候会wait for you to type another character and then sends that character literally.

raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

修复-M

raw.c_iflag &= ~(ICRNL | IXON);

image

It turns out that the terminal is helpfully translating any carriage returns (13, '\r') inputted by the user into newlines (10, '\n'). Let’s turn off this feature.

代码:

#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include<stdio.h>
#include<ctype.h>
struct termios orig_termios;
void disableRawMode() {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}
void enableRawMode() {
  tcgetattr(STDIN_FILENO, &orig_termios);
  atexit(disableRawMode);
  struct termios raw = orig_termios;
  raw.c_iflag &= ~(ICRNL | IXON);
  raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main() {
  enableRawMode();
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
    if (iscntrl(c)) {
      printf("%d\n", c);
    } else {
      printf("%d ('%c')\n", c, c);
    }
  }
  return 0;
}

六,关闭所有的输出处理

终端在输出端做了类似的转换。它将打印的每个换行符(“\n”)转换成后跟换行符(“\r\n”)的回车。终端需要这两个字符才能开始新的文本行。

我们将通过关闭post标志来关闭所有的输出处理功能。在实践中,“\n”到“\r\n”的转换可能是默认情况下打开的唯一输出处理功能。

raw.c_oflag &= ~(OPOST);

结果我们只看到光标向下移动该怎么办?在printf()中加入回车

  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
    if (iscntrl(c)) {
      printf("%d\r\n", c);
    } else {
      printf("%d ('%c')\r\n", c, c);
    }
  }

七,Miscellaneous flags

raw.c_cflag |= (CS8);
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

设置目标:

  • When BRKINT is turned on, a break condition will cause a SIGINT signal to be sent to the program, like pressing Ctrl-C.

  • INPCK enables parity checking, which doesn’t seem to apply to modern terminal emulators.

  • ISTRIP causes the 8th bit of each input byte to be stripped, meaning it will set it to 0. This is probably already turned off.

  • CS8 is not a flag, it is a bit mask with multiple bits, which we set using the bitwise-OR (|) operator unlike all the flags we are turning off. It sets the character size (CS) to 8 bits per byte. On my system, it’s already set that way.

八,a timeout for read()

We can set a timeout, so that read() returns if it doesn’t get any input for a certain amount of time.

  raw.c_cc[VMIN] = 0;
  raw.c_cc[VTIME] = 1;
  while (1) {
    char c = '\0';
    read(STDIN_FILENO, &c, 1);
    if (iscntrl(c)) {
      printf("%d\r\n", c);
    } else {
      printf("%d ('%c')\r\n", c, c);
    }
    if (c == 'q') break;
  }

VMIN值设置了read()可以返回之前所需的最小输入字节数。我们将其设置为0,这样只要有任何输入要读取,read()就会返回。

VTIME值设置read()返回前等待的最大时间。它的单位是十分之一秒,所以我们把它设为十分之一秒,即100毫秒。如果read()超时,它将返回0,这是有意义的,因为它的通常返回值是读取的字节数。

image

九,Error handling

现在已经充分进入raw mode,那么我们增加error handling

void die(const char *s) {
  perror(s);
  exit(1);
}

image

翻译:

大多数失败的C库函数将设置global errno变量来指示错误是什么。Perror()查看globa errno变量,并为它打印一条描述性错误消息。它还在打印错误消息之前打印给定的字符串,这意味着提供有关代码的哪一部分导致错误的上下文。

在打印出错误消息后,我们退出程序,退出状态为1,这表示失败(任何非零值也表示失败)。

让我们检查每个库调用是否失败,并在它们失败时调用die()。

void disableRawMode() {
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
    die("tcsetattr");
}

void enableRawMode() {
  if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) die("tcgetattr");
  atexit(disableRawMode);
  struct termios raw = orig_termios;
  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  raw.c_oflag &= ~(OPOST);
  raw.c_cflag |= (CS8);
  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  raw.c_cc[VMIN] = 0;
  raw.c_cc[VTIME] = 1;
  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr");
}

if (read(STDIN_FILENO, &c, 1) == -1 && errno != EAGAIN) die("read");

验证方法:

tcgetattr()函数用于获取终端的属性。当你的程序以文本文件或管道作为标准输入而不是终端时,tcgetattr()会失败。

1.要以文件作为标准输入运行程序,请运行以下命令:./kilo <kilo.c。这将把kilo.c文件的内容作为标准输入传递给程序。

2.要以管道作为标准输入运行程序,请运行以下命令:echo test | ./kilo。这将把"test"字符串通过管道传递给程序。

无论是使用文件还是管道作为标准输入,都会导致tcgetattr()返回类似的错误信息:“Inappropriate ioctl for device”。这意味着你正在尝试对一个不支持的设备执行ioctl操作。

image

十,Sections

split code into section

在这里呈现完整的代码、

/*** includes ***/
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include<stdio.h>
#include<ctype.h>
#include<errno.h>
/*** data ***/
struct termios orig_termios;
/*** terminal ***/
void die(const char *s) {
  perror(s);
  exit(1);
}
void disableRawMode() {
  if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
    die("tcsetattr");
}
void enableRawMode() {
  if(tcgetattr(STDIN_FILENO, &orig_termios) == -1) die("tcgetattr");
  atexit(disableRawMode);
  struct termios raw = orig_termios;
  raw.c_iflag &= ~(BRKINT | INPCK | ICRNL | ISTRIP |IXON);
  raw.c_lflag &= ~(OPOST);
  raw.c_cflag |=CS8;
  raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
  raw.c_cc[VMIN] = 0;
  raw.c_cc[VTIME] = 10;
  if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw)== -1) die("tcsetattr");
}
/*** init  ***/
int main() {
  enableRawMode();
  while(1)
  {
    char c='\0';
    if(read(STDIN_FILENO,&c,1) == -1 && errno !=EAGAIN) die("read");
    if (iscntrl(c)) {
      printf("%d\n\r", c);
    } else {
      printf("%d ('%c')\n\r", c, c);
    }
    if(c=='q') break;
  }
  return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值