Screen refreshing and arrow navigation is complete.

This commit is contained in:
GrenderG
2017-04-06 03:35:05 +02:00
parent c330d1a3d7
commit ce1bf87767
+295 -16
View File
@@ -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;