Skip to content

Instantly share code, notes, and snippets.

@maksimKorzh
Last active December 21, 2018 09:48
Show Gist options
  • Save maksimKorzh/4f68182fdbe0f704a9984a5b1b76a7dd to your computer and use it in GitHub Desktop.
Save maksimKorzh/4f68182fdbe0f704a9984a5b1b76a7dd to your computer and use it in GitHub Desktop.
/*********************************************************************************\
;---------------------------------------------------------------------------------;
; 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