Last active
December 21, 2018 09:48
-
-
Save maksimKorzh/4f68182fdbe0f704a9984a5b1b76a7dd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; C STANDARD LIBRARIES ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
#include <termios.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <stdio.h> | |
#include <ctype.h> | |
#include <errno.h> | |
#include <sys/ioctl.h> | |
#include <string.h> | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; DEFS ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
#define VERSION "0.0.1" | |
#define CTRL_KEY(key) ((key) & 0x1F) | |
enum keys | |
{ | |
LEFT_ARROW = 1000, | |
RIGHT_ARROW, | |
UP_ARROW, | |
DOWN_ARROW, | |
DEL, | |
HOME, | |
END, | |
PAGE_UP, | |
PAGE_DOWN | |
}; | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; DATA ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
typedef struct | |
{ | |
int size; | |
char *chars; | |
} | |
Line; | |
struct Environment | |
{ | |
int curX; | |
int curY; | |
int rows; | |
int cols; | |
int numLines; | |
Line *line; | |
struct termios canonical; // create terminal | |
}; | |
struct Environment env; | |
// append buffer | |
struct Buffer | |
{ | |
char *append; | |
int len; | |
}; | |
#define ABUF_INIT {NULL, 0} | |
void append_buffer(struct Buffer *buf, const char *str, int len) | |
{ | |
char *new = realloc(buf->append, buf->len + len); | |
if(new == NULL) return; | |
memcpy(&new[buf->len], str, len); | |
buf->append = new; | |
buf->len += len; | |
} | |
void free_buffer(struct Buffer *buf) | |
{ | |
free(buf->append); | |
} | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; TERMINAL ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
void draw_rows(struct Buffer *buf) | |
{ | |
for(int row = 0; row < env.rows; row++) | |
{ | |
if(row >= env.numLines) | |
{ | |
if(!env.numLines && row == env.rows / 3) | |
{ | |
char welcome[80]; | |
int welcomeLen = snprintf(welcome, sizeof(welcome), "source CODE editor v%s", VERSION); | |
if(welcomeLen > env.cols) welcomeLen = env.cols; | |
int padding = (env.cols - welcomeLen) / 2; | |
if(padding) | |
{ | |
append_buffer(buf, "~", 1); | |
padding--; | |
} | |
while(padding--) append_buffer(buf, " ", 1); | |
append_buffer(buf, welcome, welcomeLen); | |
} | |
else append_buffer(buf, "~", 1); | |
append_buffer(buf, "\x1b[K", 3); // refresh single line | |
if(row < env.rows - 1) | |
{ | |
append_buffer(buf, "\r\n", 2); | |
} | |
} | |
else | |
{ | |
int len = env.line[row].size; | |
if(len > env.cols) len = env.cols; | |
append_buffer(buf, env.line[row].chars, len); | |
} | |
} | |
} | |
void refresh_screen() | |
{ | |
struct Buffer buf = ABUF_INIT; | |
append_buffer(&buf, "\x1b[?25l", 6); // hide cursor | |
append_buffer(&buf, "\x1b[H", 3); // put cursor in top left corner | |
draw_rows(&buf); | |
char cursor[32]; | |
snprintf(cursor, sizeof(cursor), "\x1b[%d;%dH", env.curY + 1, env.curX + 1); | |
append_buffer(&buf, cursor, strlen(cursor)); | |
append_buffer(&buf, "\x1b[?25h", 6); // show cursor | |
write(STDOUT_FILENO, buf.append, buf.len); | |
free_buffer(&buf); | |
} | |
void die(const char *s) | |
{ | |
//refresh_screen(); | |
write(STDOUT_FILENO, "\x1b[2J", 4); // clear the screen | |
write(STDOUT_FILENO, "\x1b[H", 3); // put cursor in top left corner | |
perror(s); | |
exit(1); | |
} | |
void disableRawMode() | |
{ | |
if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &env.canonical) == -1) // restore terminal settings | |
die("tcsetattr"); | |
} | |
void enableRawMode() | |
{ | |
if(tcgetattr(STDIN_FILENO, &env.canonical)) // get initial terminal state | |
die("tcgetattr"); | |
atexit(disableRawMode); // call on exit | |
struct termios raw = env.canonical; // copy initial state | |
raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP | ICRNL | IXON); // disable CTRL-S, CTRL-M and CTRL-Q | |
raw.c_oflag &= ~(OPOST); // disable all output processing | |
raw.c_cflag |= (CS8); | |
// turn off echoing, read input by byte, disable CTRL-C, CTRL-V and CTRL-Z | |
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | |
raw.c_cc[VMIN] = 0; // minimal bytes to read | |
raw.c_cc[VTIME] = 1; // 100ms timeout | |
if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) // apply changes | |
die("tcsetattr"); | |
} | |
int read_key_press() | |
{ | |
int nread; | |
char key; | |
while((nread = read(STDIN_FILENO, &key, 1)) != 1) | |
{ | |
if(nread == -1 && errno != EAGAIN) | |
die("read"); | |
} | |
if(key == '\x1b') | |
{ | |
char escSeq[3]; | |
if(read(STDIN_FILENO, &escSeq[0], 1) != 1) return '\x1b'; | |
if(read(STDIN_FILENO, &escSeq[1], 1) != 1) return '\x1b'; | |
if(escSeq[0] == '[') | |
{ | |
if(escSeq[1] >= '0' && escSeq[1] <= '9') | |
{ | |
if(read(STDIN_FILENO, &escSeq[2], 1) != 1) return '\x1b'; | |
if(escSeq[2] == '~') | |
{ | |
switch(escSeq[1]) | |
{ | |
case '1': return HOME; | |
case '3': return DEL; | |
case '4': return END; | |
case '5': return PAGE_UP; | |
case '6': return PAGE_DOWN; | |
case '7': return HOME; | |
case '8': return END; | |
} | |
} | |
} | |
else | |
{ | |
switch(escSeq[1]) | |
{ | |
case 'A': return UP_ARROW; | |
case 'B': return DOWN_ARROW; | |
case 'C': return RIGHT_ARROW; | |
case 'D': return LEFT_ARROW; | |
case 'H': return HOME; | |
case 'F': return END; | |
} | |
} | |
} | |
else if(escSeq[0] == 'O') | |
{ | |
switch(escSeq[1]) | |
{ | |
case 'H': return HOME; | |
case 'F': return END; | |
} | |
} | |
return '\x1b'; | |
} | |
else return key; | |
} | |
int get_cursor_position(int *rows, int *cols) | |
{ | |
char buf[32]; | |
unsigned int i = 0; | |
if(write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; // cursor pos escape sequence | |
while(i < sizeof(buf) - 1) | |
{ | |
if(read(STDIN_FILENO, &buf[i], 1) != 1) break; | |
if(buf[i] == 'R') break; | |
i++; | |
} | |
buf[i] = '\0'; | |
if(buf[0] != '\x1b' || buf[1] != '[') return -1; | |
if(sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1; | |
return 0; | |
} | |
int get_terminal_size(int *rows, int *cols) | |
{ | |
struct winsize ws; | |
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) | |
{ | |
if(write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1; | |
return get_cursor_position(rows, cols); | |
} | |
else | |
{ | |
*cols = ws.ws_col; | |
*rows = ws.ws_row; | |
return 0; | |
} | |
} | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; INPUT ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
void move_cursor(int key) | |
{ | |
switch(key) | |
{ | |
case LEFT_ARROW: | |
if(env.curX != 0) env.curX--; | |
break; | |
case RIGHT_ARROW: | |
if(env.curX != env.cols - 1) env.curX++; | |
break; | |
case UP_ARROW: | |
if(env.curY != 0) env.curY--; | |
break; | |
case DOWN_ARROW: | |
if(env.curY != env.rows - 1) env.curY++; | |
break; | |
} | |
} | |
void process_key_press() | |
{ | |
int key = read_key_press(); | |
switch(key) | |
{ | |
case CTRL_KEY('q'): | |
//refresh_screen(); | |
write(STDOUT_FILENO, "\x1b[2J", 4); // clear the screen | |
write(STDOUT_FILENO, "\x1b[H", 3); // put cursor in top left corner | |
exit(0); | |
break; | |
case HOME: | |
env.curX = 0; | |
break; | |
case END: | |
env.curX = env.cols - 1; | |
break; | |
case PAGE_UP: | |
case PAGE_DOWN: | |
{ | |
int times = env.rows; | |
while(times--) | |
move_cursor(key == PAGE_UP ? UP_ARROW : DOWN_ARROW); | |
break; | |
} | |
case UP_ARROW: | |
case DOWN_ARROW: | |
case LEFT_ARROW: | |
case RIGHT_ARROW: | |
move_cursor(key); | |
break; | |
} | |
} | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; LINE OPERATIONS ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
void append_line(char *s, size_t len) | |
{ | |
env.line = realloc(env.line, sizeof(Line) * (env.numLines + 1)); | |
int at = env.numLines; | |
env.line[at].size = len; | |
env.line[at].chars = malloc(len + 1); | |
memcpy(env.line[at].chars, s, len); | |
env.line[at].chars[len] = '\0'; | |
env.numLines++; | |
} | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; FILE I/O ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
void file_open(char *filename) | |
{ | |
FILE *file = fopen(filename, "r"); | |
if(!file) die("fopen"); | |
char *line = NULL; | |
size_t linecap = 0; | |
ssize_t lineLen; | |
while((lineLen = getline(&line, &linecap, file)) != -1) | |
{ | |
while(lineLen > 0 && | |
(line[lineLen - 1] == '\n' || | |
line[lineLen - 1] == '\r')) | |
lineLen--; | |
append_line(line, lineLen); | |
} | |
free(line); | |
fclose(file); | |
} | |
/*********************************************************************************\ | |
;---------------------------------------------------------------------------------; | |
; MAIN ; | |
;---------------------------------------------------------------------------------; | |
\*********************************************************************************/ | |
void init() | |
{ | |
env.curX = 0; | |
env.curY = 0; | |
env.numLines = 0; | |
env.line = NULL; | |
if(get_terminal_size(&env.rows, &env.cols) == -1) | |
die("get_terminal_size"); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
enableRawMode(); | |
init(); | |
if(argc >= 2) | |
file_open(argv[1]); | |
while(1) | |
{ | |
refresh_screen(); | |
process_key_press(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment