├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── author ├── inc └── minivim.h └── src ├── append_buff.c ├── editor_ops.c ├── file_io.c ├── find.c ├── init.c ├── input.c ├── main.c ├── output.c ├── row_ops.c ├── syntax_hl.c └── terminal.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | 50 | # MAC OS files 51 | .DS_Store 52 | ._.DS_Store 53 | **/.DS_Store 54 | **/._.DS_Store 55 | 56 | # VS code files 57 | .vscode 58 | **/.vscode 59 | 60 | # cache 61 | .cache 62 | **/.cache 63 | 64 | # bear 65 | compile_commands.json 66 | **/compile_commands.json 67 | 68 | # Miscellaneous 69 | modules.order 70 | Module.symvers 71 | Mkfile.old 72 | dkms.conf 73 | 74 | # Program name 75 | minivim 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, izenynn 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ###################################################################### 2 | # VARS # 3 | ###################################################################### 4 | 5 | TAB_SIZE = 4 6 | 7 | UNAME_S := $(shell uname -s) 8 | 9 | ###################################################################### 10 | # NAME # 11 | ###################################################################### 12 | 13 | NAME = minivim 14 | #NAME = mvm 15 | 16 | BIN_DIR ?= /usr/local/bin 17 | 18 | ###################################################################### 19 | # COMPILER # 20 | ###################################################################### 21 | 22 | CC = gcc 23 | 24 | # flags 25 | CFLAGS += -D TAB_SIZE=$(TAB_SIZE) 26 | 27 | CURSOR_HL ?= 1 28 | CFLAGS += -D CURSOR_HL=$(CURSOR_HL) 29 | 30 | ###################################################################### 31 | # LIBS # 32 | ###################################################################### 33 | 34 | CFLAGS += -I ./inc 35 | 36 | #LDFLAGS = -L ./inc 37 | 38 | # ncurses will be probably implemented in the future, right now only supports VT100 escapes 39 | #LDLIBS = -lncurses 40 | 41 | ###################################################################### 42 | # RM # 43 | ###################################################################### 44 | 45 | RM = rm 46 | 47 | RMFLAGS = -rf 48 | 49 | ###################################################################### 50 | # PATHS # 51 | ###################################################################### 52 | 53 | SRC_PATH = src 54 | 55 | OBJ_PATH = obj 56 | 57 | ###################################################################### 58 | # SRC # 59 | ###################################################################### 60 | 61 | SRC_FILES = main.c init.c input.c \ 62 | output.c append_buff.c find.c \ 63 | file_io.c editor_ops.c row_ops.c \ 64 | syntax_hl.c terminal.c 65 | 66 | OBJ_FILES = $(SRC_FILES:%.c=%.o) 67 | 68 | SRC = $(addprefix $(SRC_PATH)/, $(SRC_FILES)) 69 | OBJ = $(addprefix $(OBJ_PATH)/, $(OBJ_FILES)) 70 | 71 | ###################################################################### 72 | # RULES # 73 | ###################################################################### 74 | 75 | .PHONY: all dev clean fclean re 76 | 77 | all: $(NAME) 78 | 79 | install: $(NAME) 80 | install $(NAME) $(BIN_DIR) 81 | 82 | $(NAME): $(OBJ) 83 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(LDLIBS) 84 | 85 | ifeq ($(UNAME_S),Linux) 86 | sanitize: CFLAGS += -pedantic -fsanitize=address -fsanitize=leak -fsanitize=undefined -fsanitize=bounds -fsanitize=null -g3 87 | endif 88 | ifeq ($(UNAME_S),Darwin) 89 | sanitize: CFLAGS += -pedantic -fsanitize=address -g3 90 | endif 91 | sanitize: $(NAME) 92 | 93 | $(OBJ_PATH)/%.o: $(SRC_PATH)/%.c | $(OBJ_PATH) 94 | $(CC) $(CFLAGS) -c $< -o $@ 95 | 96 | $(OBJ_PATH): 97 | mkdir -p $(OBJ_PATH) 2> /dev/null 98 | 99 | clean: 100 | $(RM) $(RMFLAGS) $(OBJ_PATH) 101 | 102 | fclean: clean 103 | $(RM) $(RMFLAGS) $(NAME) 104 | 105 | re: fclean all 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minivim 2 | 3 | ## info 4 | 5 | A "mini" implementation of vim :3 6 | 7 | It uses `VT100` escape characters (I will implement `ncurses` in the future probably). 8 | 9 | P.S. I know they are way too much comments, sorry for that. I do this project for learning purpouses, so I comment everything I do for that reason ":D 10 | 11 | ## How to use 12 | 13 | Clone the repo 14 | 15 | ```sh 16 | git clone https://github.com/izenynn/minivim.git 17 | ``` 18 | 19 | Run make inside the repo 20 | 21 | ```sh 22 | cd ./minivim && make 23 | ``` 24 | 25 | Open a file using minivim (it will create the file if it does not exists) 26 | 27 | ```sh 28 | ./minivim [FILE] 29 | ``` 30 | 31 | - or create a file with no name, and name it later with vim command `:saveas` 32 | 33 | ```sh 34 | ./minivim 35 | ``` 36 | 37 | *NOTE: if cursor highlighting is not working, that is probably becouse your terminal is reversing the cursor position color too, so it goes back to normal, to fix this, compile again with the variable CURSOR_HL=0 (disabled).* 38 | 39 | ```sh 40 | make re CURSOR_HL=0 41 | ``` 42 | 43 | ## How to install 44 | 45 | If you want to add minivim to your path and be able to use it in any directory like any other command, run `make install` 46 | 47 | ```sh 48 | make install 49 | ``` 50 | 51 | In case it gives you permissions error, try running it with `sudo` 52 | 53 | ```sh 54 | sudo make install 55 | ``` 56 | 57 | *NOTE: if you are having the issue I described before with the cursor highlighting, you will need to also install with CURSOR_HL=0.* 58 | 59 | ```sh 60 | sudo make install CURSOR_HL=0 61 | ``` 62 | 63 | *NOTE: to change the directory in which the binary is installed, you can compile with BIN_DIR="/usr/local" (just an example).* 64 | 65 | ```sh 66 | sudo make install BIN_DIR="/usr/local/bin" 67 | ``` 68 | 69 | ## Features 70 | 71 | Editor features: 72 | - Open, edit and save any text files. 73 | - C and C++ syntax highlighting. 74 | 75 | Vim features: 76 | - `normal` and `insert` mode. 77 | - `i`, `a`: change to insert mode. 78 | - `o`, `O`: insert new line. 79 | - `h`, `j`, `k`, `l`: move around (also: arrows). 80 | - `0`: move to first character in the line (also: home key). 81 | - `^`: move to first non-blank character in the line. 82 | - `$`: move to last character in the line (also: end key). 83 | - `gg`: goto first line. 84 | - `G`: goto last line. 85 | - `:w`, `:q`, `:q!`, `:wq`, `x`: supported commands. 86 | - `:saveas [NAME]`: supported command. 87 | - `/[MATCH]`: supported command (`n` / `N`: move to next / previous occurrence). 88 | 89 | ## 90 | [![forthebadge](https://forthebadge.com/images/badges/made-with-c.svg)](https://forthebadge.com) 91 | [![forthebadge](https://forthebadge.com/images/badges/you-didnt-ask-for-this.svg)](https://forthebadge.com) 92 | -------------------------------------------------------------------------------- /author: -------------------------------------------------------------------------------- 1 | izenynn 2 | -------------------------------------------------------------------------------- /inc/minivim.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIVIM_H 2 | # define MINIVIM_H 3 | 4 | /*** special defines ***/ 5 | 6 | # define _DEFAULT_SOURCE 7 | # define _BSD_SOURCE 8 | # define _GNU_SOURCE 9 | 10 | /*** includes ***/ 11 | 12 | # include 13 | # include 14 | # include 15 | # include 16 | # include 17 | # include 18 | # include 19 | # include 20 | # include 21 | # include 22 | # include 23 | 24 | /*** defines ***/ 25 | 26 | # define MINIVIM_VER "0.1" 27 | 28 | # ifndef TAB_SIZE 29 | # define TAB_SIZE 4 30 | # endif 31 | 32 | # ifndef CURSOR_HL 33 | # define CURSOR_HL 1 34 | # endif 35 | 36 | # define CTRL_KEY(k) ((k) & 0x1f) 37 | # define APBUFF_INIT {NULL, 0} 38 | 39 | # define NORMAL_MODE 0 40 | # define INSERT_MODE 1 41 | 42 | # define C_HL_EXT_SIZE 6 43 | # define C_HL_KEYW_SIZE 82 44 | # define HLDB_SIZE 1 45 | 46 | # define HL_HL_NBR (1<<0) 47 | # define HL_HL_STR (1<<1) 48 | 49 | /*** enums ***/ 50 | 51 | /* keys code enum */ 52 | enum editor_key { 53 | K_BACKSPACE = 127, 54 | K_ARROW_UP = 1000, 55 | K_ARROW_DOWN, 56 | K_ARROW_LEFT, 57 | K_ARROW_RIGHT, 58 | K_DEL, 59 | K_HOME, 60 | K_END, 61 | K_PAGE_UP, 62 | K_PAGE_DOWN 63 | }; 64 | 65 | /* highlight */ 66 | enum editor_hl { 67 | HL_NORMAL = 0, 68 | HL_COMMENT, 69 | HL_MLCOMMENT, 70 | HL_KEYWORD1, 71 | HL_KEYWORD2, 72 | HL_STRING, 73 | HL_NUMBER, 74 | HL_MATCH 75 | }; 76 | 77 | /*** data ***/ 78 | 79 | /* editor syntax data struct */ 80 | struct e_syntax { 81 | char *f_type; 82 | char **f_match; 83 | char **keywords; 84 | char *oneline_comment; 85 | char *ml_comment_st; 86 | char *ml_comment_end; 87 | int flags; 88 | }; 89 | 90 | /* editor row struct */ 91 | typedef struct e_row { 92 | int idx; 93 | int sz; 94 | int r_sz; 95 | char *line; 96 | char *rend; 97 | unsigned char *hl; 98 | int hl_open_comment; 99 | } e_row; 100 | 101 | /* editor config struct */ 102 | struct editor_conf { 103 | int cx, cy; 104 | int rx; 105 | int y_off; 106 | int x_off; 107 | int scrn_rows; 108 | int scrn_cols; 109 | int n_rows; 110 | e_row *row; 111 | int dirty; 112 | char *filename; 113 | char status_msg[80]; 114 | /* 0: normal, 1: insert */ 115 | int mode; 116 | struct e_syntax *syntax; 117 | struct termios org_termios; 118 | }; 119 | 120 | /* append buff struct */ 121 | struct apbuff { 122 | char *buff; 123 | size_t len; 124 | }; 125 | 126 | /* editor_conf global var */ 127 | extern struct editor_conf g_e; 128 | 129 | /* init.c */ 130 | void init_editor(); 131 | 132 | /* input.c */ 133 | char *editor_prompt(char *prompt, void (*callback)(char *, int)); 134 | void editor_move_cursor(int key); 135 | void editor_process_keypress(); 136 | 137 | /* output.c */ 138 | void editor_scroll(); 139 | void editor_draw_rows(struct apbuff *ab); 140 | void editor_draw_status_bar(struct apbuff *ab); 141 | void editor_draw_msg_bar(struct apbuff *ab); 142 | void editor_refresh_screen(); 143 | void editor_set_status_msg(const char *format, ...); 144 | 145 | /* append_buff.c */ 146 | void apbuff_append(struct apbuff *ab, const char *s, int len); 147 | void apbuff_free(struct apbuff *ab); 148 | 149 | /* find.c */ 150 | void editor_find_callback(char *query, int n); 151 | void editor_find(); 152 | 153 | /* file_io.c */ 154 | char *editor_rows_to_str(int *buff_l); 155 | void editor_open(const char *filename); 156 | void editor_save(); 157 | 158 | /* editor_ops.c */ 159 | void editor_insert_char(int c); 160 | void editor_insert_nl(); 161 | void editor_del_char(); 162 | 163 | /* row_ops.c */ 164 | int editor_row_cx_to_rx(e_row *row, int cx); 165 | int editor_row_rx_to_cx(e_row *row, int rx); 166 | void editor_update_row(e_row *row); 167 | void editor_insert_row(int idx, char *s, size_t len); 168 | void editor_free_row(e_row *row); 169 | void editor_del_row(int idx); 170 | void editor_row_insert_char(e_row *row, int idx, int c); 171 | void editor_row_append_str(e_row *row, char *s, size_t len); 172 | void editor_row_del_char(e_row *row, int idx); 173 | 174 | /* syntax_hl.c */ 175 | int is_separator(int c); 176 | void editor_update_syntax(e_row *row); 177 | int editor_syntax_to_color(int hl); 178 | void editor_select_syntax_hl(); 179 | 180 | /* terminal.c */ 181 | void die(const char *s); 182 | void dis_raw_mode(); 183 | void enb_raw_mode(); 184 | int editor_read_key(); 185 | int get_cursor_pos(int *rows, int *cols); 186 | int get_windows_size(int *rows, int *cols); 187 | 188 | #endif 189 | -------------------------------------------------------------------------------- /src/append_buff.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* append new string to struct apbuff */ 4 | void apbuff_append(struct apbuff *ab, const char *s, int len) { 5 | char *new; 6 | 7 | /* realloc out to make sure it can store the string we will append */ 8 | new = (char *)realloc(ab->buff, ab->len + len); 9 | if (!new) 10 | return; 11 | /* append string s to the new one */ 12 | memcpy(&new[ab->len], s, len); 13 | /* change ab->buff to the new string and update len */ 14 | ab->buff = new; 15 | ab->len += len; 16 | } 17 | 18 | /* free an apbuff struct */ 19 | void apbuff_free(struct apbuff *ab) { 20 | free(ab->buff); 21 | } 22 | -------------------------------------------------------------------------------- /src/editor_ops.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* insert char */ 4 | void editor_insert_char(int c) { 5 | /* check if we are in the tilde line to add a new row */ 6 | if (g_e.cy == g_e.n_rows) 7 | editor_insert_row(g_e.n_rows, "", 0); 8 | /* insert char on the row so we see it */ 9 | editor_row_insert_char(&g_e.row[g_e.cy], g_e.cx, c); 10 | /* move cursor */ 11 | g_e.cx++; 12 | } 13 | 14 | /* insert new line */ 15 | void editor_insert_nl() { 16 | /* if we are at the start of a row just insert an empty one */ 17 | if (g_e.cx == 0) { 18 | editor_insert_row(g_e.cy, "", 0); 19 | /* else we will need to split the line we are on into two rows */ 20 | } else { 21 | e_row *row = &g_e.row[g_e.cy]; 22 | editor_insert_row(g_e.cy + 1, &row->line[g_e.cx], row->sz - g_e.cx); 23 | row = &g_e.row[g_e.cy]; 24 | row->sz = g_e.cx; 25 | row->line[row->sz] = '\0'; 26 | editor_update_row(row); 27 | } 28 | /* move cursor to the new line */ 29 | g_e.cy++; 30 | g_e.cx = 0; 31 | } 32 | 33 | /* delete char */ 34 | void editor_del_char() { 35 | /* if we are past the end of the file there is nothing to delete */ 36 | /* return immediately */ 37 | if (g_e.cy == g_e.n_rows) 38 | return; 39 | /* return if we are at start of file */ 40 | if (g_e.cx == 0 && g_e.cy == 0) 41 | return; 42 | 43 | /* del char on the row so we see it */ 44 | e_row *row = &g_e.row[g_e.cy]; 45 | if (g_e.cx > 0) { 46 | editor_row_del_char(row, g_e.cx - 1); 47 | g_e.cx--; 48 | } else { 49 | /* move cursor to end of line so it end up where the two lines will join */ 50 | g_e.cx = g_e.row[g_e.cy - 1].sz; 51 | /* append row to the previus row */ 52 | editor_row_append_str(&g_e.row[g_e.cy - 1], row->line, row->sz); 53 | /* delete row (now is appended to other row) */ 54 | editor_del_row(g_e.cy); 55 | /* move cursor again now that row is deleted */ 56 | g_e.cy--; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/file_io.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* convert rows to a string with all the file */ 4 | char *editor_rows_to_str(int *buff_l) { 5 | int i; 6 | int t_len = 0; 7 | 8 | /* calculate total len of the file */ 9 | for (i = 0; i < g_e.n_rows; i++) 10 | t_len += g_e.row[i].sz + 1; 11 | *buff_l = t_len; 12 | 13 | /* allocate memory to store rows */ 14 | char *buff = (char *)malloc(t_len); 15 | /* loop rows and copy all to the buffer */ 16 | char *ptr = buff; 17 | for (i = 0; i < g_e.n_rows; i++) { 18 | memcpy(ptr, g_e.row[i].line, g_e.row[i].sz); 19 | ptr += g_e.row[i].sz; 20 | *ptr = '\n'; 21 | ptr++; 22 | } 23 | 24 | return (buff); 25 | } 26 | 27 | /* open file in editor */ 28 | void editor_open(const char *filename) { 29 | /* set filename */ 30 | free(g_e.filename); 31 | g_e.filename = strdup(filename); 32 | 33 | /* get file type to select hl */ 34 | editor_select_syntax_hl(); 35 | 36 | /* open file */ 37 | FILE *fp = fopen(filename, "r"); 38 | if (!fp) { 39 | fp = fopen(filename, "w"); 40 | if (!fp) { 41 | die("fopen"); 42 | } 43 | } 44 | 45 | /* read file */ 46 | char *line = NULL; 47 | size_t linecap = 0; 48 | ssize_t line_l; 49 | /* read lines */ 50 | while ((line_l = getline(&line, &linecap, fp)) != -1) { 51 | /* remove new line characters from the line (already added between rows) */ 52 | while (line_l > 0 && (line[line_l - 1] == '\r' || line[line_l - 1] == '\n')) 53 | line_l--; 54 | /* append row */ 55 | editor_insert_row(g_e.n_rows, line, line_l); 56 | } 57 | 58 | /* free line and close file */ 59 | free(line); 60 | fclose(fp); 61 | /* reset dirty */ 62 | g_e.dirty = 0; 63 | } 64 | 65 | /* save file in disk */ 66 | void editor_save() { 67 | /* if is a new file (with no name of course) return */ 68 | if (g_e.filename == NULL) { 69 | editor_set_status_msg("\x1b[41mERROR: no file name\x1b[m"); 70 | return; 71 | } 72 | 73 | /* get string of all the file */ 74 | int len; 75 | char *buff; 76 | buff = editor_rows_to_str(&len); 77 | 78 | /* open file */ 79 | int fd; 80 | fd = open(g_e.filename, O_RDWR | O_CREAT, 0644); 81 | if (fd != -1) { 82 | /* set file size to len */ 83 | /* if is larger it will cut off any data at the end */ 84 | /* if is shorter it will add 0 bytes at the end */ 85 | /* this way we ensure the file haz the size of the actual content */ 86 | if (ftruncate(fd, len) != -1) { 87 | /* write buff to file and close */ 88 | if (write(fd, buff, len) == len) { 89 | close(fd); 90 | free(buff); 91 | /* reset dirty */ 92 | g_e.dirty = 0; 93 | /* set status bar message */ 94 | editor_set_status_msg("\"%.20s\" %dL, written", g_e.filename ? g_e.filename : "[No Name]", g_e.n_rows); 95 | return; 96 | } 97 | } 98 | /* close */ 99 | close (fd); 100 | } 101 | 102 | /* free buf */ 103 | free(buff); 104 | editor_set_status_msg("Error: cant save, %s", strerror(errno)); 105 | } 106 | -------------------------------------------------------------------------------- /src/find.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* editor find callback */ 4 | void editor_find_callback(char *query, int key) { 5 | int last_match = -1; 6 | int dir = 1; 7 | 8 | /* hl match saves to know which lines needs to be restored */ 9 | int saved_hl_line; 10 | char *saved_hl = NULL; 11 | 12 | while (1) { 13 | /* restore if it is something */ 14 | if (saved_hl) { 15 | memcpy(g_e.row[saved_hl_line].hl, saved_hl, g_e.row[saved_hl_line].r_sz); 16 | free(saved_hl); 17 | saved_hl = NULL; 18 | } 19 | 20 | /* return if is a special action key */ 21 | if (key == '\x1b') { 22 | /* restore if it is something */ 23 | if (saved_hl) { 24 | memcpy(g_e.row[saved_hl_line].hl, saved_hl, g_e.row[saved_hl_line].r_sz); 25 | free(saved_hl); 26 | saved_hl = NULL; 27 | } 28 | break; 29 | /* check direction */ 30 | } else if (key == 'n') { 31 | dir = 1; 32 | } else if (key == 'N') { 33 | dir = -1; 34 | /* if not valid input */ 35 | } else { 36 | /* get another key input */ 37 | key = editor_read_key(); 38 | /* and loop again */ 39 | continue; 40 | } 41 | 42 | /* check last_match */ 43 | if (last_match == -1) dir = 1; 44 | int cur = last_match; 45 | /* loop rows and search for next match */ 46 | int i; 47 | for (i = 0; i < g_e.n_rows; i++) { 48 | /* move cur one row (loop) */ 49 | cur += dir; 50 | /* handle special cases */ 51 | if (cur == -1) cur = g_e.n_rows -1; 52 | else if (cur == g_e.n_rows) cur = 0; 53 | 54 | /* get row */ 55 | e_row *row = &g_e.row[cur]; 56 | /* check for match into row */ 57 | char *match = strstr(row->rend, query); 58 | if (match) { 59 | /* update last match */ 60 | last_match = cur; 61 | /* positionate cursor y on match */ 62 | g_e.cy = cur; 63 | /* positionate cursor x on start of the match */ 64 | g_e.cx = editor_row_rx_to_cx(row, match - row->rend); 65 | /* positionate match line in top of screen */ 66 | g_e.y_off = g_e.n_rows; 67 | 68 | /* get saved hl (so we can restore it later) */ 69 | saved_hl_line = cur; 70 | saved_hl = (char *)malloc(row->r_sz); 71 | memcpy(saved_hl, row->hl, row->r_sz); 72 | /* set hl color to match */ 73 | memset(&row->hl[match - row->rend], HL_MATCH, strlen(query)); 74 | 75 | break; 76 | } 77 | } 78 | /* exit for with no match in file */ 79 | if (last_match == -1) { 80 | editor_set_status_msg("\x1b[41mERROR: pattern not found: %s\x1b[m", query); 81 | return; 82 | } 83 | 84 | /* refesh screen */ 85 | editor_refresh_screen(); 86 | 87 | /* get key input */ 88 | key = editor_read_key(); 89 | } 90 | } 91 | 92 | /* find string in editor */ 93 | void editor_find() { 94 | /* save cursor pos */ 95 | int saved_cx = g_e.cx; 96 | int saved_cy = g_e.cy; 97 | int saved_x_off = g_e.x_off; 98 | int saved_y_off = g_e.y_off; 99 | 100 | /* change to insert mode */ 101 | g_e.mode = INSERT_MODE; 102 | 103 | /* ask for keyword */ 104 | char *query = editor_prompt("/%s", NULL); 105 | 106 | /* return to normal mode (to see cursor) */ 107 | g_e.mode = NORMAL_MODE; 108 | 109 | /* if no query inserted */ 110 | if (query == NULL) { 111 | editor_set_status_msg("\x1b[41mERROR: pattern not found: [None]\x1b[m"); 112 | return; 113 | } 114 | 115 | /* seach for matches */ 116 | editor_find_callback(query, 'n'); 117 | 118 | /* free */ 119 | free(query); 120 | 121 | /* if query is null is becouse we pressed ESC */ 122 | g_e.cx = saved_cx; 123 | g_e.cy = saved_cy; 124 | g_e.x_off = saved_x_off; 125 | g_e.y_off = saved_y_off; 126 | } 127 | -------------------------------------------------------------------------------- /src/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern struct editor_conf g_e; 4 | 5 | /* initialise editor */ 6 | void init_editor() { 7 | /* initisalise vars */ 8 | g_e.cx = 0; 9 | g_e.cy = 0; 10 | g_e.rx = 0; 11 | g_e.y_off = 0; 12 | g_e.x_off = 0; 13 | g_e.n_rows = 0; 14 | g_e.row = NULL; 15 | g_e.dirty = 0; 16 | g_e.filename = NULL; 17 | g_e.status_msg[0] = '\0'; 18 | g_e.mode = NORMAL_MODE; 19 | g_e.syntax = NULL; 20 | 21 | /* get window size */ 22 | if (get_windows_size(&g_e.scrn_rows, &g_e.scrn_cols) == -1) 23 | die("get_window_size"); 24 | 25 | /* remove rows, status bar and msg bar */ 26 | g_e.scrn_rows -= 2; 27 | } 28 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* editor prompt */ 4 | char *editor_prompt(char *prompt, void (*callback)(char *, int)) { 5 | size_t buff_l = 0; 6 | size_t buff_sz = 128; 7 | char *buff = (char *)malloc(buff_sz); 8 | if (!buff) 9 | die ("malloc"); 10 | 11 | /* initialise buff */ 12 | buff[0] = '\0'; 13 | 14 | while (1) { 15 | /* set status message */ 16 | editor_set_status_msg(prompt, buff); 17 | /* refesh screen */ 18 | editor_refresh_screen(); 19 | 20 | /* get key input */ 21 | int key = editor_read_key(); 22 | /* handle delete keys */ 23 | if (key == K_DEL || key == CTRL_KEY('h') || key == K_BACKSPACE) { 24 | if (buff_l != 0) { 25 | buff[--buff_l] = '\0'; 26 | } else if (buff_l == 0) { 27 | editor_set_status_msg(""); 28 | /* callback */ 29 | if (callback) callback(buff, key); 30 | free(buff); 31 | return (NULL); 32 | } 33 | /* quit prompt if ESC is pressed */ 34 | } else if (key == '\x1b') { 35 | editor_set_status_msg(""); 36 | /* callback */ 37 | if (callback) callback(buff, key); 38 | free(buff); 39 | return (NULL); 40 | } 41 | /* is new line (enter) detect, return */ 42 | if (key == '\r') { 43 | if (buff_l != 0) { 44 | editor_set_status_msg(""); 45 | /* callback */ 46 | if (callback) callback(buff, key); 47 | return (buff); 48 | } 49 | /* any other keys */ 50 | } else if (!iscntrl(key) && key < 128) { 51 | /* if buffer does not have enought space duplicate it */ 52 | if (buff_l == buff_sz - 1) { 53 | buff_sz *= 2; 54 | buff = (char *)realloc(buff, buff_sz); 55 | } 56 | /* register key into buffer */ 57 | buff[buff_l++] = key; 58 | buff[buff_l] = '\0'; 59 | } 60 | 61 | /* callback */ 62 | if (callback) callback(buff, key); 63 | } 64 | } 65 | 66 | /* process movement keys */ 67 | void editor_move_cursor(int key) { 68 | e_row *row = (g_e.cy >= g_e.n_rows) ? NULL : &g_e.row[g_e.cy]; 69 | int right_off = g_e.mode == NORMAL_MODE ? 1 : 0; 70 | 71 | /* handle key */ 72 | if (key == K_ARROW_UP || key == 'k') { 73 | if (g_e.cy != 0) { 74 | g_e.cy--; 75 | } 76 | } 77 | else if (key == K_ARROW_DOWN || key == 'j') { 78 | if (g_e.cy < g_e.n_rows) { 79 | g_e.cy++; 80 | } 81 | } 82 | else if (key == K_ARROW_LEFT || key == 'h') { 83 | if (g_e.cx != 0){ 84 | g_e.cx--; 85 | } 86 | } 87 | else if (key == K_ARROW_RIGHT || key == 'l') { 88 | if (row && g_e.cx < row->sz - right_off) { 89 | g_e.cx++; 90 | } 91 | } 92 | 93 | /* positionate cursor at end of line */ 94 | row = (g_e.cy >= g_e.n_rows) ? NULL : &g_e.row[g_e.cy]; 95 | int row_l = row ? row->sz : 0; 96 | if (g_e.cx > row_l) { 97 | g_e.cx = row_l - right_off; 98 | } 99 | } 100 | 101 | /* process key press */ 102 | void editor_process_keypress() { 103 | int key; 104 | 105 | key = editor_read_key(); 106 | 107 | /* normal mode */ 108 | if (g_e.mode == NORMAL_MODE) { 109 | /* prompt */ 110 | if (key == ':') { 111 | /* change to insert mode */ 112 | g_e.mode = INSERT_MODE; 113 | char *cmd = editor_prompt(":%s", NULL); 114 | /* if no command is inserted */ 115 | if (!cmd) { 116 | editor_set_status_msg(""); 117 | /* do stuff */ 118 | } else if (!strcmp(cmd, "w")) { 119 | /* save */ 120 | editor_save(); 121 | /* exit if there are no changes */ 122 | } else if (!strcmp(cmd, "q") && g_e.dirty == 0) { 123 | /* clear screen and move cursor before exit */ 124 | write(STDOUT_FILENO, "\x1b[2J", 4); 125 | write(STDOUT_FILENO, "\x1b[H", 3); 126 | exit(EXIT_SUCCESS); 127 | /* exit but not saved changes (dirty is not 0) */ 128 | } else if (!strcmp(cmd, "q")) { 129 | /* not saved changes */ 130 | editor_set_status_msg("\x1b[41mERROR: no write since last change (add ! to override)\x1b[m"); 131 | /* force exist with out saving */ 132 | } else if (!strcmp(cmd, "q!")) { 133 | /* clear screen and move cursor before exit */ 134 | write(STDOUT_FILENO, "\x1b[2J", 4); 135 | write(STDOUT_FILENO, "\x1b[H", 3); 136 | exit(EXIT_SUCCESS); 137 | /* save and exit */ 138 | } else if (!strcmp(cmd, "wq") || !strcmp(cmd, "x")) { 139 | /* save */ 140 | editor_save(); 141 | /* clear screen and move cursor before exit */ 142 | write(STDOUT_FILENO, "\x1b[2J", 4); 143 | write(STDOUT_FILENO, "\x1b[H", 3); 144 | exit(EXIT_SUCCESS); 145 | /* save as */ 146 | } else if (!strncmp(cmd, "saveas ", 7)) { 147 | /* get file name */ 148 | int fname_l; 149 | fname_l = strlen(cmd + 7); 150 | if (fname_l < 0) { 151 | /* error, no name */ 152 | editor_set_status_msg("\x1b[41mERROR: argument required\x1b[m"); 153 | } 154 | char *fname = (char *)malloc(fname_l + 1); 155 | memcpy(fname, cmd + 7, fname_l); 156 | fname[fname_l] = '\0'; 157 | g_e.filename = fname; 158 | /* update syntax file type and see if it matchs now */ 159 | editor_select_syntax_hl(); 160 | /* save */ 161 | editor_save(); 162 | } else { 163 | /* not an editor command */ 164 | editor_set_status_msg("\x1b[41mERROR: not an editor command: %s\x1b[m", cmd); 165 | } 166 | /* free and return to normal mode */ 167 | if (cmd) free(cmd); 168 | g_e.mode = NORMAL_MODE; 169 | /* search for a keyword */ 170 | } else if (key == '/') { 171 | editor_find(); 172 | /* move keys */ 173 | } else if (key == 'k' || key == 'j' || key == 'h' || key == 'l' 174 | || key == K_ARROW_UP || key == K_ARROW_DOWN || key == K_ARROW_LEFT || key == K_ARROW_RIGHT) { 175 | editor_move_cursor(key); 176 | /* insert keys */ 177 | } else if (key == 'i' || key == 'a' || key == 'o' || key == 'O') { 178 | /* change to insert mode */ 179 | g_e.mode = INSERT_MODE; 180 | /* move cursor depending on key */ 181 | if (key == 'a') { 182 | editor_move_cursor(K_ARROW_RIGHT); 183 | } else if (key == 'o' || key == 'O') { 184 | /* move cursor to start of line */ 185 | if (key == 'O') { 186 | g_e.cx = 0; 187 | } 188 | /* move cursor to end of line */ 189 | if (key == 'o') { 190 | if (g_e.cy < g_e.n_rows) 191 | g_e.cx = g_e.row[g_e.cy].sz; 192 | } 193 | /* insert nl from there */ 194 | editor_insert_nl(); 195 | /* move to correct line */ 196 | if (key == 'O') 197 | editor_move_cursor(K_ARROW_UP); 198 | } 199 | editor_set_status_msg("-- INSERT --"); 200 | /* home key */ 201 | } else if (key == '0' || key == K_HOME) { 202 | g_e.cx = 0; 203 | /* firts non blank */ 204 | } else if (key == '^') { 205 | g_e.cx = 0; 206 | while((g_e.row[g_e.cy].line[g_e.cx] == '\t' || g_e.row[g_e.cy].line[g_e.cx] == ' ') 207 | && g_e.cx < g_e.row[g_e.cy].sz - 1) 208 | g_e.cx++; 209 | /* end key */ 210 | } else if (key == '$' || key == K_END) { 211 | if (g_e.cy < g_e.n_rows && g_e.row[g_e.cy].sz > 0) 212 | g_e.cx = g_e.row[g_e.cy].sz - 1; 213 | /* page up and page down */ 214 | } else if (key == K_PAGE_UP || key == K_PAGE_DOWN) { 215 | /* positionate cursor before moving a page */ 216 | if (key == K_PAGE_UP) { 217 | g_e.cy = g_e.y_off; 218 | } else { 219 | g_e.cy = g_e.y_off + g_e.scrn_rows - 1; 220 | if (g_e.cy > g_e.n_rows) g_e.cy = g_e.n_rows; 221 | } 222 | /* move cursor a page */ 223 | int cnt = g_e.scrn_rows; 224 | while (cnt--) 225 | editor_move_cursor(key == K_PAGE_UP ? K_ARROW_UP : K_ARROW_DOWN); 226 | /* go to end of file */ 227 | } else if (key == 'G') { 228 | g_e.cy = g_e.n_rows - 1; 229 | g_e.cx = 0; 230 | /* go to top of file */ 231 | } else if (key == 'g') { 232 | key = editor_read_key(); 233 | if (key == 'g') { 234 | g_e.cy = 0; 235 | g_e.cx = 0; 236 | } 237 | } 238 | /* insert mode */ 239 | } else if (g_e.mode == INSERT_MODE) { 240 | /* new line */ 241 | if (key == '\r') { 242 | editor_insert_nl(); 243 | /* home key */ 244 | } else if (key == K_HOME) { 245 | g_e.cx = 0; 246 | /* end key */ 247 | } else if (key == K_END) { 248 | if (g_e.cy < g_e.n_rows) 249 | g_e.cx = g_e.row[g_e.cy].sz; 250 | /* delete keys */ 251 | } else if (key == K_BACKSPACE || key == CTRL_KEY('h') || key == K_DEL) { 252 | if (key == K_DEL) 253 | editor_move_cursor(K_ARROW_RIGHT); 254 | editor_del_char(); 255 | /* page up and page down keys */ 256 | } else if (key == K_PAGE_UP || key == K_PAGE_DOWN) { 257 | /* positionate cursor before moving a page */ 258 | if (key == K_PAGE_UP) { 259 | g_e.cy = g_e.y_off; 260 | } else { 261 | g_e.cy = g_e.y_off + g_e.scrn_rows - 1; 262 | if (g_e.cy > g_e.n_rows) g_e.cy = g_e.n_rows; 263 | } 264 | /* move cursor a page */ 265 | int cnt = g_e.scrn_rows; 266 | while (cnt--) 267 | editor_move_cursor(key == K_PAGE_UP ? K_ARROW_UP : K_ARROW_DOWN); 268 | /* arrow keys move the cursor */ 269 | } else if (key == K_ARROW_UP || key == K_ARROW_DOWN || key == K_ARROW_LEFT || key == K_ARROW_RIGHT) { 270 | editor_move_cursor(key); 271 | /* change to normal mode */ 272 | } else if (key == '\x1b') { 273 | g_e.mode = NORMAL_MODE; 274 | editor_move_cursor(K_ARROW_LEFT); 275 | editor_set_status_msg(""); 276 | /* default case (rest of chars) */ 277 | /* dont do nothing for Ctrl-L (default for refresh screen) */ 278 | /* we already handle the refresh ourselves */ 279 | } else if (key != CTRL_KEY('l')) { 280 | editor_insert_char(key); 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* editor_conf global var */ 4 | struct editor_conf g_e; 5 | 6 | /* main */ 7 | int main(int argc, char *argv[]) { 8 | /* change terminal to raw mode */ 9 | enb_raw_mode(); 10 | 11 | /* initialise editor */ 12 | init_editor(); 13 | 14 | /* open file in editor */ 15 | if (argc >= 2) 16 | editor_open(argv[1]); 17 | 18 | /* set editor status msg empty at start */ 19 | editor_set_status_msg(""); 20 | 21 | /* program loop */ 22 | while (1) { 23 | editor_refresh_screen(); 24 | editor_process_keypress(); 25 | } 26 | 27 | return (EXIT_SUCCESS); 28 | } 29 | -------------------------------------------------------------------------------- /src/output.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* check if we can scroll */ 4 | void editor_scroll() { 5 | /* get rx */ 6 | g_e.rx = 0; 7 | if (g_e.cy < g_e.n_rows) 8 | g_e.rx = editor_row_cx_to_rx(&g_e.row[g_e.cy], g_e.cx); 9 | 10 | /* handle vertical scroll */ 11 | if (g_e.cy < g_e.y_off) 12 | g_e.y_off = g_e.cy; 13 | if (g_e.cy >= g_e.y_off + g_e.scrn_rows) 14 | g_e.y_off = g_e.cy - g_e.scrn_rows + 1; 15 | /* handle horizontal scroll */ 16 | if (g_e.rx < g_e.x_off) 17 | g_e.x_off = g_e.rx; 18 | if (g_e.rx >= g_e.x_off + g_e.scrn_cols) 19 | g_e.x_off = g_e.rx - g_e.scrn_cols + 1; 20 | } 21 | 22 | /* draw rows on editor */ 23 | void editor_draw_rows(struct apbuff *ab) { 24 | int y; 25 | 26 | /* iterate rows and draw lines */ 27 | for (y = 0; y < g_e.scrn_rows; y++) { 28 | /* get file row where we want to start */ 29 | int f_row = y + g_e.y_off; 30 | /* if no rows to print */ 31 | if (f_row >= g_e.n_rows) { 32 | /* if total rows == 0 means no argument, so print welcome msg */ 33 | if (g_e.n_rows == 0 && y == g_e.scrn_rows / 3) { 34 | /* welcome message */ 35 | char welcome[32]; 36 | int welcome_l = snprintf(welcome, sizeof(welcome), "minivim - ver %s", MINIVIM_VER); 37 | if (welcome_l > g_e.scrn_cols) 38 | welcome_l = g_e.scrn_cols; 39 | int padding = (g_e.scrn_cols - welcome_l) / 2; 40 | if (padding) { 41 | apbuff_append(ab, "\x1b[34m~\x1b[m", 9); 42 | padding--; 43 | } 44 | while (padding--) 45 | apbuff_append(ab, " ", 1); 46 | apbuff_append(ab, welcome, welcome_l); 47 | /* there is a document but no more rows to print */ 48 | } else { 49 | /* if nothing to draw */ 50 | apbuff_append(ab, "\x1b[34m~\x1b[m", 9); 51 | } 52 | /* draw actual row */ 53 | } else { 54 | /* get row len */ 55 | int len = g_e.row[f_row].r_sz - g_e.x_off; 56 | if (len < 0) len = 0; 57 | if (len > g_e.scrn_cols) len = g_e.scrn_cols; 58 | /* get string to draw */ 59 | char *c = &g_e.row[f_row].rend[g_e.x_off]; 60 | /* get hl string */ 61 | unsigned char *hl = &g_e.row[f_row].hl[g_e.x_off]; 62 | /* for optimization record current color so we not do extra writes */ 63 | int cur_color = -1; 64 | /* loop row */ 65 | int i; 66 | for (i = 0; i < len; i++) { 67 | /* handle cursor */ 68 | if (CURSOR_HL && g_e.mode == NORMAL_MODE && y == g_e.cy - g_e.y_off && i == g_e.rx - g_e.x_off) { 69 | /* change color on cursor position only */ 70 | apbuff_append(ab, "\x1b[m", 4); 71 | apbuff_append(ab, "\x1b[7m", 4); 72 | apbuff_append(ab, &c[i], 1); 73 | apbuff_append(ab, "\x1b[m", 4); 74 | /* print escape secuence for cur color so we not cut hl */ 75 | if (cur_color != -1) { 76 | char buff[16]; 77 | int c_len = snprintf(buff, sizeof(buff), "\x1b[%dm", cur_color); 78 | apbuff_append(ab, buff, c_len); 79 | } 80 | /* handle no print chars */ 81 | } else if (iscntrl(c[i])) { 82 | char sym = (c[i] <= 26) ? '@' + c[i] : '?'; 83 | apbuff_append(ab, "\x1b[7m", 4); 84 | apbuff_append(ab, &sym, 1); 85 | apbuff_append(ab, "\x1b[m", 3); 86 | /* print escape secuence for cur color so we not cut hl */ 87 | if (cur_color != -1) { 88 | char buff[16]; 89 | int c_len = snprintf(buff, sizeof(buff), "\x1b[%dm", cur_color); 90 | apbuff_append(ab, buff, c_len); 91 | } 92 | /* if normal color (white) */ 93 | } else if (hl[i] == HL_NORMAL) { 94 | if (cur_color != -1) { 95 | /* set color */ 96 | cur_color = -1; 97 | /* append color */ 98 | apbuff_append(ab, "\x1b[39m", 5); 99 | } 100 | /* append char */ 101 | apbuff_append(ab, &c[i], 1); 102 | /* handle syntax hl */ 103 | } else { 104 | /* get color */ 105 | int color = editor_syntax_to_color(hl[i]); 106 | if (color != cur_color) { 107 | /* set color */ 108 | cur_color = color; 109 | /* get color escape char len */ 110 | char buff[16]; 111 | int c_len = snprintf(buff, sizeof(buff), "\x1b[%dm", color); 112 | /* append color */ 113 | apbuff_append(ab, buff, c_len); 114 | } 115 | /* append char */ 116 | apbuff_append(ab, &c[i], 1); 117 | } 118 | } 119 | /* handle cursor on empty lines */ 120 | if (CURSOR_HL && g_e.mode == NORMAL_MODE && y == g_e.cy - g_e.y_off && g_e.row[f_row].sz == 0) { 121 | apbuff_append(ab, "\x1b[m", 4); 122 | apbuff_append(ab, "\x1b[7m", 4); 123 | apbuff_append(ab, " ", 1); 124 | apbuff_append(ab, "\x1b[m", 4); 125 | } 126 | /* reset to normal color */ 127 | apbuff_append(ab, "\x1b[39m", 5); 128 | } 129 | 130 | /* erase part of the line to the right of the cursor */ 131 | apbuff_append(ab, "\x1b[K", 3); 132 | /* dont write a new line on the last row */ 133 | apbuff_append(ab, "\r\n", 2); 134 | } 135 | } 136 | 137 | /* draw status bar */ 138 | void editor_draw_status_bar(struct apbuff *ab) { 139 | /* invert draw color */ 140 | apbuff_append(ab, "\x1b[7m", 4); 141 | 142 | /* draw file name */ 143 | char status[80]; 144 | char r_status[80]; 145 | /* get filename and file lines */ 146 | int len = snprintf(status, sizeof(status), "%.20s %s", 147 | g_e.filename ? g_e.filename : "[No Name]", 148 | g_e.dirty ? "[+] " : ""); 149 | /* get status bar end string data */ 150 | int r_len; 151 | /* get status bar end string */ 152 | r_len = snprintf(r_status, sizeof(r_status), "%s | %d/%d", 153 | g_e.syntax ? g_e.syntax->f_type : "no ft", 154 | g_e.cy + 1,g_e.n_rows); 155 | /* append file name and file lines */ 156 | if (len > g_e.scrn_cols) len = g_e.scrn_cols; 157 | apbuff_append(ab, status, len); 158 | 159 | /* draw status bar */ 160 | while (len < g_e.scrn_cols) { 161 | if (g_e.scrn_cols - len == r_len) { 162 | apbuff_append(ab, r_status, r_len); 163 | break; 164 | } else { 165 | apbuff_append(ab, " ", 1); 166 | len++; 167 | } 168 | } 169 | 170 | /* reset draw color */ 171 | apbuff_append(ab, "\x1b[m", 3); 172 | /* add new line for message bar */ 173 | apbuff_append(ab, "\r\n", 2); 174 | } 175 | 176 | /* draw message bar */ 177 | void editor_draw_msg_bar(struct apbuff *ab) { 178 | /* */ 179 | apbuff_append(ab, "\x1b[K", 3); 180 | 181 | /* */ 182 | int msg_l = strlen(g_e.status_msg); 183 | if (msg_l > g_e.scrn_cols) msg_l = g_e.scrn_cols; 184 | /* apped to buff */ 185 | apbuff_append(ab, g_e.status_msg, msg_l); 186 | } 187 | 188 | /* refresh editor screen */ 189 | void editor_refresh_screen() { 190 | /* handle vertical scroll */ 191 | editor_scroll(); 192 | 193 | /* initialise struct */ 194 | struct apbuff ab = APBUFF_INIT; 195 | 196 | /* hide cursor when drawing on screen */ 197 | apbuff_append(&ab, "\x1b[?25l", 6); 198 | /* move cursor */ 199 | apbuff_append(&ab, "\x1b[H", 3); 200 | 201 | /* draw rows */ 202 | editor_draw_rows(&ab); 203 | /* draw status bar and msg bar*/ 204 | editor_draw_status_bar(&ab); 205 | editor_draw_msg_bar(&ab); 206 | 207 | /* get cursor pos and move cursor */ 208 | char buff[32]; 209 | snprintf(buff, sizeof(buff), "\x1b[%d;%dH", (g_e.cy - g_e.y_off) + 1, (g_e.rx - g_e.x_off) + 1); 210 | apbuff_append(&ab, buff, strlen(buff)); 211 | 212 | /* show cursor again (finish drawing) */ 213 | apbuff_append(&ab, "\x1b[?25h", 6); 214 | 215 | /* write buffer on screen */ 216 | write(STDOUT_FILENO, ab.buff, ab.len); 217 | 218 | /* free buffer */ 219 | apbuff_free(&ab); 220 | } 221 | 222 | /* set status message */ 223 | void editor_set_status_msg(const char *format, ...) { 224 | va_list args; 225 | va_start(args, format); 226 | vsnprintf(g_e.status_msg, sizeof(g_e.status_msg), format, args); 227 | va_end(args); 228 | } 229 | -------------------------------------------------------------------------------- /src/row_ops.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* calculate rx */ 4 | int editor_row_cx_to_rx(e_row *row, int cx) { 5 | int rx = 0; 6 | int i; 7 | 8 | /* iterate line and calculate len with tabs included */ 9 | for (i = 0; i < cx; i++) { 10 | if (row->line[i] == '\t') 11 | rx += (TAB_SIZE - 1) - (rx % TAB_SIZE); 12 | rx++; 13 | } 14 | 15 | return (rx); 16 | } 17 | 18 | /* calculate cx */ 19 | int editor_row_rx_to_cx(e_row *row, int rx) { 20 | int cx; 21 | int cur_rx = 0; 22 | 23 | /* loop row */ 24 | for (cx = 0; cx < row->sz; cx++) { 25 | /* handle tab size */ 26 | if (row->line[cx] == '\t') 27 | cur_rx += (TAB_SIZE - 1) - (cur_rx % TAB_SIZE); 28 | /* handle every other char */ 29 | cur_rx++; 30 | 31 | /* return if the provided rx was out of range */ 32 | if (cur_rx > rx) 33 | return (cx); 34 | } 35 | 36 | /* return cx */ 37 | return (cx); 38 | } 39 | 40 | /* update row */ 41 | void editor_update_row(e_row *row) { 42 | int tabs = 0; 43 | int i; 44 | 45 | /* count tabs */ 46 | for (i = 0; i < row->sz; i++) { 47 | if (row->line[i] == '\t') tabs++; 48 | } 49 | /* free old rend and allocate the new one */ 50 | free(row->rend); 51 | row->rend = (char *)malloc(row->sz + tabs * (TAB_SIZE - 1) + 1); 52 | 53 | /* copy line chars to rend and handle tabs */ 54 | int idx = 0; 55 | for (i = 0; i < row->sz; i++) { 56 | if (row->line[i] == '\t') { 57 | row->rend[idx++] = ' '; 58 | while (idx % TAB_SIZE) 59 | row->rend[idx++] = ' '; 60 | } else if (row->line[i] == ' ') { 61 | row->rend[idx++] = ' '; 62 | } else { 63 | row->rend[idx++] = row->line[i]; 64 | } 65 | } 66 | 67 | /* set '\0' at end of string and set render size */ 68 | row->rend[idx] = '\0'; 69 | row->r_sz = idx; 70 | 71 | /* update syntax */ 72 | editor_update_syntax(row); 73 | } 74 | 75 | /* insert / append row */ 76 | void editor_insert_row(int idx, char *s, size_t len) { 77 | /* check index is valid */ 78 | if (idx < 0 || idx > g_e.n_rows) 79 | return; 80 | 81 | /* realloc e_row struct to allocate all rows */ 82 | g_e.row = (e_row *)realloc(g_e.row, sizeof(e_row) * (g_e.n_rows + 1)); 83 | if (!g_e.row) 84 | die("realloc"); 85 | 86 | /* shift rows */ 87 | memmove(&g_e.row[idx + 1], &g_e.row[idx], sizeof(e_row) * (g_e.n_rows - idx)); 88 | 89 | /* update index of the row (displaced by insert) */ 90 | for (int i = idx + 1; i <= g_e.n_rows; i++) g_e.row[i].idx++; 91 | 92 | /* set row index to index */ 93 | g_e.row[idx].idx = idx; 94 | 95 | /* insert / append row */ 96 | g_e.row[idx].sz = len; 97 | g_e.row[idx].line = (char *)malloc(len + 1); 98 | memcpy(g_e.row[idx].line, s, len); 99 | g_e.row[idx].line[len] = '\0'; 100 | 101 | /* initialise render */ 102 | g_e.row[idx].r_sz = 0; 103 | g_e.row[idx].rend = NULL; 104 | g_e.row[idx].hl = NULL; 105 | g_e.row[idx].hl_open_comment = 0; 106 | editor_update_row(&g_e.row[idx]); 107 | 108 | /* increase number of rows */ 109 | g_e.n_rows++; 110 | /* increase dirty (we make changes) */ 111 | g_e.dirty++; 112 | } 113 | 114 | /* free row */ 115 | void editor_free_row(e_row *row) { 116 | free(row->rend); 117 | free(row->line); 118 | free(row->hl); 119 | } 120 | 121 | /* delete row */ 122 | void editor_del_row(int idx) { 123 | /* check index is valid */ 124 | if (idx < 0 || idx >= g_e.n_rows) 125 | return; 126 | 127 | /* delete row */ 128 | editor_free_row(&g_e.row[idx]); 129 | 130 | /* shift rest of the rows */ 131 | memmove(&g_e.row[idx], &g_e.row[idx + 1], sizeof(e_row) * (g_e.n_rows - idx - 1)); 132 | 133 | /* update index of the row (displaced by delete) */ 134 | for (int i = idx; i < g_e.n_rows - 1; i++) g_e.row[i].idx--; 135 | 136 | /* update number of rows */ 137 | g_e.n_rows--; 138 | /* update dirty */ 139 | g_e.dirty++; 140 | } 141 | 142 | /* insert char in a row */ 143 | void editor_row_insert_char(e_row *row, int idx, int c) { 144 | /* check idx is valid */ 145 | if (idx < 0 || idx > row->sz) 146 | idx = row->sz; 147 | 148 | /* realloc line so it can store one more char */ 149 | row->line = (char *)realloc(row->line, row->sz + 2); 150 | /* shift line one position from where we will add the char */ 151 | /* memmove to prevent overlap, we are in the same string */ 152 | memmove(&row->line[idx + 1], &row->line[idx], row->sz - idx + 1); 153 | /* update row size */ 154 | row->sz++; 155 | /* insert chart */ 156 | row->line[idx] = c; 157 | /* update row */ 158 | editor_update_row(row); 159 | /* increase dirty (we make changes) */ 160 | g_e.dirty++; 161 | } 162 | 163 | /* append str to a row */ 164 | void editor_row_append_str(e_row *row, char *s, size_t len) { 165 | /* allocate space for the append */ 166 | row->line = (char *)realloc(row->line, row->sz + len + 1); 167 | /* append string */ 168 | memcpy(&row->line[row->sz], s, len); 169 | /* update row size */ 170 | row->sz += len; 171 | /* update row */ 172 | editor_update_row(row); 173 | /* set dirty */ 174 | g_e.dirty++; 175 | } 176 | 177 | /* delete char in a row */ 178 | void editor_row_del_char(e_row *row, int idx) { 179 | /* check idx is valid */ 180 | if (idx < 0 || idx >= row->sz) 181 | return; 182 | 183 | /* shift line one position left */ 184 | memmove(&row->line[idx], &row->line[idx + 1], row->sz - idx); 185 | /* update row size */ 186 | row->sz--; 187 | editor_update_row(row); 188 | g_e.dirty++; 189 | } 190 | -------------------------------------------------------------------------------- /src/syntax_hl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /*** filetypes ***/ 4 | 5 | char *C_HL_extensions[C_HL_EXT_SIZE] = { ".c", ".h", ".cpp", ".hpp", ".cc", NULL}; 6 | 7 | char *C_HL_keywords[C_HL_KEYW_SIZE] = { 8 | /* C Keywords */ 9 | "auto","break","case","continue","default","do","else","enum", 10 | "extern","for","goto","if","register","return","sizeof","static", 11 | "struct","switch","typedef","union","volatile","while","NULL", 12 | 13 | /* C++ Keywords */ 14 | "alignas","alignof","and","and_eq","asm","bitand","bitor","class", 15 | "compl","constexpr","const_cast","deltype","delete","dynamic_cast", 16 | "explicit","export","false","friend","inline","mutable","namespace", 17 | "new","noexcept","not","not_eq","nullptr","operator","or","or_eq", 18 | "private","protected","public","reinterpret_cast","static_assert", 19 | "static_cast","template","this","thread_local","throw","true","try", 20 | "typeid","typename","virtual","xor","xor_eq", 21 | 22 | /* C types */ 23 | "int|","long|","double|","float|","char|","unsigned|","signed|", 24 | "void|","short|","auto|","const|","bool|",NULL 25 | }; 26 | 27 | struct e_syntax HLDB[HLDB_SIZE] = { 28 | { 29 | "c", 30 | C_HL_extensions, 31 | C_HL_keywords, 32 | "//", "/*", "*/", 33 | HL_HL_NBR | HL_HL_STR 34 | }, 35 | }; 36 | 37 | # define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0])) 38 | 39 | /* return true if char is a separator */ 40 | int is_separator(int c) { 41 | return (isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[];", c) != NULL); 42 | } 43 | 44 | /* set row syntax */ 45 | void editor_update_syntax(e_row *row) { 46 | /* realloc memory por hl array */ 47 | row->hl = (unsigned char *)realloc(row->hl, row->r_sz); 48 | /* set all array to normal hl */ 49 | memset(row->hl, HL_NORMAL, row->r_sz); 50 | 51 | /* not update syntax if no fily type is detected */ 52 | if (g_e.syntax == NULL) return; 53 | 54 | /* keywords alias */ 55 | char **keywords = g_e.syntax->keywords; 56 | 57 | /* initialise comment */ 58 | char *olc = g_e.syntax->oneline_comment; 59 | char *mlcs = g_e.syntax->ml_comment_st; 60 | char *mlce = g_e.syntax->ml_comment_end; 61 | 62 | /* check if active comment */ 63 | int olc_l = olc ? strlen(olc) : 0; 64 | int mlcs_l = mlcs ? strlen(mlcs) : 0; 65 | int mlce_l = mlce ? strlen(mlce) : 0; 66 | 67 | /* save if previus character was a separator (def: 1) */ 68 | int prev_sep = 1; 69 | /* save if we are in a string (def: 0) */ 70 | int in_str = 0; 71 | /* check if previus lines was a multiline comment with no end */ 72 | int in_comment = (row->idx > 0 && g_e.row[row->idx - 1].hl_open_comment); 73 | 74 | int i = 0; 75 | while (i < row->r_sz) { 76 | /* get current char */ 77 | char c = row->rend[i]; 78 | /* get previus hl */ 79 | unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL; 80 | 81 | /* hl one line comment */ 82 | if (olc_l && !in_str && !in_comment) { 83 | if (!strncmp(&row->rend[i], olc, olc_l)) { 84 | memset(&row->hl[i], HL_COMMENT, row->r_sz - i); 85 | break; 86 | } 87 | } 88 | 89 | /* hl multi line comments */ 90 | if (mlcs_l && mlce_l && !in_str) { 91 | /* if we are in a comment, comment until multicomment end */ 92 | if (in_comment) { 93 | row->hl[i] = HL_MLCOMMENT; 94 | if (!strncmp(&row->rend[i], mlce, mlce_l)) { 95 | memset(&row->hl[i], HL_MLCOMMENT, mlce_l); 96 | i += mlce_l; 97 | in_comment = 0; 98 | prev_sep = 1; 99 | continue; 100 | } else { 101 | i++; 102 | continue; 103 | } 104 | /* else check if multicomment starts */ 105 | } else if (!strncmp(&row->rend[i], mlcs, mlcs_l)) { 106 | memset(&row->hl[i], HL_MLCOMMENT, mlcs_l); 107 | i += mlcs_l; 108 | in_comment = 1; 109 | continue; 110 | } 111 | } 112 | 113 | /* hl strings */ 114 | if (g_e.syntax->flags & HL_HL_STR) { 115 | /* if we already are in a str */ 116 | if (in_str) { 117 | /* change hl */ 118 | row->hl[i] = HL_STRING; 119 | /* if we find \ skip two chars just in case the second is a " or ' */ 120 | if (c == '\\' && i + 1 < row->r_sz) { 121 | row->hl[i + 1] = HL_STRING; 122 | i += 2; 123 | continue; 124 | } 125 | /* if we find " or ' again set in_str to 0 (we are out of the str) */ 126 | if (c == in_str) in_str = 0; 127 | /* set prev_sep to true and continue looping */ 128 | i++; 129 | prev_sep = 1; 130 | continue; 131 | /* if we are not in a str */ 132 | } else { 133 | /* if we find a start str character */ 134 | if (c == '"' || c == '\'') { 135 | /* set in_str to the char (" or ') */ 136 | in_str = c; 137 | /* change hl */ 138 | row->hl[i] = HL_STRING; 139 | /* continue looping */ 140 | i++; 141 | continue; 142 | } 143 | } 144 | } 145 | 146 | /* hl numbers */ 147 | if (g_e.syntax->flags & HL_HL_NBR) { 148 | if ((isdigit(c) && (prev_sep || prev_hl == HL_NUMBER)) || (c == '.' && prev_hl == HL_NUMBER)) { 149 | row->hl[i] = HL_NUMBER; 150 | i++; 151 | prev_sep = 0; 152 | continue; 153 | } 154 | } 155 | 156 | /* hl keywords */ 157 | /* check is preceeded by separator */ 158 | if (prev_sep) { 159 | int j; 160 | /* loop keywords and check */ 161 | for (j = 0; keywords[j]; j++) { 162 | /* get len of keyword */ 163 | int k_len = strlen(keywords[j]); 164 | /* get if it is a secondary keyword '|' (and remove '|' from k_len if so) */ 165 | int kw2 = keywords[j][k_len - 1] == '|'; 166 | if (kw2) k_len--; 167 | 168 | /* check if it is a keyword and ends in separator */ 169 | if (!strncmp(&row->rend[i], keywords[j], k_len) && is_separator(row->rend[i + k_len])) { 170 | /* hl keyword and increase i*/ 171 | memset(&row->hl[i], kw2 ? HL_KEYWORD2 : HL_KEYWORD1, k_len); 172 | i += k_len; 173 | /* break for */ 174 | break; 175 | } 176 | } 177 | /* check if for was broken (or forced) out by checking the terminating NULL value */ 178 | if (keywords[j] != NULL) { 179 | prev_sep = 0; 180 | continue; 181 | } 182 | } 183 | 184 | /* set prev_sep */ 185 | prev_sep = is_separator(c); 186 | 187 | i++; 188 | } 189 | 190 | /* set value of hl_open_comment to in_comment state */ 191 | int changed = (row->hl_open_comment != in_comment); 192 | row->hl_open_comment = in_comment; 193 | /* update syntax of all lines in case multiline comment */ 194 | if (changed && row->idx + 1 < g_e.n_rows) 195 | editor_update_syntax(&g_e.row[row->idx + 1]); 196 | } 197 | 198 | /* handle colors */ 199 | int editor_syntax_to_color(int hl) { 200 | if (hl == HL_COMMENT || hl == HL_MLCOMMENT) return (36); 201 | if (hl == HL_KEYWORD1) return (33); 202 | if (hl == HL_KEYWORD2) return (32); 203 | if (hl == HL_STRING) return (35); 204 | if (hl == HL_NUMBER) return (31); 205 | if (hl == HL_MATCH) return (34); 206 | else return (37); 207 | } 208 | 209 | /* select syntax hl */ 210 | void editor_select_syntax_hl() { 211 | /* initialise syntax to null */ 212 | g_e.syntax = NULL; 213 | 214 | /* return if there is no file name yet */ 215 | if (g_e.filename == NULL) return; 216 | 217 | /* get last '.' in string */ 218 | char *ext = strrchr(g_e.filename, '.'); 219 | 220 | /* loop editor syntax struct in the HLDB array */ 221 | for (unsigned int i = 0; i < HLDB_ENTRIES; i++) { 222 | struct e_syntax *s = &HLDB[i]; 223 | unsigned int j = 0; 224 | /* loop every pattern */ 225 | while (s->f_match[j]) { 226 | int is_ext = (s->f_match[j][0] == '.'); 227 | /* check if match pattern */ 228 | if ((is_ext && ext && !strcmp(ext, s->f_match[j])) || (!is_ext && strstr(g_e.filename, s->f_match[j]))) { 229 | /* set syntax */ 230 | g_e.syntax = s; 231 | 232 | /* update syntax */ 233 | int f_row; 234 | for (f_row = 0; f_row < g_e.n_rows; f_row++) { 235 | editor_update_syntax(&g_e.row[f_row]); 236 | } 237 | 238 | return; 239 | } 240 | j++; 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/terminal.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* die func., refresh screen, print error and exit */ 4 | void die(const char *s) { 5 | /* clear screen and move cursor */ 6 | write(STDOUT_FILENO, "\x1b[2J", 4); 7 | write(STDOUT_FILENO, "\x1b[H", 3); 8 | 9 | /* restore terminal */ 10 | dis_raw_mode(); 11 | 12 | /* print error and exit */ 13 | perror(s); 14 | exit (EXIT_FAILURE); 15 | } 16 | 17 | /* atexit(), end ncurses, disable raw mode on terminal and restore origin attributes */ 18 | void dis_raw_mode() { 19 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_e.org_termios) == -1) 20 | die("tcsetattr"); 21 | } 22 | 23 | /* enable raw mode on terminal */ 24 | void enb_raw_mode() { 25 | struct termios raw; 26 | 27 | /* save terminal's original attributes */ 28 | if (tcgetattr(STDIN_FILENO, &g_e.org_termios) == -1) 29 | die("tcgetattr"); 30 | atexit(dis_raw_mode); 31 | 32 | /* get terminal's attributes */ 33 | raw = g_e.org_termios; 34 | /* terminal flags */ 35 | raw.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); 36 | raw.c_oflag &= ~(OPOST); 37 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 38 | raw.c_cflag &= ~(CSIZE | PARENB); 39 | raw.c_cflag |= ~(CS8); 40 | /* timeout */ 41 | raw.c_cc[VMIN] = 0; 42 | raw.c_cc[VTIME] = 1; 43 | /* apply changes to terminal */ 44 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) 45 | die("tcsetattr"); 46 | } 47 | 48 | /* read key */ 49 | int editor_read_key() { 50 | int nread; 51 | char c; 52 | 53 | /* read 1 byte */ 54 | while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { 55 | if (nread == -1 && errno != EAGAIN) 56 | die("read"); 57 | } 58 | 59 | /* keep reading if escape char is read */ 60 | if (c == '\x1b') { 61 | char seq[3]; 62 | 63 | /* read the rest of the sequence */ 64 | if (read(STDIN_FILENO, &seq[0], 1) != 1) return ('\x1b'); 65 | if (read(STDIN_FILENO, &seq[1], 1) != 1) return ('\x1b'); 66 | 67 | /* get arrows */ 68 | if (seq[0] == '[') { 69 | if (seq[1] >= '0' && seq[1] <= '9') { 70 | if (read(STDIN_FILENO, &seq[2], 1) != 1) return ('\x1b'); 71 | if (seq[2] == '~') { 72 | if (seq[1] == '1') return (K_HOME); 73 | if (seq[1] == '3') return (K_DEL); 74 | if (seq[1] == '4') return (K_END); 75 | if (seq[1] == '5') return (K_PAGE_UP); 76 | if (seq[1] == '6') return (K_PAGE_DOWN); 77 | if (seq[1] == '7') return (K_HOME); 78 | if (seq[1] == '8') return (K_END); 79 | } 80 | } 81 | if (seq[1] == 'A') return (K_ARROW_UP); 82 | if (seq[1] == 'B') return (K_ARROW_DOWN); 83 | if (seq[1] == 'C') return (K_ARROW_RIGHT); 84 | if (seq[1] == 'D') return (K_ARROW_LEFT); 85 | if (seq[1] == 'H') return (K_HOME); 86 | if (seq[1] == 'F') return (K_END); 87 | } else if (seq[0] == '0') { 88 | if (seq[1] == 'H') return (K_HOME); 89 | if (seq[1] == 'F') return (K_END); 90 | } 91 | 92 | return ('\x1b'); 93 | } 94 | 95 | return (c); 96 | } 97 | 98 | /* get cursor pos on terminal */ 99 | int get_cursor_pos(int *rows, int *cols) { 100 | char buff[32]; 101 | unsigned int i; 102 | 103 | /* n command can be used to query the terminal for status info */ 104 | /* the argument "6" ask for cursor position */ 105 | /* this way we can read the reply from the standard input */ 106 | if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) 107 | return (-1); 108 | 109 | /* read standard input into a buffer until 'R' is found */ 110 | /* buff will be something like "30;120" (30 is the number of rows, and 120 of cols) */ 111 | i = -1; 112 | while (++i < sizeof(buff) - 1) { 113 | if (read(STDIN_FILENO, &buff[i], 1) != 1) 114 | break; 115 | if (buff[i] == 'R') 116 | break; 117 | } 118 | buff[i] = '\0'; 119 | 120 | /* make sure buff has an escape sequence */ 121 | if (buff[0] != '\x1b' || buff[1] != '[') 122 | return (-1); 123 | /* parse buff into two integers: rows and cols */ 124 | if (sscanf(&buff[2], "%d;%d", rows, cols) != 2) 125 | return (-1); 126 | 127 | return (0); 128 | } 129 | 130 | /* get terminal window size */ 131 | int get_windows_size(int *rows, int *cols) { 132 | /* struct */ 133 | struct winsize ws; 134 | 135 | /* get rows and cols using ioctl() (input output control) */ 136 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 137 | /* fallback method if ioctl fails */ 138 | /* move cursor to bottom right corner */ 139 | if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) 140 | return (-1); 141 | /* return the fallback method */ 142 | return (get_cursor_pos(rows, cols)); 143 | } else { 144 | *cols = ws.ws_col; 145 | *rows = ws.ws_row; 146 | return (0); 147 | } 148 | } 149 | --------------------------------------------------------------------------------