Screen refreshing and arrow navigation is complete.
This commit is contained in:
@@ -4,16 +4,59 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/*** Define section ***/
|
||||||
|
|
||||||
|
// This mimics the Ctrl + whatever behavior, setting the
|
||||||
|
// 3 upper bits of the character pressed to 0.
|
||||||
|
#define CTRL_KEY(k) ((k) & 0x1f)
|
||||||
|
// Empty buffer
|
||||||
|
#define ABUF_INIT {NULL, 0}
|
||||||
|
// Version code
|
||||||
|
#define TTE_VERSION "0.0.1"
|
||||||
|
|
||||||
/*** Data section ***/
|
/*** Data section ***/
|
||||||
|
|
||||||
struct termios orig_termios;
|
struct editor_config {
|
||||||
|
int cursor_x;
|
||||||
|
int cursor_y;
|
||||||
|
int rows;
|
||||||
|
int cols;
|
||||||
|
struct termios orig_termios;
|
||||||
|
} ec;
|
||||||
|
|
||||||
|
// Having a dynamic buffer will allow us to write only one
|
||||||
|
// time once the screen is refreshing, instead of doing
|
||||||
|
// a lot of write's.
|
||||||
|
struct a_buf {
|
||||||
|
char* buf;
|
||||||
|
int len;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum editor_key {
|
||||||
|
ARROW_LEFT = 0x3e8, // 1000, large value out of range of a char.
|
||||||
|
ARROW_RIGHT,
|
||||||
|
ARROW_UP,
|
||||||
|
ARROW_DOWN,
|
||||||
|
PAGE_UP,
|
||||||
|
PAGE_DOWN,
|
||||||
|
HOME_KEY,
|
||||||
|
END_KEY,
|
||||||
|
DEL_KEY
|
||||||
|
};
|
||||||
|
|
||||||
|
/*** Declarations section ***/
|
||||||
|
|
||||||
|
void editorClearScreen();
|
||||||
|
|
||||||
/*** Terminal section ***/
|
/*** Terminal section ***/
|
||||||
|
|
||||||
void die(const char* s) {
|
void die(const char* s) {
|
||||||
|
editorClearScreen();
|
||||||
// perror looks for global errno variable and then prints
|
// perror looks for global errno variable and then prints
|
||||||
// a descriptive error mesage for it.
|
// a descriptive error mesage for it.
|
||||||
perror(s);
|
perror(s);
|
||||||
@@ -21,19 +64,19 @@ void die(const char* s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void disableRawMode() {
|
void disableRawMode() {
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ec.orig_termios) == -1)
|
||||||
die("Failed to disable raw mode");
|
die("Failed to disable raw mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
void enableRawMode() {
|
void enableRawMode() {
|
||||||
// Save original terminal state into orig_termios.
|
// Save original terminal state into orig_termios.
|
||||||
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
if (tcgetattr(STDIN_FILENO, &ec.orig_termios) == -1)
|
||||||
die("Failed to get current terminal state");
|
die("Failed to get current terminal state");
|
||||||
// At exit, restore the original state.
|
// At exit, restore the original state.
|
||||||
atexit(disableRawMode);
|
atexit(disableRawMode);
|
||||||
|
|
||||||
// Modify the original state to enter in raw mode.
|
// Modify the original state to enter in raw mode.
|
||||||
struct termios raw = orig_termios;
|
struct termios raw = ec.orig_termios;
|
||||||
// This disables Ctrl-M, Ctrl-S and Ctrl-Q commands.
|
// This disables Ctrl-M, Ctrl-S and Ctrl-Q commands.
|
||||||
// (BRKINT, INPCK and ISTRIP are not estrictly mandatory,
|
// (BRKINT, INPCK and ISTRIP are not estrictly mandatory,
|
||||||
// but it is recommended to turn them off in case any
|
// but it is recommended to turn them off in case any
|
||||||
@@ -63,24 +106,260 @@ void enableRawMode() {
|
|||||||
die("Failed to set raw mode");
|
die("Failed to set raw mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int editorReadKey() {
|
||||||
|
int nread;
|
||||||
|
char c;
|
||||||
|
while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
|
||||||
|
// Ignoring EAGAIN to make it work on Cygwin.
|
||||||
|
if (nread == -1 && errno != EAGAIN)
|
||||||
|
die("Error reading input");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check escape sequences, if first byte
|
||||||
|
// is an escape character then...
|
||||||
|
if (c == '\x1b') {
|
||||||
|
char seq[3];
|
||||||
|
|
||||||
|
if (read(STDIN_FILENO, &seq[0], 1) != 1 ||
|
||||||
|
read(STDIN_FILENO, &seq[1], 1) != 1)
|
||||||
|
return '\x1b';
|
||||||
|
|
||||||
|
if (seq[0] == '[') {
|
||||||
|
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||||
|
if (read(STDIN_FILENO, &seq[2], 1) != 1)
|
||||||
|
return '\x1b';
|
||||||
|
if (seq[2] == '~') {
|
||||||
|
switch (seq[1]) {
|
||||||
|
// Home and End keys may be sent in many ways depending on the OS
|
||||||
|
// \x1b[1~, \x1b[7~, \x1b[4~, \x1b[8~
|
||||||
|
case '1':
|
||||||
|
case '7':
|
||||||
|
return HOME_KEY;
|
||||||
|
case '4':
|
||||||
|
case '8':
|
||||||
|
return END_KEY;
|
||||||
|
// Del key is sent as \x1b[3~
|
||||||
|
case '3':
|
||||||
|
return DEL_KEY;
|
||||||
|
// Page Up and Page Down send '\x1b', '[', '5' or '6' and '~'.
|
||||||
|
case '5': return PAGE_UP;
|
||||||
|
case '6': return PAGE_DOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (seq[1]) {
|
||||||
|
// Arrow keys send multiple bytes starting with '\x1b', '[''
|
||||||
|
// and followed by an 'A', 'B', 'C' or 'D' depending on which
|
||||||
|
// arrow is pressed.
|
||||||
|
case 'A': return ARROW_UP;
|
||||||
|
case 'B': return ARROW_DOWN;
|
||||||
|
case 'C': return ARROW_RIGHT;
|
||||||
|
case 'D': return ARROW_LEFT;
|
||||||
|
// Home key can also be sent as \x1b[H
|
||||||
|
case 'H': return HOME_KEY;
|
||||||
|
// End key can also be sent as \x1b[F
|
||||||
|
case 'F': return END_KEY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (seq[0] == 'O') {
|
||||||
|
switch (seq[1]) {
|
||||||
|
// Yes, Home key can ALSO be sent as \x1bOH
|
||||||
|
case 'H': return HOME_KEY;
|
||||||
|
// And... End key as \x1bOF
|
||||||
|
case 'F': return END_KEY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '\x1b';
|
||||||
|
} else {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getWindowSize(int* rows, int* cols) {
|
||||||
|
struct winsize ws;
|
||||||
|
|
||||||
|
// Getting window size thanks to ioctl into the given
|
||||||
|
// winsize struct.
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
*cols = ws.ws_col;
|
||||||
|
*rows = ws.ws_row;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Append buffer section **/
|
||||||
|
|
||||||
|
void abufAppend(struct a_buf* ab, const char* s, int len) {
|
||||||
|
// Using realloc to get a block of free memory that is
|
||||||
|
// the size of the current string + the size of the string
|
||||||
|
// to be appended.
|
||||||
|
char* new = realloc(ab -> buf, ab -> len + len);
|
||||||
|
|
||||||
|
if (new == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Copying the string s at the end of the current data in
|
||||||
|
// the buffer.
|
||||||
|
memcpy(&new[ab -> len], s, len);
|
||||||
|
ab -> buf = new;
|
||||||
|
ab -> len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void abufFree(struct a_buf* ab) {
|
||||||
|
// Deallocating buffer.
|
||||||
|
free(ab -> buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Output section ***/
|
||||||
|
|
||||||
|
void editorDrawWelcomeMessage(struct a_buf* ab) {
|
||||||
|
char welcome[80];
|
||||||
|
// Using snprintf to truncate message in case the terminal
|
||||||
|
// is too tiny to handle the entire string.
|
||||||
|
int welcome_len = snprintf(welcome, sizeof(welcome),
|
||||||
|
"tte -- version %s", TTE_VERSION);
|
||||||
|
if (welcome_len > ec.cols)
|
||||||
|
welcome_len = ec.cols;
|
||||||
|
// Centering the message.
|
||||||
|
int padding = (ec.cols - welcome_len) / 2;
|
||||||
|
// Remember that everything != 0 is true.
|
||||||
|
if (padding) {
|
||||||
|
abufAppend(ab, "~", 1);
|
||||||
|
padding--;
|
||||||
|
}
|
||||||
|
while (padding--)
|
||||||
|
abufAppend(ab, " ", 1);
|
||||||
|
abufAppend(ab, welcome, welcome_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void editorDrawRows(struct a_buf* ab) {
|
||||||
|
int y;
|
||||||
|
for (y = 0; y < ec.rows; y++) {
|
||||||
|
if (y == ec.rows / 3)
|
||||||
|
editorDrawWelcomeMessage(ab);
|
||||||
|
else
|
||||||
|
abufAppend(ab, "~", 1);
|
||||||
|
|
||||||
|
// Redrawing each line instead of the whole screen.
|
||||||
|
abufAppend(ab, "\x1b[K", 3);
|
||||||
|
// Addind a new line only if we are not printing the
|
||||||
|
// last tilde.
|
||||||
|
if (y < ec.rows - 1)
|
||||||
|
abufAppend(ab, "\r\n", 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void editorRefreshScreen() {
|
||||||
|
struct a_buf ab = ABUF_INIT;
|
||||||
|
|
||||||
|
// Hiding the cursor while the screen is refreshing.
|
||||||
|
// See http://vt100.net/docs/vt100-ug/chapter3.html#S3.3.4
|
||||||
|
// for more info.
|
||||||
|
abufAppend(&ab, "\x1b[?25l", 6);
|
||||||
|
abufAppend(&ab, "\x1b[H", 3);
|
||||||
|
|
||||||
|
editorDrawRows(&ab);
|
||||||
|
|
||||||
|
// Moving the cursor where it should be.
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "\x1b[%d;%dH", ec.cursor_y + 1, ec.cursor_x + 1);
|
||||||
|
abufAppend(&ab, buf, strlen(buf));
|
||||||
|
|
||||||
|
// Showing again the cursor.
|
||||||
|
abufAppend(&ab, "\x1b[?25h", 6);
|
||||||
|
|
||||||
|
// Writing all content at once
|
||||||
|
write(STDOUT_FILENO, ab.buf, ab.len);
|
||||||
|
abufFree(&ab);
|
||||||
|
}
|
||||||
|
|
||||||
|
void editorClearScreen() {
|
||||||
|
// Writing 4 bytes out to the terminal:
|
||||||
|
// - (1 byte) \x1b : escape character
|
||||||
|
// - (3 bytes) [2J : Clears the entire screen, see
|
||||||
|
// http://vt100.net/docs/vt100-ug/chapter3.html#ED
|
||||||
|
// for more info.
|
||||||
|
write(STDOUT_FILENO, "\x1b[2J", 4);
|
||||||
|
// Writing 3 bytes to reposition the cursor back at
|
||||||
|
// the top-left corner, see
|
||||||
|
// http://vt100.net/docs/vt100-ug/chapter3.html#CUP
|
||||||
|
// for more info.
|
||||||
|
write(STDOUT_FILENO, "\x1b[H", 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*** Input section ***/
|
||||||
|
|
||||||
|
void editorMoveCursor(int key) {
|
||||||
|
switch (key) {
|
||||||
|
case ARROW_LEFT:
|
||||||
|
if (ec.cursor_x != 0)
|
||||||
|
ec.cursor_x--;
|
||||||
|
break;
|
||||||
|
case ARROW_RIGHT:
|
||||||
|
if (ec.cursor_x != ec.cols - 1)
|
||||||
|
ec.cursor_x++;
|
||||||
|
break;
|
||||||
|
case ARROW_UP:
|
||||||
|
if (ec.cursor_y != 0)
|
||||||
|
ec.cursor_y--;
|
||||||
|
break;
|
||||||
|
case ARROW_DOWN:
|
||||||
|
if (ec.cursor_y != ec.rows - 1)
|
||||||
|
ec.cursor_y++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void editorProcessKeypress() {
|
||||||
|
int c = editorReadKey();
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case CTRL_KEY('q'):
|
||||||
|
editorClearScreen();
|
||||||
|
exit(0);
|
||||||
|
break;
|
||||||
|
case ARROW_UP:
|
||||||
|
case ARROW_DOWN:
|
||||||
|
case ARROW_LEFT:
|
||||||
|
case ARROW_RIGHT:
|
||||||
|
editorMoveCursor(c);
|
||||||
|
break;
|
||||||
|
case PAGE_UP:
|
||||||
|
case PAGE_DOWN:
|
||||||
|
{ // You can't declare variables directly inside a switch statement.
|
||||||
|
int times = ec.rows;
|
||||||
|
while (times--)
|
||||||
|
editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HOME_KEY:
|
||||||
|
ec.cursor_x = 0;
|
||||||
|
break;
|
||||||
|
case END_KEY:
|
||||||
|
ec.cursor_x = ec.cols - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*** Init section ***/
|
/*** Init section ***/
|
||||||
|
|
||||||
|
void initEditor() {
|
||||||
|
ec.cursor_x = 0;
|
||||||
|
ec.cursor_y = 0;
|
||||||
|
|
||||||
|
if (getWindowSize(&ec.rows, &ec.cols) == -1)
|
||||||
|
die("Failed to get window size");
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
enableRawMode();
|
enableRawMode();
|
||||||
|
initEditor();
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
char c = '\0';
|
editorRefreshScreen();
|
||||||
// Ignoring EAGAIN to make it work on Cygwin.
|
editorProcessKeypress();
|
||||||
if (read(STDIN_FILENO, &c, 1) == -1 && errno != EAGAIN)
|
|
||||||
die("Error reading input");
|
|
||||||
|
|
||||||
if (isprint(c))
|
|
||||||
printf("%d ('%c')\r\n", c, c);
|
|
||||||
else
|
|
||||||
printf("%d\n\r", c);
|
|
||||||
|
|
||||||
if (c == 'q')
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user