├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── include ├── buf.h ├── conf.h ├── draw.h ├── editor.h ├── editor_bind.h ├── file_exp.h ├── frame.h ├── hl │ ├── hl_c.h │ ├── hl_cc.h │ ├── hl_cs.h │ ├── hl_html.h │ ├── hl_md.h │ ├── hl_rs.h │ └── hl_s.h ├── keybd.h ├── label.h ├── mode.h ├── mode │ ├── mode_clike.h │ ├── mode_html.h │ ├── mode_null.h │ ├── mode_s.h │ └── mutil.h ├── prompt.h ├── utf8.h └── util.h ├── install.sh ├── mincbuild.conf ├── src ├── buf.c ├── conf.c ├── draw.c ├── editor.c ├── editor_bind.c ├── file_exp.c ├── frame.c ├── hl │ ├── hl_c.c │ ├── hl_cc.c │ ├── hl_cs.c │ ├── hl_html.c │ ├── hl_md.c │ ├── hl_rs.c │ └── hl_s.c ├── keybd.c ├── label.c ├── main.c ├── mode.c ├── mode │ ├── mode_clike.c │ ├── mode_html.c │ ├── mode_null.c │ ├── mode_s.c │ └── mutil.c ├── prompt.c ├── utf8.c └── util.c └── uninstall.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /medioed 3 | /medioed.log 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | 3 | SRC_DIR := src 4 | INC_DIR := include 5 | LIB_DIR := lib 6 | 7 | CC := gcc 8 | LD := gcc 9 | CFLAGS := -std=c99 -pedantic -I$(INC_DIR) -D_POSIX_C_SOURCE=200809 -D_GNU_SOURCE -O3 10 | LDFLAGS := 11 | 12 | SRCS := $(shell find $(SRC_DIR) -name "*.c") 13 | OBJS := $(patsubst $(SRC_DIR)/%,$(LIB_DIR)/%.o,$(SRCS)) 14 | OUT_BIN := medioed 15 | 16 | all: $(OUT_BIN) 17 | 18 | $(OUT_BIN): $(OBJS) 19 | $(LD) $(LDFLAGS) -o $@ $^ 20 | 21 | $(LIB_DIR)/%.o: $(SRC_DIR)/% 22 | @ mkdir -p $@ 23 | @ rmdir $@ 24 | $(CC) $(CFLAGS) -o $@ -c $< 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # medioed 2 | 3 | *NOTE: I am in the process of creating a new personal text editor that works in 4 | a fundamentally different way. As it stands, this project is effectively 5 | discontinued for now.* 6 | 7 | ## Introduction 8 | 9 | medioed, or (Medio)cre Text (Ed)itor, is a text editor program I'm making 10 | because I want to. The user experience should be somewhat similar to a less 11 | configurable (and substantially more minimal) GNU Emacs. This won't be anything 12 | serious, just an educational project with the side effect of creating a text 13 | editor that I personally enjoy using. 14 | 15 | ## Dependencies 16 | 17 | Software / system dependencies are: 18 | 19 | * A shell environment (for program execution) 20 | * Man (for manpage viewer command in editor, optional) 21 | * mincbuild (for build, optional) 22 | * Make (for build, optional) 23 | 24 | ## Management 25 | 26 | * To build the program, run `mincbuild` or `make` 27 | * To install the program, run `./install.sh` 28 | * To uninstall the program from the system, run `./uninstall.sh` 29 | 30 | ## Usage 31 | 32 | After installation: either run `medioed` on its own to open the medioed greeter, 33 | or `medioed file.txt other.txt ...` to open all specified files as buffers. From 34 | there, edit the files as necessary. 35 | 36 | ## Contributing 37 | 38 | To contribute, please open a pull request with your changes or an issue with a 39 | suggestion. As a rule of thumb, I will be more likely to accept pull requests 40 | which fix bugs or improve compatiblity rather than those which add new features. 41 | If you want to add a feature, I would prefer you opened an issue - however I 42 | will accept pull requests if I like them. 43 | -------------------------------------------------------------------------------- /include/buf.h: -------------------------------------------------------------------------------- 1 | #ifndef BUF_H 2 | #define BUF_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "util.h" 10 | 11 | enum buf_src_type 12 | { 13 | BST_FRESH = 0, 14 | BST_FILE, 15 | }; 16 | 17 | enum buf_flag 18 | { 19 | BF_WRITABLE = 0x1, 20 | BF_MODIFIED = 0x2, 21 | BF_NO_HIST = 0x4, 22 | }; 23 | 24 | enum buf_op_type 25 | { 26 | BOT_WRITE = 0, 27 | BOT_ERASE, 28 | BOT_BRK, 29 | }; 30 | 31 | struct buf_op 32 | { 33 | wchar_t *data; 34 | size_t lb, ub; 35 | unsigned char type; 36 | }; 37 | 38 | VEC_DEF_PROTO(struct buf_op, buf_op) 39 | 40 | struct buf 41 | { 42 | // TODO: change name back and implement gap buffer system. 43 | // name suffixed with `_` to raise errors where it was previously used, 44 | // so that they can be changed. 45 | wchar_t *conts_; 46 | 47 | // `size` ignores the size of the internal gap. 48 | // e.g. a five-wchar gap with two wchars around it would yield a buffer 49 | // size of 2. 50 | size_t size; 51 | 52 | size_t cap; 53 | size_t gap_pos, gap_size; 54 | void *src; 55 | unsigned char src_type; 56 | uint8_t flags; 57 | struct vec_buf_op hist; 58 | }; 59 | 60 | VEC_DEF_PROTO(struct buf *, p_buf) 61 | 62 | struct buf buf_create(bool writable); 63 | struct buf buf_from_file(char const *path); 64 | struct buf buf_from_wstr(wchar_t const *wstr, bool writable); 65 | int buf_save(struct buf *b); 66 | int buf_undo(struct buf *b); 67 | void buf_destroy(struct buf *b); 68 | void buf_write_wch(struct buf *b, size_t ind, wchar_t wch); 69 | void buf_write_wstr(struct buf *b, size_t ind, wchar_t const *wstr); 70 | void buf_erase(struct buf *b, size_t lb, size_t ub); 71 | void buf_push_hist_brk(struct buf *b); 72 | void buf_pos(struct buf const *b, size_t pos, unsigned *out_r, unsigned *out_c); 73 | wchar_t buf_get_wch(struct buf const *b, size_t ind); 74 | wchar_t *buf_get_wstr(struct buf const *b, wchar_t *dst, size_t ind, size_t n); 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /include/conf.h: -------------------------------------------------------------------------------- 1 | #ifndef CONF_H 2 | #define CONF_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "draw.h" 9 | #include "mode.h" 10 | 11 | // text layout options. 12 | #define CONF_TAB_SIZE 6 13 | #define CONF_GUTTER_LEFT 1 14 | #define CONF_GUTTER_RIGHT 1 15 | #define CONF_MNUM 4 16 | #define CONF_MDENOM 7 17 | 18 | // master color options. 19 | #define CONF_A_GNORM_FG 183 20 | #define CONF_A_GNORM_BG 232 21 | #define CONF_A_GHIGH_FG CONF_A_GNORM_BG 22 | #define CONF_A_GHIGH_BG CONF_A_GNORM_FG 23 | #define CONF_A_NORM_FG 253 24 | #define CONF_A_NORM_BG 233 25 | #define CONF_A_LINUM_FG 245 26 | #define CONF_A_LINUM_BG 232 27 | #define CONF_A_CURSOR_FG 0 28 | #define CONF_A_CURSOR_BG 15 29 | 30 | // theme color options. 31 | #define CONF_A_ACCENT_1_FG 183 32 | #define CONF_A_ACCENT_1_BG CONF_A_NORM_BG 33 | #define CONF_A_ACCENT_2_FG 176 34 | #define CONF_A_ACCENT_2_BG CONF_A_NORM_BG 35 | #define CONF_A_ACCENT_3_FG 229 36 | #define CONF_A_ACCENT_3_BG CONF_A_NORM_BG 37 | #define CONF_A_ACCENT_4_FG 212 38 | #define CONF_A_ACCENT_4_BG CONF_A_NORM_BG 39 | #define CONF_A_STRING_FG 182 40 | #define CONF_A_STRING_BG 52 41 | #define CONF_A_SPECIAL_FG 247 42 | #define CONF_A_SPECIAL_BG CONF_A_NORM_BG 43 | #define CONF_A_COMMENT_FG 245 44 | #define CONF_A_COMMENT_BG 235 45 | 46 | #define CONF_MARK_MOD L"[~*]" 47 | #define CONF_MARK_MONO L"[M!]" 48 | 49 | // scrap buffer options. 50 | #define CONF_SCRAP_NAME L"*scrap*" 51 | 52 | // manpage viewer options. 53 | #define CONF_READ_MAN_TITLE L"*manpage viewer*" 54 | #define CONF_READ_MAN_CMD "man -E ascii -P cat %w 2> /dev/null" 55 | #define CONF_READ_MAN_SR 20 56 | #define CONF_READ_MAN_SC 85 57 | 58 | // file explorer options. 59 | #define CONF_FILE_EXP_NAME L"*file explorer*" 60 | #define CONF_FILE_EXP_SC 40 61 | #define CONF_FILE_EXP_NEST_DEPTH 2 62 | 63 | // greeter options. 64 | #define CONF_GREET_NAME L"*greeter*" 65 | #define CONF_GREET_TEXT \ 66 | L" _ _ _\n" \ 67 | L" _ __ ___ ___ __| (_) ___ ___ __| |\n" \ 68 | L"| '_ ` _ \\ / _ \\/ _` | |/ _ \\ / _ \\/ _` |\n" \ 69 | L"| | | | | | __/ (_| | | (_) | __/ (_| |\n" \ 70 | L"|_| |_| |_|\\___|\\__,_|_|\\___/ \\___|\\__,_|\n" \ 71 | L"\n" \ 72 | L"Welcome to medioed, the (Medio)cre Text (Ed)itor inspired by GNU Emacs.\n" \ 73 | L"\n" \ 74 | L"Medioed has always been, and will always be, Free Software, licensed under the\n" \ 75 | L"GNU GPLv3, for the benefit of users and anyone wishing to study the source. I\n" \ 76 | L"created medioed for my own personal use, but you are free to use it on your own\n" \ 77 | L"machines as well.\n" \ 78 | L"\n" \ 79 | L"Additional resources:\n" \ 80 | L"\n" \ 81 | L"* Source code: https://github.com/tirimid/medioed\n" \ 82 | L"* Documentation: https://tirimid.net/software/medioed.html\n" \ 83 | L"\n" \ 84 | L"(The greeter logo seen above was generated with the use of Figlet)\n" 85 | 86 | struct highlight 87 | { 88 | char const *local_mode; 89 | int (*find)(struct buf const *, size_t, size_t *, size_t *, uint8_t *, uint8_t *); 90 | }; 91 | 92 | struct margin 93 | { 94 | unsigned col; 95 | wchar_t wch; 96 | uint8_t fg, bg; 97 | }; 98 | 99 | struct mode_ext 100 | { 101 | char const **exts; 102 | char const *localmode, *globalmode; 103 | }; 104 | 105 | extern int const conf_bind_quit[]; 106 | extern int const conf_bind_chg_frame_fwd[]; 107 | extern int const conf_bind_chg_frame_back[]; 108 | extern int const conf_bind_focus_frame[]; 109 | extern int const conf_bind_kill_frame[]; 110 | extern int const conf_bind_open_file[]; 111 | extern int const conf_bind_save_file[]; 112 | extern int const conf_bind_nav_fwd_ch[]; 113 | extern int const conf_bind_nav_fwd_word[]; 114 | extern int const conf_bind_nav_fwd_page[]; 115 | extern int const conf_bind_nav_back_ch[]; 116 | extern int const conf_bind_nav_back_word[]; 117 | extern int const conf_bind_nav_back_page[]; 118 | extern int const conf_bind_nav_down[]; 119 | extern int const conf_bind_nav_up[]; 120 | extern int const conf_bind_nav_ln_start[]; 121 | extern int const conf_bind_nav_ln_end[]; 122 | extern int const conf_bind_nav_goto[]; 123 | extern int const conf_bind_del_fwd_ch[]; 124 | extern int const conf_bind_del_back_ch[]; 125 | extern int const conf_bind_del_back_word[]; 126 | extern int const conf_bind_chg_mode_global[]; 127 | extern int const conf_bind_chg_mode_local[]; 128 | extern int const conf_bind_create_scrap[]; 129 | extern int const conf_bind_new_line[]; 130 | extern int const conf_bind_focus[]; 131 | extern int const conf_bind_kill[]; 132 | extern int const conf_bind_paste[]; 133 | extern int const conf_bind_undo[]; 134 | extern int const conf_bind_copy[]; 135 | extern int const conf_bind_ncopy[]; 136 | extern int const conf_bind_find_lit[]; 137 | extern int const conf_bind_mac_begin[]; 138 | extern int const conf_bind_mac_end[]; 139 | extern int const conf_bind_toggle_mono[]; 140 | extern int const conf_bind_read_man_word[]; 141 | extern int const conf_bind_file_exp[]; 142 | 143 | extern struct highlight const conf_htab[]; 144 | extern size_t const conf_htab_size; 145 | extern struct margin const conf_mtab[]; 146 | extern size_t const conf_mtab_size; 147 | extern struct mode const conf_lmtab[]; 148 | extern size_t const conf_lmtab_size; 149 | extern struct mode_ext const conf_metab[]; 150 | extern size_t const conf_metab_size; 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /include/draw.h: -------------------------------------------------------------------------------- 1 | #ifndef DRAW_H 2 | #define DRAW_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct win_size 9 | { 10 | unsigned sr, sc; 11 | }; 12 | 13 | void draw_init(void); 14 | void draw_quit(void); 15 | void draw_clear(wchar_t wch, uint8_t fg, uint8_t bg); 16 | void draw_fill(unsigned pr, unsigned pc, unsigned sr, unsigned sc, wchar_t wch, uint8_t fg, uint8_t bg); 17 | void draw_put_wch(unsigned r, unsigned c, wchar_t wch); 18 | void draw_put_wstr(unsigned r, unsigned c, wchar_t const *wstr); 19 | void draw_put_attr(unsigned r, unsigned c, uint8_t fg, uint8_t bg, unsigned n); 20 | void draw_refresh(void); 21 | struct win_size draw_win_size(void); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /include/editor.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_H 2 | #define EDITOR_H 3 | 4 | int editor_init(int argc, char const *argv[]); 5 | void editor_main_loop(void); 6 | void editor_quit(void); 7 | void editor_redraw(void); 8 | struct buf *editor_add_buf(struct buf *b); 9 | struct frame *editor_add_frame(struct frame *f); 10 | void editor_arrange_frames(void); 11 | void editor_reset_binds(void); 12 | void editor_set_global_mode(void); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /include/editor_bind.h: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_BIND_H 2 | #define EDITOR_BIND_H 3 | 4 | void editor_bind_quit(void); 5 | void editor_bind_chg_frame_fwd(void); 6 | void editor_bind_chg_frame_back(void); 7 | void editor_bind_focus_frame(void); 8 | void editor_bind_kill_frame(void); 9 | void editor_bind_open_file(void); 10 | void editor_bind_save_file(void); 11 | void editor_bind_nav_fwd_ch(void); 12 | void editor_bind_nav_fwd_word(void); 13 | void editor_bind_nav_fwd_page(void); 14 | void editor_bind_nav_back_ch(void); 15 | void editor_bind_nav_back_word(void); 16 | void editor_bind_nav_back_page(void); 17 | void editor_bind_nav_down(void); 18 | void editor_bind_nav_up(void); 19 | void editor_bind_nav_ln_start(void); 20 | void editor_bind_nav_ln_end(void); 21 | void editor_bind_nav_goto(void); 22 | void editor_bind_del_fwd_ch(void); 23 | void editor_bind_del_back_ch(void); 24 | void editor_bind_del_back_word(void); 25 | void editor_bind_chg_mode_global(void); 26 | void editor_bind_chg_mode_local(void); 27 | void editor_bind_create_scrap(void); 28 | void editor_bind_new_line(void); 29 | void editor_bind_focus(void); 30 | void editor_bind_kill(void); 31 | void editor_bind_paste(void); 32 | void editor_bind_undo(void); 33 | void editor_bind_copy(void); 34 | void editor_bind_ncopy(void); 35 | void editor_bind_find_lit(void); 36 | void editor_bind_mac_begin(void); 37 | void editor_bind_mac_end(void); 38 | void editor_bind_toggle_mono(void); 39 | void editor_bind_read_man_word(void); 40 | void editor_bind_file_exp(void); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/file_exp.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_EXP_H 2 | #define FILE_EXP_H 3 | 4 | #include 5 | 6 | enum file_exp_rc 7 | { 8 | FER_SUCCESS = 0, 9 | FER_FAIL, 10 | FER_QUIT, 11 | }; 12 | 13 | enum file_exp_rc file_exp_open(char *out_path, size_t n, char const *dir); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /include/frame.h: -------------------------------------------------------------------------------- 1 | #ifndef FRAME_H 2 | #define FRAME_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "buf.h" 9 | #include "util.h" 10 | 11 | enum frame_draw_flag 12 | { 13 | FDF_ACTIVE = 0x1, 14 | FDF_MONO = 0x2, 15 | }; 16 | 17 | struct frame 18 | { 19 | wchar_t *name; 20 | unsigned pr, pc; 21 | unsigned sr, sc; 22 | struct buf *buf; 23 | char *local_mode; 24 | size_t buf_start, csr; 25 | unsigned linum_width; 26 | unsigned csr_want_col; 27 | }; 28 | 29 | VEC_DEF_PROTO(struct frame, frame) 30 | 31 | struct frame frame_create(wchar_t const *name, struct buf *buf); 32 | void frame_destroy(struct frame *f); 33 | void frame_draw(struct frame const *f, unsigned long flags); 34 | void frame_pos(struct frame const *f, size_t pos, unsigned *out_r, unsigned *out_c); 35 | void frame_mv_csr(struct frame *f, unsigned r, unsigned c); 36 | void frame_mv_csr_rel(struct frame *f, int dr, int dc, bool wrap); 37 | void frame_comp_boundary(struct frame *f); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /include/hl/hl_c.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HL_C_H 2 | #define HL_HL_C_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_c_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/hl/hl_cc.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HL_CC_H 2 | #define HL_HL_CC_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_cc_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/hl/hl_cs.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HL_CS_H 2 | #define HL_HL_CS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_cs_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/hl/hl_html.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HTML_H 2 | #define HL_HTML_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_html_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif -------------------------------------------------------------------------------- /include/hl/hl_md.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HL_MD_H 2 | #define HL_HL_MD_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_md_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/hl/hl_rs.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HL_RS_H 2 | #define HL_HL_RS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_rs_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/hl/hl_s.h: -------------------------------------------------------------------------------- 1 | #ifndef HL_HL_S_H 2 | #define HL_HL_S_H 3 | 4 | #include 5 | #include 6 | 7 | #include "buf.h" 8 | 9 | int hl_s_find(struct buf const *buf, 10 | size_t off, 11 | size_t *out_lb, 12 | size_t *out_ub, 13 | uint8_t *out_fg, 14 | uint8_t *out_bg); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/keybd.h: -------------------------------------------------------------------------------- 1 | #ifndef KEYBD_H 2 | #define KEYBD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define KEYBD_IGNORE 0xfedc1234 9 | #define KEYBD_MAX_BIND_LEN 32 10 | #define KEYBD_MAX_DPY_LEN 7 11 | #define KEYBD_MAX_MAC_LEN 512 12 | 13 | void keybd_init(void); 14 | void keybd_quit(void); 15 | void keybd_bind(int const *key_seq, void (*fn)(void)); 16 | void keybd_organize(void); 17 | void keybd_rec_mac_begin(void); 18 | bool keybd_is_rec_mac(void); 19 | void keybd_rec_mac_end(void); 20 | void keybd_exec_mac(void); 21 | bool keybd_is_exec_mac(void); 22 | wint_t keybd_await_key_nb(void); 23 | wint_t keybd_await_key(void); 24 | void keybd_key_dpy(wchar_t *out, int const *kbuf, size_t nk); 25 | int const *keybd_cur_bind(size_t *out_len); 26 | int const *keybd_cur_mac(size_t *out_len); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /include/label.h: -------------------------------------------------------------------------------- 1 | #ifndef LABEL_H 2 | #define LABEL_H 3 | 4 | #include 5 | 6 | struct label_bounds 7 | { 8 | unsigned pr, pc; 9 | unsigned sr, sc; 10 | }; 11 | 12 | int label_rebound(struct label_bounds *bounds, unsigned anchor_l, unsigned anchor_r, unsigned anchor_t); 13 | int label_show(wchar_t const *name, wchar_t const *msg, struct label_bounds const *bounds); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /include/mode.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_H 2 | #define MODE_H 3 | 4 | #include 5 | 6 | #include "frame.h" 7 | 8 | struct mode 9 | { 10 | char const *name; 11 | void (*init)(struct frame *), (*quit)(void); 12 | void (*update)(void); 13 | void (*keypress)(wint_t); 14 | }; 15 | 16 | struct mode const *mode_get(void); 17 | void mode_set(char const *name, struct frame *f); 18 | void mode_update(void); 19 | void mode_key_update(wint_t k); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/mode/mode_clike.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_MODE_CLIKE_H 2 | #define MODE_MODE_CLIKE_H 3 | 4 | #include 5 | 6 | #include "frame.h" 7 | 8 | void mode_clike_init(struct frame *f); 9 | void mode_clike_quit(void); 10 | void mode_clike_update(void); 11 | void mode_clike_keypress(wint_t k); 12 | 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /include/mode/mode_html.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_MODE_HTML_H 2 | #define MODE_MODE_HTML_H 3 | 4 | #include 5 | 6 | #include "frame.h" 7 | 8 | void mode_html_init(struct frame *f); 9 | void mode_html_quit(void); 10 | void mode_html_update(void); 11 | void mode_html_keypress(wint_t k); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/mode/mode_null.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_MODE_NULL_H 2 | #define MODE_MODE_NULL_H 3 | 4 | #include 5 | 6 | #include "frame.h" 7 | 8 | void mode_null_init(struct frame *f); 9 | void mode_null_quit(void); 10 | void mode_null_update(void); 11 | void mode_null_keypress(wint_t k); 12 | 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /include/mode/mode_s.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_MODE_S_H 2 | #define MODE_MODE_S_H 3 | 4 | #include 5 | 6 | #include "frame.h" 7 | 8 | void mode_s_init(struct frame *f); 9 | void mode_s_quit(void); 10 | void mode_s_update(void); 11 | void mode_s_keypress(wint_t k); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /include/mode/mutil.h: -------------------------------------------------------------------------------- 1 | #ifndef MODE_MUTIL_H 2 | #define MODE_MUTIL_H 3 | 4 | #include "frame.h" 5 | 6 | enum pair_flags 7 | { 8 | PF_PAREN = 0x1, 9 | PF_BRACKET = 0x2, 10 | PF_BRACE = 0x4, 11 | PF_ANGLE = 0x8, 12 | PF_SQUOTE = 0x10, 13 | PF_DQUOTE = 0x20, 14 | }; 15 | 16 | void mu_init(struct frame *f); 17 | void mu_set_base(void); 18 | void mu_set_pairing(unsigned long flags); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/prompt.h: -------------------------------------------------------------------------------- 1 | #ifndef PROMPT_H 2 | #define PROMPT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | void prompt_show(wchar_t const *msg); 9 | wchar_t *prompt_ask(wchar_t const *msg, void (*comp)(wchar_t **, size_t *, size_t *)); 10 | int prompt_yes_no(wchar_t const *msg, bool deflt); 11 | 12 | // completion functions for use in `comp` for `prompt_ask()`. 13 | void prompt_comp_path(wchar_t **resp, size_t *rlen, size_t *csr); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /include/utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef UTF8_H 2 | #define UTF8_H 3 | 4 | #include 5 | #include 6 | 7 | #define UTF8_BAD_CH 0xffffbadc 8 | 9 | typedef uint32_t uchar32; 10 | 11 | int uc32scmp(uchar32 const *lhs, uchar32 const *rhs); 12 | int uc32sncmp(uchar32 const *lhs, uchar32 const *rhs, size_t n); 13 | uchar32 utf8_decode_ch(uint8_t const *bytes); 14 | uint8_t *utf8_encode_ch(uint8_t *out_bytes, uchar32 ch); 15 | int utf8_decode_str(uchar32 *out_str, uint8_t const *bytes); 16 | int utf8_encode_str(uint8_t *out_bytes, uchar32 const *str); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define NOTHING 13 | 14 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 15 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 16 | #define CLAMP(min, n, max) MIN(MAX((min), (n)), (max)) 17 | #define ABS(n) ((n) < 0 ? -(n) : (n)) 18 | #define SIGN(n) ((n) / ABS(n)) 19 | 20 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 21 | 22 | #define K_CTL(k) (k - 'a' + 1) 23 | #define K_F(n) 27, 79, (79 + n) 24 | #define K_META(k) 27, k 25 | #define K_BACKSPC 127 26 | #define K_ESC 27 27 | #define K_RET 13 28 | #define K_SPC 32 29 | #define K_TAB 9 30 | 31 | #define VEC_DEF_PROTO_EX(t, name, acc_level) \ 32 | struct vec_##name \ 33 | { \ 34 | t *data; \ 35 | size_t size, cap; \ 36 | }; \ 37 | acc_level struct vec_##name vec_##name##_create(void); \ 38 | acc_level void vec_##name##_destroy(struct vec_##name *v); \ 39 | acc_level void vec_##name##_add(struct vec_##name *v, t *new); \ 40 | acc_level void vec_##name##_rm(struct vec_##name *v, size_t ind); \ 41 | acc_level void vec_##name##_swap(struct vec_##name *v, size_t a, size_t b); 42 | 43 | #define VEC_DEF_IMPL_EX(t, name, acc_level) \ 44 | acc_level struct vec_##name \ 45 | vec_##name##_create(void) \ 46 | { \ 47 | return (struct vec_##name) \ 48 | { \ 49 | .data = malloc(sizeof(t)), \ 50 | .size = 0, \ 51 | .cap = 1, \ 52 | }; \ 53 | } \ 54 | acc_level void \ 55 | vec_##name##_destroy(struct vec_##name *v) \ 56 | { \ 57 | free(v->data); \ 58 | } \ 59 | acc_level void \ 60 | vec_##name##_add(struct vec_##name *v, t *new) \ 61 | { \ 62 | if (v->size >= v->cap) \ 63 | { \ 64 | v->cap *= 2; \ 65 | v->data = realloc(v->data, sizeof(t) * v->cap); \ 66 | } \ 67 | v->data[v->size++] = *new; \ 68 | } \ 69 | acc_level void \ 70 | vec_##name##_rm(struct vec_##name *v, size_t ind) \ 71 | { \ 72 | memmove(&v->data[ind], \ 73 | &v->data[ind + 1], \ 74 | sizeof(t) * (v->size - ind - 1)); \ 75 | --v->size; \ 76 | } \ 77 | acc_level void \ 78 | vec_##name##_swap(struct vec_##name *v, size_t a, size_t b) \ 79 | { \ 80 | t tmp = v->data[a]; \ 81 | v->data[a] = v->data[b]; \ 82 | v->data[b] = tmp; \ 83 | } 84 | 85 | #define VEC_DEF_PROTO(t, name) VEC_DEF_PROTO_EX(t, name, NOTHING) 86 | #define VEC_DEF_IMPL(t, name) VEC_DEF_IMPL_EX(t, name, NOTHING) 87 | #define VEC_DEF_PROTO_STATIC(t, name) VEC_DEF_PROTO_EX(t, name, static) 88 | #define VEC_DEF_IMPL_STATIC(t, name) VEC_DEF_IMPL_EX(t, name, static) 89 | 90 | VEC_DEF_PROTO(char *, str) 91 | 92 | char const *file_ext(char const *path); 93 | int mk_dir_rec(char const *dir); 94 | int mk_file(char const *path); 95 | bool is_path_same(char const *pa, char const *pb); 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $EUID -ne 0 ] 4 | then 5 | echo "install procedure requires root permissions" 6 | exit 1 7 | fi 8 | 9 | cp medioed /usr/bin 10 | if [ $? -ne 0 ] 11 | then 12 | echo "please build program before installing" 13 | exit 1 14 | fi 15 | -------------------------------------------------------------------------------- /mincbuild.conf: -------------------------------------------------------------------------------- 1 | # toolchain. 2 | cc = /usr/bin/gcc 3 | ld = /usr/bin/gcc 4 | cflags = -std=c99 -pedantic -D_POSIX_C_SOURCE=200809 -D_GNU_SOURCE -O3 5 | ldflags = NONE 6 | 7 | # project. 8 | src_dir = src 9 | inc_dir = include 10 | lib_dir = lib 11 | produce_output = true 12 | output = medioed 13 | src_exts = c 14 | hdr_exts = h 15 | 16 | # dependencies. 17 | incs = NONE 18 | libs = NONE 19 | 20 | # toolchain information. 21 | cc_cmd_fmt = %c %f -o %o -c %s %i 22 | cc_inc_fmt = -I%i 23 | ld_obj_fmt = %o 24 | ld_lib_fmt = -l%l 25 | ld_cmd_fmt = %c %f -o %b %o %l 26 | cc_success_rc = 0 27 | ld_success_rc = 0 28 | -------------------------------------------------------------------------------- /src/buf.c: -------------------------------------------------------------------------------- 1 | #include "buf.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "prompt.h" 12 | 13 | // adding entries past `MAX_HIST_SIZE` will cause the earliest existing entries 14 | // to be deleted in order to make space. 15 | #define MAX_HIST_SIZE 512 16 | 17 | VEC_DEF_IMPL(struct buf_op, buf_op) 18 | VEC_DEF_IMPL(struct buf *, p_buf) 19 | 20 | extern bool flag_r; 21 | 22 | static void push_hist(struct buf *b, enum buf_op_type type, wchar_t const *data, size_t lb, size_t ub); 23 | 24 | struct buf 25 | buf_create(bool writable) 26 | { 27 | return (struct buf) 28 | { 29 | .conts_ = malloc(sizeof(wchar_t)), 30 | .size = 0, 31 | .cap = 1, 32 | .src = NULL, 33 | .src_type = BST_FRESH, 34 | .flags = writable * BF_WRITABLE, 35 | .hist = vec_buf_op_create(), 36 | }; 37 | } 38 | 39 | struct buf 40 | buf_from_file(char const *path) 41 | { 42 | FILE *fp = fopen(path, "rb"); 43 | if (!fp) 44 | { 45 | size_t msg_len = sizeof(wchar_t) * (strlen(path) + 20); 46 | wchar_t *msg = malloc(msg_len); 47 | swprintf(msg, msg_len, L"cannot read file: %s!", path); 48 | prompt_show(msg); 49 | free(msg); 50 | return buf_create(false); 51 | } 52 | 53 | struct buf b = buf_create(true); 54 | b.flags = BF_WRITABLE | BF_NO_HIST; 55 | b.src_type = BST_FILE; 56 | b.src = strdup(path); 57 | 58 | errno = 0; 59 | wint_t wch; 60 | while ((wch = fgetwc(fp)) != WEOF) 61 | buf_write_wch(&b, b.size, wch); 62 | 63 | if (errno == EILSEQ) 64 | { 65 | size_t msg_len = sizeof(wchar_t) * (strlen(path) + 31); 66 | wchar_t *msg = malloc(msg_len); 67 | swprintf(msg, msg_len, L"file contains invalid UTF-8: %s!", path); 68 | prompt_show(msg); 69 | free(msg); 70 | } 71 | 72 | fclose(fp); 73 | 74 | int ea = euidaccess(path, W_OK); 75 | if (ea != 0 || flag_r) 76 | { 77 | size_t msg_len = sizeof(wchar_t) * (strlen(path) + 24); 78 | wchar_t *msg = malloc(msg_len); 79 | swprintf(msg, msg_len, L"opening file readonly: %s", path); 80 | prompt_show(msg); 81 | free(msg); 82 | } 83 | 84 | b.flags = (!ea && !flag_r) * BF_WRITABLE; 85 | 86 | return b; 87 | } 88 | 89 | struct buf 90 | buf_from_wstr(wchar_t const *wstr, bool writable) 91 | { 92 | struct buf b = buf_create(true); 93 | 94 | b.flags = BF_WRITABLE | BF_NO_HIST; 95 | buf_write_wstr(&b, 0, wstr); 96 | b.flags = writable * BF_WRITABLE; 97 | 98 | return b; 99 | } 100 | 101 | int 102 | buf_save(struct buf *b) 103 | { 104 | // it doesnt make sense to save to anything other than a file and since 105 | // no file is specified as the buffer source, nothing is done. 106 | if (b->src_type != BST_FILE) 107 | return 1; 108 | 109 | // no point saving an unchanged file into itself. 110 | if (!(b->flags & BF_MODIFIED)) 111 | return 0; 112 | 113 | FILE *fp = fopen(b->src, "wb"); 114 | if (!fp) 115 | return 1; 116 | 117 | for (size_t i = 0; i < b->size; ++i) 118 | { 119 | wchar_t wcs[] = {b->conts_[i], 0}; 120 | char mbs[sizeof(wchar_t) + 1] = {0}; 121 | wcstombs(mbs, wcs, sizeof(wchar_t) + 1); 122 | fputs(mbs, fp); 123 | } 124 | 125 | fclose(fp); 126 | b->flags &= ~BF_MODIFIED; 127 | 128 | return 0; 129 | } 130 | 131 | int 132 | buf_undo(struct buf *b) 133 | { 134 | if (!(b->flags & BF_WRITABLE)) 135 | return 1; 136 | 137 | while (b->hist.size > 0 && b->hist.data[b->hist.size - 1].type == BOT_BRK) 138 | vec_buf_op_rm(&b->hist, b->hist.size - 1); 139 | 140 | // this is not a failure, so a 0 is returned. 141 | // having nothing happen upon undoing a non-action is the expected 142 | // result. 143 | if (b->hist.size == 0) 144 | return 0; 145 | 146 | struct buf_op const *bo = &b->hist.data[b->hist.size - 1]; 147 | switch (bo->type) 148 | { 149 | case BOT_WRITE: 150 | b->flags |= BF_NO_HIST; 151 | buf_erase(b, bo->lb, bo->ub); 152 | b->flags &= ~BF_NO_HIST; 153 | break; 154 | case BOT_ERASE: 155 | b->flags |= BF_NO_HIST; 156 | buf_write_wstr(b, bo->lb, bo->data); 157 | b->flags &= ~BF_NO_HIST; 158 | free(bo->data); 159 | break; 160 | } 161 | 162 | vec_buf_op_rm(&b->hist, b->hist.size - 1); 163 | buf_push_hist_brk(b); 164 | return 0; 165 | } 166 | 167 | void 168 | buf_destroy(struct buf *b) 169 | { 170 | free(b->conts_); 171 | 172 | if (b->src) 173 | free(b->src); 174 | 175 | for (size_t i = 0; i < b->hist.size; ++i) 176 | { 177 | if (b->hist.data[i].data) 178 | free(b->hist.data[i].data); 179 | } 180 | vec_buf_op_destroy(&b->hist); 181 | } 182 | 183 | void 184 | buf_write_wch(struct buf *b, size_t ind, wchar_t wch) 185 | { 186 | if (!(b->flags & BF_WRITABLE)) 187 | return; 188 | 189 | if (b->size >= b->cap) 190 | { 191 | b->cap *= 2; 192 | b->conts_ = realloc(b->conts_, sizeof(wchar_t) * b->cap); 193 | } 194 | 195 | memmove(b->conts_ + ind + 1, 196 | b->conts_ + ind, 197 | sizeof(wchar_t) * (b->size - ind)); 198 | b->conts_[ind] = wch; 199 | ++b->size; 200 | b->flags |= BF_MODIFIED; 201 | push_hist(b, BOT_WRITE, NULL, ind, ind + 1); 202 | } 203 | 204 | void 205 | buf_write_wstr(struct buf *b, size_t ind, wchar_t const *wstr) 206 | { 207 | if (!(b->flags & BF_WRITABLE)) 208 | return; 209 | 210 | size_t len = wcslen(wstr); 211 | size_t newcap = b->cap; 212 | 213 | for (size_t i = 1; i <= len; ++i) 214 | { 215 | if (b->size + i > newcap) 216 | newcap *= 2; 217 | } 218 | 219 | if (b->cap != newcap) 220 | { 221 | b->cap = newcap; 222 | b->conts_ = realloc(b->conts_, sizeof(wchar_t) * b->cap); 223 | } 224 | 225 | memmove(b->conts_ + ind + len, 226 | b->conts_ + ind, 227 | sizeof(wchar_t) * (b->size - ind)); 228 | memcpy(b->conts_ + ind, wstr, sizeof(wchar_t) * len); 229 | b->size += len; 230 | b->flags |= BF_MODIFIED; 231 | push_hist(b, BOT_WRITE, NULL, ind, ind + len); 232 | } 233 | 234 | void 235 | buf_erase(struct buf *b, size_t lb, size_t ub) 236 | { 237 | if (!(b->flags & BF_WRITABLE)) 238 | return; 239 | 240 | push_hist(b, BOT_ERASE, b->conts_ + lb, lb, ub); 241 | memmove(b->conts_ + lb, b->conts_ + ub, sizeof(wchar_t) * (b->size - ub)); 242 | b->size -= ub - lb; 243 | b->flags |= BF_MODIFIED; 244 | } 245 | 246 | void 247 | buf_push_hist_brk(struct buf *b) 248 | { 249 | if (b->hist.size > 0) 250 | push_hist(b, BOT_BRK, NULL, 0, 1); 251 | } 252 | 253 | void 254 | buf_pos(struct buf const *b, size_t pos, unsigned *out_r, unsigned *out_c) 255 | { 256 | pos = MIN(pos, b->size); 257 | 258 | *out_r = *out_c = 0; 259 | 260 | for (size_t i = 0; i < pos; ++i) 261 | { 262 | ++*out_c; 263 | if (buf_get_wch(b, i) == L'\n') 264 | { 265 | *out_c = 0; 266 | ++*out_r; 267 | } 268 | } 269 | } 270 | 271 | wchar_t 272 | buf_get_wch(struct buf const *b, size_t ind) 273 | { 274 | return b->conts_[ind]; 275 | } 276 | 277 | wchar_t * 278 | buf_get_wstr(struct buf const *b, wchar_t *dst, size_t ind, size_t n) 279 | { 280 | n = MIN(n, b->size - ind); 281 | if (ind >= b->size || !n) 282 | { 283 | dst[0] = 0; 284 | return dst; 285 | } 286 | 287 | wcsncpy(dst, &b->conts_[ind], n - 1); 288 | dst[n - 1] = 0; 289 | return dst; 290 | } 291 | 292 | static void 293 | push_hist(struct buf *b, 294 | enum buf_op_type type, 295 | wchar_t const *data, 296 | size_t lb, 297 | size_t ub) 298 | { 299 | if (b->flags & BF_NO_HIST) 300 | return; 301 | 302 | if (lb >= ub) 303 | return; 304 | 305 | while (b->hist.size >= MAX_HIST_SIZE) 306 | { 307 | if (b->hist.data[0].data) 308 | free(b->hist.data[0].data); 309 | vec_buf_op_rm(&b->hist, 0); 310 | } 311 | 312 | struct buf_op *prev = b->hist.size > 0 ? &b->hist.data[b->hist.size - 1] : NULL; 313 | 314 | switch (type) 315 | { 316 | case BOT_WRITE: 317 | if (prev && prev->type == BOT_WRITE && lb == prev->ub) 318 | prev->ub = ub; 319 | else 320 | { 321 | struct buf_op new = 322 | { 323 | .type = BOT_WRITE, 324 | .data = NULL, 325 | .lb = lb, 326 | .ub = ub, 327 | }; 328 | vec_buf_op_add(&b->hist, &new); 329 | } 330 | break; 331 | case BOT_ERASE: 332 | if (prev && prev->type == BOT_ERASE && ub == prev->lb) 333 | { 334 | size_t size = sizeof(wchar_t) * (ub - lb); 335 | size_t psize = sizeof(wchar_t) * (prev->ub - prev->lb); 336 | 337 | prev->data = realloc(prev->data, size + psize + sizeof(wchar_t)); 338 | memmove(prev->data + ub - lb, prev->data, psize); 339 | memcpy(prev->data, data, size); 340 | prev->data[prev->ub - lb] = 0; 341 | prev->lb = lb; 342 | } 343 | else 344 | { 345 | struct buf_op new = 346 | { 347 | .type = BOT_ERASE, 348 | .data = malloc(sizeof(wchar_t) * (ub - lb + 1)), 349 | .lb = lb, 350 | .ub = ub, 351 | }; 352 | memcpy(new.data, data, sizeof(wchar_t) * (ub - lb)); 353 | new.data[ub - lb] = 0; 354 | vec_buf_op_add(&b->hist, &new); 355 | } 356 | break; 357 | case BOT_BRK: 358 | { 359 | struct buf_op new = 360 | { 361 | .type = BOT_BRK, 362 | .data = NULL, 363 | .lb = 0, 364 | .ub = 0, 365 | }; 366 | vec_buf_op_add(&b->hist, &new); 367 | break; 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/conf.c: -------------------------------------------------------------------------------- 1 | #include "conf.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hl/hl_c.h" 9 | #include "hl/hl_cc.h" 10 | #include "hl/hl_cs.h" 11 | #include "hl/hl_html.h" 12 | #include "hl/hl_md.h" 13 | #include "hl/hl_rs.h" 14 | #include "hl/hl_s.h" 15 | #include "mode/mode_clike.h" 16 | #include "mode/mode_html.h" 17 | #include "mode/mode_null.h" 18 | #include "mode/mode_s.h" 19 | #include "util.h" 20 | 21 | // binds. 22 | int const conf_bind_quit[] = {K_CTL('x'), K_CTL('c'), -1}; 23 | int const conf_bind_chg_frame_fwd[] = {K_CTL('x'), 'b', -1}; 24 | int const conf_bind_chg_frame_back[] = {K_CTL('c'), 'b', -1}; 25 | int const conf_bind_focus_frame[] = {K_CTL('c'), 'f', -1}; 26 | int const conf_bind_kill_frame[] = {K_CTL('x'), 'k', -1}; 27 | int const conf_bind_open_file[] = {K_CTL('x'), K_CTL('f'), -1}; 28 | int const conf_bind_save_file[] = {K_CTL('x'), K_CTL('s'), -1}; 29 | int const conf_bind_nav_fwd_ch[] = {K_CTL('f'), -1}; 30 | int const conf_bind_nav_fwd_word[] = {K_META('f'), -1}; 31 | int const conf_bind_nav_fwd_page[] = {K_CTL('v'), -1}; 32 | int const conf_bind_nav_back_ch[] = {K_CTL('b'), -1}; 33 | int const conf_bind_nav_back_word[] = {K_META('b'), -1}; 34 | int const conf_bind_nav_back_page[] = {K_META('v'), -1}; 35 | int const conf_bind_nav_down[] = {K_CTL('n'), -1}; 36 | int const conf_bind_nav_up[] = {K_CTL('p'), -1}; 37 | int const conf_bind_nav_ln_start[] = {K_CTL('a'), -1}; 38 | int const conf_bind_nav_ln_end[] = {K_CTL('e'), -1}; 39 | int const conf_bind_nav_goto[] = {K_META('g'), K_META('g'), -1}; 40 | int const conf_bind_del_fwd_ch[] = {K_CTL('d'), -1}; 41 | int const conf_bind_del_back_ch[] = {K_BACKSPC, -1}; 42 | int const conf_bind_del_back_word[] = {K_META(K_BACKSPC), -1}; 43 | int const conf_bind_chg_mode_global[] = {K_CTL('c'), K_META('g'), 'g', -1}; 44 | int const conf_bind_chg_mode_local[] = {K_CTL('c'), K_META('g'), 'l', -1}; 45 | int const conf_bind_create_scrap[] = {K_CTL('c'), 'n', -1}; 46 | int const conf_bind_new_line[] = {K_RET, -1}; 47 | int const conf_bind_focus[] = {K_CTL('l'), -1}; 48 | int const conf_bind_kill[] = {K_CTL('k'), -1}; 49 | int const conf_bind_paste[] = {K_CTL('y'), -1}; 50 | int const conf_bind_undo[] = {K_CTL('x'), 'u', -1}; 51 | int const conf_bind_copy[] = {K_CTL('c'), K_SPC, K_META('w'), -1}; 52 | int const conf_bind_ncopy[] = {K_CTL('c'), K_SPC, K_META('n'), -1}; 53 | int const conf_bind_find_lit[] = {K_CTL('s'), 'l', -1}; 54 | int const conf_bind_mac_begin[] = {K_F(3), -1}; 55 | int const conf_bind_mac_end[] = {K_F(4), -1}; 56 | int const conf_bind_toggle_mono[] = {K_CTL('c'), 'm', -1}; 57 | int const conf_bind_read_man_word[] = {K_CTL('h'), K_CTL('d'), -1}; 58 | int const conf_bind_file_exp[] = {K_CTL('c'), 'd', -1}; 59 | 60 | // language mode extensions. 61 | static char const *ext_c[] = {"c", "h", NULL}; 62 | static char const *ext_cc[] = {"cc", "hh", "cpp", "hpp", NULL}; 63 | static char const *ext_cs[] = {"cs", NULL}; 64 | static char const *ext_html[] = {"html", NULL}; 65 | static char const *ext_lua[] = {"lua", NULL}; 66 | static char const *ext_md[] = {"md", NULL}; 67 | static char const *ext_rs[] = {"rs", NULL}; 68 | static char const *ext_s[] = {"s", "S", NULL}; 69 | static char const *ext_sh[] = {"sh", NULL}; 70 | 71 | // highlight table. 72 | struct highlight const conf_htab[] = 73 | { 74 | { 75 | .local_mode = "c", 76 | .find = hl_c_find, 77 | }, 78 | { 79 | .local_mode = "cc", 80 | .find = hl_cc_find, 81 | }, 82 | { 83 | .local_mode = "cs", 84 | .find = hl_cs_find, 85 | }, 86 | { 87 | .local_mode = "html", 88 | .find = hl_html_find, 89 | }, 90 | { 91 | .local_mode = "md", 92 | .find = hl_md_find, 93 | }, 94 | { 95 | .local_mode = "rs", 96 | .find = hl_rs_find, 97 | }, 98 | { 99 | .local_mode = "s", 100 | .find = hl_s_find, 101 | }, 102 | }; 103 | size_t const conf_htab_size = ARRAY_SIZE(conf_htab); 104 | 105 | // margin table. 106 | struct margin const conf_mtab[] = 107 | { 108 | { 109 | .col = 80, 110 | .wch = L'|', 111 | .fg = 241, 112 | .bg = CONF_A_NORM_BG, 113 | }, 114 | { 115 | .col = 110, 116 | .wch = L'|', 117 | .fg = 248, 118 | .bg = CONF_A_NORM_BG, 119 | }, 120 | }; 121 | size_t const conf_mtab_size = ARRAY_SIZE(conf_mtab); 122 | 123 | // language mode table. 124 | struct mode const conf_lmtab[] = 125 | { 126 | { 127 | .name = "clike", 128 | .init = mode_clike_init, 129 | .quit = mode_clike_quit, 130 | .update = mode_clike_update, 131 | .keypress = mode_clike_keypress, 132 | }, 133 | { 134 | .name = "html", 135 | .init = mode_html_init, 136 | .quit = mode_html_quit, 137 | .update = mode_html_update, 138 | .keypress = mode_html_keypress, 139 | }, 140 | { 141 | .name = "md", 142 | .init = mode_null_init, 143 | .quit = mode_null_quit, 144 | .update = mode_null_update, 145 | .keypress = mode_null_keypress, 146 | }, 147 | { 148 | .name = "s", 149 | .init = mode_s_init, 150 | .quit = mode_s_quit, 151 | .update = mode_s_update, 152 | .keypress = mode_s_keypress, 153 | }, 154 | }; 155 | size_t const conf_lmtab_size = ARRAY_SIZE(conf_lmtab); 156 | 157 | // mode extension table. 158 | struct mode_ext const conf_metab[] = 159 | { 160 | { 161 | .exts = ext_c, 162 | .localmode = "c", 163 | .globalmode = "clike", 164 | }, 165 | { 166 | .exts = ext_cc, 167 | .localmode = "cc", 168 | .globalmode = "clike", 169 | }, 170 | { 171 | .exts = ext_cs, 172 | .localmode = "cs", 173 | .globalmode = "clike", 174 | }, 175 | { 176 | .exts = ext_html, 177 | .localmode = "html", 178 | .globalmode = "html", 179 | }, 180 | { 181 | .exts = ext_lua, 182 | .localmode = "lua", 183 | .globalmode = "lua", 184 | }, 185 | { 186 | .exts = ext_md, 187 | .localmode = "md", 188 | .globalmode = "md", 189 | }, 190 | { 191 | .exts = ext_rs, 192 | .localmode = "rs", 193 | .globalmode = "clike", 194 | }, 195 | { 196 | .exts = ext_s, 197 | .localmode = "s", 198 | .globalmode = "s", 199 | }, 200 | { 201 | .exts = ext_sh, 202 | .localmode = "sh", 203 | .globalmode = "sh", 204 | }, 205 | }; 206 | size_t const conf_metab_size = ARRAY_SIZE(conf_metab); 207 | -------------------------------------------------------------------------------- /src/draw.c: -------------------------------------------------------------------------------- 1 | #include "draw.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util.h" 12 | 13 | struct cell 14 | { 15 | wchar_t wch; 16 | uint8_t fg, bg; 17 | }; 18 | 19 | static void sigwinch_handler(int arg); 20 | 21 | static struct cell *cells; 22 | static struct winsize ws; 23 | 24 | void 25 | draw_init(void) 26 | { 27 | fputws(L"\033[?25l", stdout); 28 | 29 | ioctl(0, TIOCGWINSZ, &ws); 30 | 31 | cells = malloc(sizeof(struct cell) * ws.ws_row * ws.ws_col); 32 | 33 | struct sigaction sa; 34 | sigaction(SIGWINCH, NULL, &sa); 35 | sa.sa_handler = sigwinch_handler; 36 | sigaction(SIGWINCH, &sa, NULL); 37 | } 38 | 39 | void 40 | draw_quit(void) 41 | { 42 | free(cells); 43 | fputws(L"\033[?25h\033[0m", stdout); 44 | } 45 | 46 | void 47 | draw_clear(wchar_t wch, uint8_t fg, uint8_t bg) 48 | { 49 | draw_fill(0, 0, ws.ws_row, ws.ws_col, wch, fg, bg); 50 | } 51 | 52 | void 53 | draw_fill(unsigned pr, 54 | unsigned pc, 55 | unsigned sr, 56 | unsigned sc, 57 | wchar_t wch, 58 | uint8_t fg, 59 | uint8_t bg) 60 | { 61 | for (size_t i = pr; i < pr + sr; ++i) 62 | { 63 | if (i >= ws.ws_row) 64 | break; 65 | 66 | for (size_t j = pc; j < pc + sc; ++j) 67 | { 68 | if (j >= ws.ws_col) 69 | break; 70 | 71 | cells[ws.ws_col * i + j] = (struct cell) 72 | { 73 | .wch = wch, 74 | .fg = fg, 75 | .bg = bg, 76 | }; 77 | } 78 | } 79 | } 80 | 81 | void 82 | draw_put_wch(unsigned r, unsigned c, wchar_t wch) 83 | { 84 | if (c >= ws.ws_col || r >= ws.ws_row) 85 | return; 86 | 87 | cells[ws.ws_col * r + c].wch = wch; 88 | } 89 | 90 | void 91 | draw_put_wstr(unsigned r, unsigned c, wchar_t const *wstr) 92 | { 93 | for (wchar_t const *wc = wstr; *wc; ++wc) 94 | { 95 | if (*wc != L'\n') 96 | { 97 | cells[ws.ws_col * r + c].wch = *wc; 98 | ++c; 99 | } 100 | 101 | if (c >= ws.ws_col || *wc == L'\n') 102 | { 103 | c = 0; 104 | ++r; 105 | } 106 | 107 | if (r >= ws.ws_row) 108 | break; 109 | } 110 | } 111 | 112 | void 113 | draw_put_attr(unsigned r, unsigned c, uint8_t fg, uint8_t bg, unsigned n) 114 | { 115 | if (r >= ws.ws_row || c >= ws.ws_col) 116 | return; 117 | 118 | for (unsigned i = 0; i < n; ++i) 119 | { 120 | cells[ws.ws_col * r + c].fg = fg; 121 | cells[ws.ws_col * r + c].bg = bg; 122 | 123 | if (++c >= ws.ws_col) 124 | { 125 | c = 0; 126 | ++r; 127 | } 128 | 129 | if (r >= ws.ws_row) 130 | break; 131 | } 132 | } 133 | 134 | void 135 | draw_refresh(void) 136 | { 137 | uint8_t prev_fg = 0xff, prev_bg = 0xff; 138 | 139 | fwprintf(stdout, L"\033[H\033[0m"); 140 | 141 | for (size_t i = 0; i < ws.ws_row; ++i) 142 | { 143 | for (size_t j = 0; j < ws.ws_col; ++j) 144 | { 145 | uint8_t cur_fg = cells[ws.ws_col * i + j].fg; 146 | uint8_t cur_bg = cells[ws.ws_col * i + j].bg; 147 | 148 | if (cur_fg != prev_fg || cur_bg != prev_bg) 149 | wprintf(L"\033[38;5;%um\033[48;5;%um", cur_fg, cur_bg); 150 | 151 | fputwc(cells[ws.ws_col * i + j].wch, stdout); 152 | 153 | prev_fg = cur_fg; 154 | prev_bg = cur_bg; 155 | } 156 | } 157 | } 158 | 159 | struct win_size 160 | draw_win_size(void) 161 | { 162 | return (struct win_size) 163 | { 164 | .sr = ws.ws_row, 165 | .sc = ws.ws_col, 166 | }; 167 | } 168 | 169 | static void 170 | sigwinch_handler(int arg) 171 | { 172 | ioctl(0, TIOCGWINSZ, &ws); 173 | cells = realloc(cells, sizeof(struct cell) * ws.ws_row * ws.ws_col); 174 | } 175 | -------------------------------------------------------------------------------- /src/editor.c: -------------------------------------------------------------------------------- 1 | #include "editor.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "buf.h" 14 | #include "conf.h" 15 | #include "draw.h" 16 | #include "editor_bind.h" 17 | #include "frame.h" 18 | #include "keybd.h" 19 | #include "prompt.h" 20 | 21 | // you may want to change the value of this, but you'll need to do some testing 22 | // and play around with values to find something good. 23 | // check `sigwinch_handler()` for more information. 24 | #define WIN_RESIZE_SPIN 50000000 25 | 26 | extern bool flag_c; 27 | 28 | // global editor state. 29 | // access carefully, preferably *only* in `src/editor_bind.c`. 30 | bool editor_running; 31 | size_t editor_cur_frame; 32 | struct vec_frame editor_frames; 33 | struct vec_p_buf editor_p_bufs; 34 | wchar_t *editor_clipbuf = NULL; 35 | bool editor_mono = false; 36 | bool editor_ignore_sigwinch = false; // used for man reader hack. 37 | 38 | static void open_arg_files(int argc, int first_arg, char const *argv[]); 39 | static void sigwinch_handler(int arg); 40 | 41 | static void (*old_sigwinch_handler)(int); 42 | 43 | int 44 | editor_init(int argc, char const *argv[]) 45 | { 46 | keybd_init(); 47 | 48 | editor_frames = vec_frame_create(); 49 | editor_p_bufs = vec_p_buf_create(); 50 | editor_cur_frame = 0; 51 | 52 | int first_arg = 1; 53 | while (first_arg < argc && *argv[first_arg] == '-') 54 | ++first_arg; 55 | 56 | if (argc <= first_arg) 57 | { 58 | struct buf gb = buf_from_wstr(CONF_GREET_TEXT, false); 59 | struct frame gf = frame_create(CONF_GREET_NAME, editor_add_buf(&gb)); 60 | editor_add_frame(&gf); 61 | } 62 | else 63 | open_arg_files(argc, first_arg, argv); 64 | 65 | if (editor_frames.size == 0) 66 | { 67 | struct buf b = buf_create(true); 68 | struct frame f = frame_create(CONF_SCRAP_NAME, editor_add_buf(&b)); 69 | editor_add_frame(&f); 70 | } 71 | 72 | struct sigaction sa; 73 | sigaction(SIGWINCH, NULL, &sa); 74 | old_sigwinch_handler = sa.sa_handler; 75 | sa.sa_handler = sigwinch_handler; 76 | sigaction(SIGWINCH, &sa, NULL); 77 | 78 | editor_reset_binds(); 79 | editor_set_global_mode(); 80 | editor_arrange_frames(); 81 | editor_redraw(); 82 | 83 | return 0; 84 | } 85 | 86 | void 87 | editor_main_loop(void) 88 | { 89 | editor_running = true; 90 | 91 | while (editor_running) 92 | { 93 | // ensure frames sharing buffers are in a valid state. 94 | for (size_t i = 0; i < editor_frames.size; ++i) 95 | { 96 | struct frame *f = &editor_frames.data[i]; 97 | f->csr = MIN(f->csr, f->buf->size); 98 | } 99 | 100 | mode_update(); 101 | 102 | editor_redraw(); 103 | 104 | wint_t k = keybd_await_key(); 105 | if (k != KEYBD_IGNORE && (wcschr(L"\n\t", k) || iswprint(k))) 106 | { 107 | struct frame *f = &editor_frames.data[editor_cur_frame]; 108 | 109 | buf_write_wch(f->buf, f->csr, k); 110 | frame_mv_csr_rel(f, 0, !!(f->buf->flags & BF_WRITABLE), true); 111 | mode_key_update(k); 112 | } 113 | } 114 | } 115 | 116 | void 117 | editor_quit(void) 118 | { 119 | if (editor_clipbuf) 120 | free(editor_clipbuf); 121 | 122 | for (size_t i = 0; i < editor_frames.size; ++i) 123 | frame_destroy(&editor_frames.data[i]); 124 | 125 | for (size_t i = 0; i < editor_p_bufs.size; ++i) 126 | { 127 | buf_destroy(editor_p_bufs.data[i]); 128 | free(editor_p_bufs.data[i]); 129 | } 130 | 131 | vec_frame_destroy(&editor_frames); 132 | vec_p_buf_destroy(&editor_p_bufs); 133 | 134 | keybd_quit(); 135 | } 136 | 137 | void 138 | editor_redraw(void) 139 | { 140 | if (editor_mono) 141 | { 142 | struct frame *f = &editor_frames.data[editor_cur_frame]; 143 | frame_comp_boundary(f); 144 | frame_draw(f, FDF_ACTIVE | FDF_MONO); 145 | } 146 | else 147 | { 148 | for (size_t i = 0; i < editor_frames.size; ++i) 149 | { 150 | frame_comp_boundary(&editor_frames.data[i]); 151 | frame_draw(&editor_frames.data[i], FDF_ACTIVE * (i == editor_cur_frame)); 152 | } 153 | } 154 | 155 | // draw current bind status if necessary. 156 | size_t len; 157 | if (!keybd_is_exec_mac() && keybd_cur_bind(NULL) 158 | || keybd_is_rec_mac() && keybd_cur_mac(NULL)) 159 | { 160 | int const *src = keybd_is_rec_mac() ? keybd_cur_mac(&len) : keybd_cur_bind(&len); 161 | 162 | wchar_t dpy[(KEYBD_MAX_DPY_LEN + 1) * KEYBD_MAX_MAC_LEN]; 163 | keybd_key_dpy(dpy, src, len); 164 | 165 | struct win_size ws = draw_win_size(); 166 | size_t draw_start = 0, draw_len = wcslen(dpy); 167 | while (draw_len > ws.sc) 168 | { 169 | wchar_t const *next = wcschr(dpy + draw_start, L' ') + 1; 170 | if (!next) 171 | break; 172 | 173 | size_t diff = (uintptr_t)next - (uintptr_t)(dpy + draw_start); 174 | size_t ndiff = diff / sizeof(wchar_t); 175 | 176 | draw_start += ndiff; 177 | draw_len -= ndiff; 178 | } 179 | 180 | draw_put_wstr(ws.sr - 1, 0, dpy + draw_start); 181 | draw_put_attr(ws.sr - 1, 0, CONF_A_GHIGH_FG, CONF_A_GHIGH_BG, draw_len); 182 | } 183 | 184 | draw_refresh(); 185 | } 186 | 187 | struct buf * 188 | editor_add_buf(struct buf *b) 189 | { 190 | if (b->src_type == BST_FILE) 191 | { 192 | for (size_t i = 0; i < editor_p_bufs.size; ++i) 193 | { 194 | if (editor_p_bufs.data[i]->src_type != BST_FILE) 195 | continue; 196 | 197 | if (is_path_same(b->src, editor_p_bufs.data[i]->src)) 198 | { 199 | buf_destroy(b); 200 | return editor_p_bufs.data[i]; 201 | } 202 | } 203 | } 204 | 205 | struct buf *pb = malloc(sizeof(struct buf)); 206 | *pb = *b; 207 | vec_p_buf_add(&editor_p_bufs, &pb); 208 | 209 | return editor_p_bufs.data[editor_p_bufs.size - 1]; 210 | } 211 | 212 | struct frame * 213 | editor_add_frame(struct frame *f) 214 | { 215 | for (size_t i = 0; i < editor_frames.size; ++i) 216 | { 217 | if (editor_frames.data[i].buf == f->buf) 218 | { 219 | prompt_show(L"cannot assign multiple frames to one buffer!"); 220 | editor_redraw(); 221 | frame_destroy(f); 222 | return &editor_frames.data[i]; 223 | } 224 | } 225 | 226 | vec_frame_add(&editor_frames, f); 227 | 228 | return &editor_frames.data[editor_frames.size - 1]; 229 | } 230 | 231 | void 232 | editor_arrange_frames(void) 233 | { 234 | struct win_size ws = draw_win_size(); 235 | 236 | struct frame *f; 237 | 238 | if (editor_mono) 239 | { 240 | f = &editor_frames.data[editor_cur_frame]; 241 | f->pr = f->pc = 0; 242 | f->sr = ws.sr; 243 | f->sc = ws.sc; 244 | return; 245 | } 246 | 247 | f = &editor_frames.data[0]; 248 | f->pr = f->pc = 0; 249 | f->sr = ws.sr; 250 | f->sc = editor_frames.size == 1 ? ws.sc : CONF_MNUM * ws.sc / CONF_MDENOM; 251 | 252 | for (size_t i = 1; i < editor_frames.size; ++i) 253 | { 254 | f = &editor_frames.data[i]; 255 | 256 | f->sr = ws.sr / (editor_frames.size - 1); 257 | f->pr = (i - 1) * f->sr; 258 | f->pc = CONF_MNUM * ws.sc / CONF_MDENOM; 259 | f->sc = ws.sc - f->pc; 260 | 261 | if (f->pr + f->sr > ws.sr || i == editor_frames.size - 1) 262 | f->sr = ws.sr - f->pr; 263 | } 264 | } 265 | 266 | void 267 | editor_reset_binds(void) 268 | { 269 | // quit and reinit to reset current keybind buffer and bind information. 270 | keybd_quit(); 271 | keybd_init(); 272 | 273 | keybd_bind(conf_bind_quit, editor_bind_quit); 274 | keybd_bind(conf_bind_chg_frame_fwd, editor_bind_chg_frame_fwd); 275 | keybd_bind(conf_bind_chg_frame_back, editor_bind_chg_frame_back); 276 | keybd_bind(conf_bind_focus_frame, editor_bind_focus_frame); 277 | keybd_bind(conf_bind_kill_frame, editor_bind_kill_frame); 278 | keybd_bind(conf_bind_open_file, editor_bind_open_file); 279 | keybd_bind(conf_bind_save_file, editor_bind_save_file); 280 | keybd_bind(conf_bind_nav_fwd_ch, editor_bind_nav_fwd_ch); 281 | keybd_bind(conf_bind_nav_fwd_word, editor_bind_nav_fwd_word); 282 | keybd_bind(conf_bind_nav_fwd_page, editor_bind_nav_fwd_page); 283 | keybd_bind(conf_bind_nav_back_ch, editor_bind_nav_back_ch); 284 | keybd_bind(conf_bind_nav_back_word, editor_bind_nav_back_word); 285 | keybd_bind(conf_bind_nav_back_page, editor_bind_nav_back_page); 286 | keybd_bind(conf_bind_nav_down, editor_bind_nav_down); 287 | keybd_bind(conf_bind_nav_up, editor_bind_nav_up); 288 | keybd_bind(conf_bind_nav_ln_start, editor_bind_nav_ln_start); 289 | keybd_bind(conf_bind_nav_ln_end, editor_bind_nav_ln_end); 290 | keybd_bind(conf_bind_nav_goto, editor_bind_nav_goto); 291 | keybd_bind(conf_bind_del_fwd_ch, editor_bind_del_fwd_ch); 292 | keybd_bind(conf_bind_del_back_ch, editor_bind_del_back_ch); 293 | keybd_bind(conf_bind_del_back_word, editor_bind_del_back_word); 294 | keybd_bind(conf_bind_chg_mode_global, editor_bind_chg_mode_global); 295 | keybd_bind(conf_bind_chg_mode_local, editor_bind_chg_mode_local); 296 | keybd_bind(conf_bind_create_scrap, editor_bind_create_scrap); 297 | keybd_bind(conf_bind_new_line, editor_bind_new_line); 298 | keybd_bind(conf_bind_focus, editor_bind_focus); 299 | keybd_bind(conf_bind_kill, editor_bind_kill); 300 | keybd_bind(conf_bind_paste, editor_bind_paste); 301 | keybd_bind(conf_bind_undo, editor_bind_undo); 302 | keybd_bind(conf_bind_copy, editor_bind_copy); 303 | keybd_bind(conf_bind_ncopy, editor_bind_ncopy); 304 | keybd_bind(conf_bind_find_lit, editor_bind_find_lit); 305 | keybd_bind(conf_bind_mac_begin, editor_bind_mac_begin); 306 | keybd_bind(conf_bind_mac_end, editor_bind_mac_end); 307 | keybd_bind(conf_bind_toggle_mono, editor_bind_toggle_mono); 308 | keybd_bind(conf_bind_read_man_word, editor_bind_read_man_word); 309 | keybd_bind(conf_bind_file_exp, editor_bind_file_exp); 310 | 311 | keybd_organize(); 312 | } 313 | 314 | void 315 | editor_set_global_mode(void) 316 | { 317 | struct frame *f = &editor_frames.data[editor_cur_frame]; 318 | if (f->buf->src_type != BST_FILE) 319 | return; 320 | 321 | char const *buf_ext = file_ext(f->buf->src); 322 | for (size_t i = 0; i < conf_metab_size; ++i) 323 | { 324 | for (char const **ext = conf_metab[i].exts; *ext; ++ext) 325 | { 326 | if (!strcmp(buf_ext, *ext)) 327 | { 328 | mode_set(conf_metab[i].globalmode, f); 329 | return; 330 | } 331 | } 332 | } 333 | 334 | mode_set(NULL, f); 335 | } 336 | 337 | static void 338 | open_arg_files(int argc, int first_arg, char const *argv[]) 339 | { 340 | for (int i = first_arg; i < argc; ++i) 341 | { 342 | draw_clear(L' ', CONF_A_GNORM_FG, CONF_A_GNORM_BG); 343 | 344 | struct stat s; 345 | if ((stat(argv[i], &s) || !S_ISREG(s.st_mode)) && !flag_c) 346 | { 347 | size_t wmsg_len = strlen(argv[i]) + 22; 348 | wchar_t *wmsg = malloc(sizeof(wchar_t) * wmsg_len); 349 | mbstowcs(wmsg + 21, argv[i], wmsg_len); 350 | wcsncpy(wmsg, L"failed to open file: ", 21); 351 | 352 | prompt_show(wmsg); 353 | free(wmsg); 354 | 355 | continue; 356 | } 357 | else if (stat(argv[i], &s) && flag_c) 358 | { 359 | if (mk_file(argv[i])) 360 | { 361 | size_t wmsg_len = strlen(argv[i]) + 24; 362 | wchar_t *wmsg = malloc(sizeof(wchar_t) * wmsg_len); 363 | mbstowcs(wmsg + 23, argv[i], wmsg_len); 364 | wcsncpy(wmsg, L"failed to create file: ", 23); 365 | 366 | prompt_show(wmsg); 367 | free(wmsg); 368 | 369 | continue; 370 | } 371 | } 372 | 373 | size_t wname_len = strlen(argv[i]) + 1; 374 | wchar_t *wname = malloc(sizeof(wchar_t) * wname_len); 375 | mbstowcs(wname, argv[i], wname_len); 376 | 377 | struct buf b = buf_from_file(argv[i]); 378 | struct frame f = frame_create(wname, editor_add_buf(&b)); 379 | editor_add_frame(&f); 380 | 381 | free(wname); 382 | } 383 | } 384 | 385 | static void 386 | sigwinch_handler(int arg) 387 | { 388 | if (editor_ignore_sigwinch) 389 | return; 390 | 391 | // small wait necessary for window size to stabilize. 392 | // sometimes, when resized, I've noticed that the terminal emulator will 393 | // switch to a certain window size, then almost instantly switch to 394 | // something else. 395 | // spinning for a bit will allow the window to settle on its final size, 396 | // before doing any kind of rendering or draw system changes, so that the 397 | // visual output doesn't fuck itself until an update. 398 | for (int volatile i = 0; i < WIN_RESIZE_SPIN; ++i) 399 | ; 400 | 401 | old_sigwinch_handler(arg); 402 | editor_arrange_frames(); 403 | editor_redraw(); 404 | } 405 | -------------------------------------------------------------------------------- /src/editor_bind.c: -------------------------------------------------------------------------------- 1 | #include "editor_bind.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "conf.h" 14 | #include "editor.h" 15 | #include "file_exp.h" 16 | #include "frame.h" 17 | #include "keybd.h" 18 | #include "label.h" 19 | #include "prompt.h" 20 | 21 | extern bool editor_running; 22 | extern size_t editor_cur_frame; 23 | extern struct vec_frame editor_frames; 24 | extern struct vec_p_buf editor_p_bufs; 25 | extern wchar_t *editor_clipbuf; 26 | extern bool editor_mono; 27 | extern bool editor_ignore_sigwinch; 28 | 29 | void 30 | editor_bind_quit(void) 31 | { 32 | bool mod_exists = false; 33 | for (size_t i = 0; i < editor_p_bufs.size; ++i) 34 | { 35 | if (editor_p_bufs.data[i]->flags & BF_MODIFIED) 36 | { 37 | mod_exists = true; 38 | break; 39 | } 40 | } 41 | 42 | if (mod_exists) 43 | { 44 | int confirm = prompt_yes_no(L"there are unsaved modified buffers! quit anyway?", false); 45 | editor_redraw(); 46 | if (confirm != 1) 47 | return; 48 | } 49 | 50 | editor_running = false; 51 | } 52 | 53 | void 54 | editor_bind_chg_frame_fwd(void) 55 | { 56 | editor_cur_frame = (editor_cur_frame + 1) % editor_frames.size; 57 | editor_reset_binds(); 58 | editor_set_global_mode(); 59 | if (editor_mono) 60 | editor_arrange_frames(); 61 | editor_redraw(); 62 | } 63 | 64 | void 65 | editor_bind_chg_frame_back(void) 66 | { 67 | if (editor_cur_frame == 0) 68 | editor_cur_frame = editor_frames.size - 1; 69 | else 70 | editor_cur_frame -= 1; 71 | 72 | editor_reset_binds(); 73 | editor_set_global_mode(); 74 | if (editor_mono) 75 | editor_arrange_frames(); 76 | editor_redraw(); 77 | } 78 | 79 | void 80 | editor_bind_focus_frame(void) 81 | { 82 | if (editor_cur_frame != 0) 83 | { 84 | vec_frame_swap(&editor_frames, 0, editor_cur_frame); 85 | editor_cur_frame = 0; 86 | editor_reset_binds(); 87 | editor_set_global_mode(); 88 | editor_arrange_frames(); 89 | editor_redraw(); 90 | } 91 | } 92 | 93 | void 94 | editor_bind_kill_frame(void) 95 | { 96 | int confirm = prompt_yes_no(L"kill active frame?", false); 97 | editor_redraw(); 98 | if (confirm != 1) 99 | return; 100 | 101 | frame_destroy(&editor_frames.data[editor_cur_frame]); 102 | vec_frame_rm(&editor_frames, editor_cur_frame); 103 | editor_cur_frame = editor_cur_frame > 0 ? editor_cur_frame - 1 : 0; 104 | 105 | // destroy orphaned buffers. 106 | for (size_t i = 0; i < editor_p_bufs.size; ++i) 107 | { 108 | bool orphan = true; 109 | for (size_t j = 0; j < editor_frames.size; ++j) 110 | { 111 | if (editor_p_bufs.data[i] == editor_frames.data[j].buf) 112 | { 113 | orphan = false; 114 | break; 115 | } 116 | } 117 | 118 | if (orphan) 119 | { 120 | buf_destroy(editor_p_bufs.data[i]); 121 | free(editor_p_bufs.data[i]); 122 | vec_p_buf_rm(&editor_p_bufs, i--); 123 | } 124 | } 125 | 126 | if (editor_frames.size == 0) 127 | { 128 | struct buf b = buf_create(true); 129 | struct frame f = frame_create(CONF_SCRAP_NAME, editor_add_buf(&b)); 130 | editor_add_frame(&f); 131 | } 132 | 133 | editor_reset_binds(); 134 | editor_set_global_mode(); 135 | editor_arrange_frames(); 136 | editor_redraw(); 137 | } 138 | 139 | void 140 | editor_bind_open_file(void) 141 | { 142 | wchar_t *wpath = prompt_ask(L"open file: ", prompt_comp_path); 143 | editor_redraw(); 144 | if (!wpath) 145 | return; 146 | 147 | size_t path_size = sizeof(wchar_t) * (wcslen(wpath) + 1); 148 | char *path = malloc(path_size); 149 | wcstombs(path, wpath, path_size); 150 | 151 | struct stat s; 152 | if (stat(path, &s) || !S_ISREG(s.st_mode)) 153 | { 154 | prompt_show(L"could not open file!"); 155 | editor_redraw(); 156 | free(path); 157 | free(wpath); 158 | return; 159 | } 160 | 161 | struct buf buf = buf_from_file(path); 162 | struct frame frame = frame_create(wpath, editor_add_buf(&buf)); 163 | editor_add_frame(&frame); 164 | 165 | editor_cur_frame = editor_frames.size - 1; 166 | editor_reset_binds(); 167 | editor_set_global_mode(); 168 | editor_arrange_frames(); 169 | editor_redraw(); 170 | 171 | free(path); 172 | free(wpath); 173 | } 174 | 175 | void 176 | editor_bind_save_file(void) 177 | { 178 | struct frame *f = &editor_frames.data[editor_cur_frame]; 179 | enum buf_src_type prev_type = f->buf->src_type; 180 | uint8_t prev_flags = f->buf->flags; 181 | 182 | wchar_t *w_path; 183 | if (f->buf->src_type == BST_FRESH) 184 | { 185 | f->buf->src_type = BST_FILE; 186 | 187 | w_path = prompt_ask(L"save to file: ", prompt_comp_path); 188 | editor_redraw(); 189 | 190 | if (w_path) 191 | { 192 | size_t path_size = sizeof(wchar_t) * (wcslen(w_path) + 1); 193 | char *path = malloc(path_size); 194 | wcstombs(path, w_path, path_size); 195 | 196 | f->buf->src = strdup(path); 197 | free(path); 198 | 199 | // force resave and allow editing of newly made file. 200 | f->buf->flags |= BF_MODIFIED | BF_WRITABLE; 201 | } 202 | else 203 | f->buf->src = NULL; 204 | } 205 | 206 | // no path was given as source for new buffer, so the buffer state is 207 | // reset to how it was before the save. 208 | if (f->buf->src && !*(uint8_t *)f->buf->src) 209 | { 210 | prompt_show(L"no save path given, nothing will be done!"); 211 | editor_redraw(); 212 | } 213 | 214 | if (!f->buf->src || !*(uint8_t *)f->buf->src) 215 | { 216 | f->buf->src_type = prev_type; 217 | f->buf->flags = prev_flags; 218 | 219 | if (w_path) 220 | free(w_path); 221 | 222 | return; 223 | } 224 | 225 | if (prev_type == BST_FRESH) 226 | mk_file(f->buf->src); 227 | 228 | if (buf_save(f->buf)) 229 | { 230 | prompt_show(L"failed to write file!"); 231 | editor_redraw(); 232 | free(w_path); 233 | return; 234 | } 235 | 236 | if (prev_type == BST_FRESH) 237 | { 238 | free(f->name); 239 | f->name = w_path; 240 | } 241 | 242 | if (!f->local_mode[0]) 243 | { 244 | free(f->local_mode); 245 | f->local_mode = strdup(file_ext(f->buf->src)); 246 | } 247 | 248 | if (!mode_get()) 249 | editor_set_global_mode(); 250 | } 251 | 252 | void 253 | editor_bind_nav_fwd_ch(void) 254 | { 255 | struct frame *f = &editor_frames.data[editor_cur_frame]; 256 | frame_mv_csr_rel(f, 0, 1, true); 257 | } 258 | 259 | void 260 | editor_bind_nav_fwd_word(void) 261 | { 262 | struct frame *f = &editor_frames.data[editor_cur_frame]; 263 | 264 | while (f->csr < f->buf->size && !iswalnum(buf_get_wch(f->buf, f->csr))) 265 | ++f->csr; 266 | while (f->csr < f->buf->size && iswalnum(buf_get_wch(f->buf, f->csr))) 267 | ++f->csr; 268 | 269 | ++f->csr; 270 | frame_mv_csr_rel(f, 0, -1, true); 271 | } 272 | 273 | void 274 | editor_bind_nav_fwd_page(void) 275 | { 276 | struct frame *f = &editor_frames.data[editor_cur_frame]; 277 | 278 | f->csr = f->buf_start; 279 | frame_mv_csr_rel(f, f->sr - 1, 0, false); 280 | 281 | f->buf_start = f->csr; 282 | while (f->buf_start > 0 283 | && buf_get_wch(f->buf, f->buf_start - 1) != L'\n') 284 | { 285 | --f->buf_start; 286 | } 287 | 288 | frame_comp_boundary(f); 289 | } 290 | 291 | void 292 | editor_bind_nav_back_ch(void) 293 | { 294 | struct frame *f = &editor_frames.data[editor_cur_frame]; 295 | frame_mv_csr_rel(f, 0, -1, true); 296 | } 297 | 298 | void 299 | editor_bind_nav_back_word(void) 300 | { 301 | struct frame *f = &editor_frames.data[editor_cur_frame]; 302 | 303 | while (f->csr > 0 && !iswalnum(buf_get_wch(f->buf, f->csr - 1))) 304 | --f->csr; 305 | while (f->csr > 0 && iswalnum(buf_get_wch(f->buf, f->csr - 1))) 306 | --f->csr; 307 | 308 | ++f->csr; 309 | frame_mv_csr_rel(f, 0, -1, true); 310 | } 311 | 312 | void 313 | editor_bind_nav_back_page(void) 314 | { 315 | struct frame *f = &editor_frames.data[editor_cur_frame]; 316 | f->csr = f->buf_start; 317 | frame_mv_csr_rel(f, 1 - f->sr, 0, false); 318 | } 319 | 320 | void 321 | editor_bind_nav_down(void) 322 | { 323 | struct frame *f = &editor_frames.data[editor_cur_frame]; 324 | frame_mv_csr_rel(f, 1, 0, false); 325 | } 326 | 327 | void 328 | editor_bind_nav_up(void) 329 | { 330 | struct frame *f = &editor_frames.data[editor_cur_frame]; 331 | frame_mv_csr_rel(f, -1, 0, false); 332 | } 333 | 334 | void 335 | editor_bind_nav_ln_start(void) 336 | { 337 | struct frame *f = &editor_frames.data[editor_cur_frame]; 338 | frame_mv_csr_rel(f, 0, -INT_MAX, false); 339 | } 340 | 341 | void 342 | editor_bind_nav_ln_end(void) 343 | { 344 | struct frame *f = &editor_frames.data[editor_cur_frame]; 345 | frame_mv_csr_rel(f, 0, INT_MAX, false); 346 | } 347 | 348 | void 349 | editor_bind_nav_goto(void) 350 | { 351 | ask_again:; 352 | wchar_t *ws_linum = prompt_ask(L"goto line: ", NULL); 353 | editor_redraw(); 354 | if (!ws_linum) 355 | return; 356 | 357 | if (!*ws_linum) 358 | { 359 | free(ws_linum); 360 | prompt_show(L"expected a line number!"); 361 | editor_redraw(); 362 | goto ask_again; 363 | } 364 | 365 | size_t s_linum_size = sizeof(wchar_t) * (wcslen(ws_linum) + 1); 366 | char *s_linum = malloc(s_linum_size); 367 | wcstombs(s_linum, ws_linum, s_linum_size); 368 | free(ws_linum); 369 | 370 | for (char const *c = s_linum; *c; ++c) 371 | { 372 | if (!isdigit(*c)) 373 | { 374 | free(s_linum); 375 | prompt_show(L"invalid line number!"); 376 | editor_redraw(); 377 | goto ask_again; 378 | } 379 | } 380 | 381 | unsigned linum = atoi(s_linum); 382 | free(s_linum); 383 | linum -= linum != 0; 384 | 385 | frame_mv_csr(&editor_frames.data[editor_cur_frame], linum, 0); 386 | } 387 | 388 | void 389 | editor_bind_del_fwd_ch(void) 390 | { 391 | struct frame *f = &editor_frames.data[editor_cur_frame]; 392 | if (f->csr < f->buf->size) 393 | buf_erase(f->buf, f->csr, f->csr + 1); 394 | } 395 | 396 | void 397 | editor_bind_del_back_ch(void) 398 | { 399 | struct frame *f = &editor_frames.data[editor_cur_frame]; 400 | 401 | if (f->csr > 0 && f->buf->flags & BF_WRITABLE) 402 | { 403 | frame_mv_csr_rel(f, 0, -1, true); 404 | buf_erase(f->buf, f->csr, f->csr + 1); 405 | frame_comp_boundary(f); 406 | } 407 | } 408 | 409 | void 410 | editor_bind_del_back_word(void) 411 | { 412 | struct frame *f = &editor_frames.data[editor_cur_frame]; 413 | if (!(f->buf->flags & BF_WRITABLE)) 414 | return; 415 | 416 | size_t ub = f->csr; 417 | 418 | while (f->csr > 0 && !iswalnum(buf_get_wch(f->buf, f->csr - 1))) 419 | --f->csr; 420 | while (f->csr > 0 && iswalnum(buf_get_wch(f->buf, f->csr - 1))) 421 | --f->csr; 422 | 423 | ++f->csr; 424 | frame_mv_csr_rel(f, 0, -1, true); 425 | buf_erase(f->buf, f->csr, ub); 426 | } 427 | 428 | void 429 | editor_bind_chg_mode_global(void) 430 | { 431 | wchar_t *w_new_gm = prompt_ask(L"new globalmode: ", NULL); 432 | editor_redraw(); 433 | if (!w_new_gm) 434 | return; 435 | 436 | size_t new_gm_size = sizeof(wchar_t) * (wcslen(w_new_gm) + 1); 437 | char *new_gm = malloc(new_gm_size); 438 | wcstombs(new_gm, w_new_gm, new_gm_size); 439 | free(w_new_gm); 440 | 441 | editor_reset_binds(); 442 | mode_set(new_gm, &editor_frames.data[editor_cur_frame]); 443 | 444 | free(new_gm); 445 | } 446 | 447 | void 448 | editor_bind_chg_mode_local(void) 449 | { 450 | wchar_t *w_new_lm = prompt_ask(L"new frame localmode: ", NULL); 451 | editor_redraw(); 452 | if (!w_new_lm) 453 | return; 454 | 455 | size_t new_lm_size = sizeof(wchar_t) * (wcslen(w_new_lm) + 1); 456 | char *new_lm = malloc(new_lm_size); 457 | wcstombs(new_lm, w_new_lm, new_lm_size); 458 | free(w_new_lm); 459 | 460 | free(editor_frames.data[editor_cur_frame].local_mode); 461 | editor_frames.data[editor_cur_frame].local_mode = strdup(new_lm); 462 | 463 | free(new_lm); 464 | } 465 | 466 | void 467 | editor_bind_create_scrap(void) 468 | { 469 | struct buf b = buf_create(true); 470 | struct frame f = frame_create(CONF_SCRAP_NAME, editor_add_buf(&b)); 471 | editor_add_frame(&f); 472 | 473 | editor_cur_frame = editor_frames.size - 1; 474 | editor_reset_binds(); 475 | editor_set_global_mode(); 476 | editor_arrange_frames(); 477 | editor_redraw(); 478 | } 479 | 480 | void 481 | editor_bind_new_line(void) 482 | { 483 | struct frame *f = &editor_frames.data[editor_cur_frame]; 484 | buf_push_hist_brk(f->buf); 485 | buf_write_wch(f->buf, f->csr, L'\n'); 486 | frame_mv_csr_rel(f, 0, !!(f->buf->flags & BF_WRITABLE), true); 487 | } 488 | 489 | void 490 | editor_bind_focus(void) 491 | { 492 | struct frame *f = &editor_frames.data[editor_cur_frame]; 493 | 494 | unsigned csrr, csrc; 495 | buf_pos(f->buf, f->csr, &csrr, &csrc); 496 | 497 | f->buf_start = 0; 498 | unsigned bsr = 0; 499 | long dst_bsr = (long)csrr - (f->sr - 1) / 2; 500 | dst_bsr = MAX(dst_bsr, 0); 501 | while (f->buf_start < f->buf->size && bsr < dst_bsr) 502 | { 503 | if (buf_get_wch(f->buf, f->buf_start++) == L'\n') 504 | ++bsr; 505 | } 506 | 507 | frame_comp_boundary(f); 508 | } 509 | 510 | void 511 | editor_bind_kill(void) 512 | { 513 | struct frame *f = &editor_frames.data[editor_cur_frame]; 514 | if (!(f->buf->flags & BF_WRITABLE)) 515 | return; 516 | 517 | if (editor_clipbuf) 518 | free(editor_clipbuf); 519 | 520 | size_t ln_end = f->csr; 521 | while (ln_end < f->buf->size && buf_get_wch(f->buf, ln_end) != L'\n') 522 | ++ln_end; 523 | 524 | editor_clipbuf = malloc((ln_end - f->csr + 1) * sizeof(wchar_t)); 525 | buf_get_wstr(f->buf, editor_clipbuf, f->csr, ln_end - f->csr + 1); 526 | 527 | buf_erase(f->buf, f->csr, ln_end); 528 | } 529 | 530 | void 531 | editor_bind_paste(void) 532 | { 533 | struct frame *f = &editor_frames.data[editor_cur_frame]; 534 | if (!(f->buf->flags & BF_WRITABLE)) 535 | return; 536 | 537 | if (!editor_clipbuf) 538 | { 539 | prompt_show(L"clipbuffer is empty!"); 540 | editor_redraw(); 541 | return; 542 | } 543 | 544 | buf_write_wstr(f->buf, f->csr, editor_clipbuf); 545 | f->csr += wcslen(editor_clipbuf) + 1; 546 | frame_mv_csr_rel(f, 0, -1, true); 547 | frame_comp_boundary(f); 548 | } 549 | 550 | void 551 | editor_bind_undo(void) 552 | { 553 | struct frame *f = &editor_frames.data[editor_cur_frame]; 554 | 555 | if (f->buf->hist.size == 0) 556 | { 557 | prompt_show(L"no further undo information!"); 558 | editor_redraw(); 559 | return; 560 | } 561 | 562 | struct buf_op const *bo = &f->buf->hist.data[f->buf->hist.size - 1]; 563 | while (bo > f->buf->hist.data && bo->type == BOT_BRK) 564 | --bo; 565 | 566 | size_t csr_dst; 567 | switch (bo->type) 568 | { 569 | case BOT_WRITE: 570 | csr_dst = bo->lb; 571 | break; 572 | case BOT_ERASE: 573 | csr_dst = bo->ub; 574 | break; 575 | } 576 | 577 | if (buf_undo(f->buf)) 578 | { 579 | prompt_show(L"failed to undo last operation!"); 580 | editor_redraw(); 581 | return; 582 | } 583 | 584 | unsigned csrr, csrc; 585 | buf_pos(f->buf, csr_dst, &csrr, &csrc); 586 | frame_mv_csr(f, csrr, csrc); 587 | f->csr_want_col = csrc; 588 | } 589 | 590 | void 591 | editor_bind_copy(void) 592 | { 593 | if (editor_clipbuf) 594 | free(editor_clipbuf); 595 | 596 | struct frame *f = &editor_frames.data[editor_cur_frame]; 597 | 598 | size_t ln = f->csr; 599 | while (ln > 0 && buf_get_wch(f->buf, ln - 1) != L'\n') 600 | --ln; 601 | 602 | size_t ln_end = f->csr; 603 | while (ln_end < f->buf->size && buf_get_wch(f->buf, ln_end) != L'\n') 604 | ++ln_end; 605 | 606 | editor_clipbuf = malloc((ln_end - ln + 1) * sizeof(wchar_t)); 607 | buf_get_wstr(f->buf, editor_clipbuf, ln, ln_end - ln + 1); 608 | } 609 | 610 | void 611 | editor_bind_ncopy(void) 612 | { 613 | ask_again:; 614 | wchar_t *ws_ln_cnt = prompt_ask(L"copy lines: ", NULL); 615 | editor_redraw(); 616 | if (!ws_ln_cnt) 617 | return; 618 | 619 | if (!*ws_ln_cnt) 620 | { 621 | free(ws_ln_cnt); 622 | prompt_show(L"expected a line count!"); 623 | editor_redraw(); 624 | goto ask_again; 625 | } 626 | 627 | size_t s_ln_cnt_size = sizeof(wchar_t) * (wcslen(ws_ln_cnt) + 1); 628 | char *s_ln_cnt = malloc(s_ln_cnt_size); 629 | wcstombs(s_ln_cnt, ws_ln_cnt, s_ln_cnt_size); 630 | free(ws_ln_cnt); 631 | 632 | for (char const *c = s_ln_cnt; *c; ++c) 633 | { 634 | if (!isdigit(*c)) 635 | { 636 | free(s_ln_cnt); 637 | prompt_show(L"invalid line count!"); 638 | editor_redraw(); 639 | goto ask_again; 640 | } 641 | } 642 | 643 | unsigned ln_cnt = atoi(s_ln_cnt); 644 | free(s_ln_cnt); 645 | if (!ln_cnt) 646 | { 647 | prompt_show(L"cannot copy zero lines!"); 648 | editor_redraw(); 649 | goto ask_again; 650 | } 651 | 652 | if (editor_clipbuf) 653 | free(editor_clipbuf); 654 | 655 | struct frame *f = &editor_frames.data[editor_cur_frame]; 656 | 657 | size_t reg_lb = f->csr; 658 | while (reg_lb > 0 && buf_get_wch(f->buf, reg_lb - 1) != L'\n') 659 | --reg_lb; 660 | 661 | size_t reg_ub = f->csr; 662 | while (reg_ub < f->buf->size && ln_cnt > 0) 663 | { 664 | while (buf_get_wch(f->buf, reg_ub) != L'\n') 665 | ++reg_ub; 666 | --ln_cnt; 667 | reg_ub += ln_cnt > 0; 668 | } 669 | 670 | editor_clipbuf = malloc((reg_ub - reg_lb + 1) * sizeof(wchar_t)); 671 | buf_get_wstr(f->buf, editor_clipbuf, reg_lb, reg_ub - reg_lb + 1); 672 | } 673 | 674 | void 675 | editor_bind_find_lit(void) 676 | { 677 | ask_again:; 678 | wchar_t *needle = prompt_ask(L"find literally: ", NULL); 679 | editor_redraw(); 680 | if (!needle) 681 | return; 682 | 683 | if (!*needle) 684 | { 685 | free(needle); 686 | prompt_show(L"expected a needle!"); 687 | editor_redraw(); 688 | goto ask_again; 689 | } 690 | 691 | size_t search_pos, needle_len = wcslen(needle); 692 | wchar_t *cmp_buf = malloc(sizeof(wchar_t) * (needle_len + 1)); 693 | struct frame *f = &editor_frames.data[editor_cur_frame]; 694 | for (search_pos = f->csr + 1; search_pos + needle_len <= f->buf->size; ++search_pos) 695 | { 696 | if (!wcscmp(buf_get_wstr(f->buf, cmp_buf, search_pos, needle_len + 1), needle)) 697 | break; 698 | } 699 | 700 | free(cmp_buf); 701 | free(needle); 702 | 703 | if (search_pos + needle_len > f->buf->size) 704 | { 705 | prompt_show(L"did not find needle in haystack!"); 706 | editor_redraw(); 707 | return; 708 | } 709 | 710 | unsigned r, c; 711 | buf_pos(f->buf, search_pos, &r, &c); 712 | frame_mv_csr(f, r, c); 713 | } 714 | 715 | void 716 | editor_bind_mac_begin(void) 717 | { 718 | keybd_rec_mac_begin(); 719 | } 720 | 721 | void 722 | editor_bind_mac_end(void) 723 | { 724 | if (keybd_is_rec_mac()) 725 | keybd_rec_mac_end(); 726 | else 727 | { 728 | keybd_rec_mac_end(); 729 | 730 | if (!keybd_cur_mac(NULL)) 731 | { 732 | prompt_show(L"no macro specified to execute!"); 733 | editor_redraw(); 734 | return; 735 | } 736 | 737 | keybd_exec_mac(); 738 | } 739 | } 740 | 741 | void 742 | editor_bind_toggle_mono(void) 743 | { 744 | editor_mono = !editor_mono; 745 | editor_arrange_frames(); 746 | editor_redraw(); 747 | } 748 | 749 | void 750 | editor_bind_read_man_word(void) 751 | { 752 | struct frame *f = &editor_frames.data[editor_cur_frame]; 753 | 754 | // get label render parameters. 755 | unsigned csrr, csrc; 756 | frame_pos(f, f->csr, &csrr, &csrc); 757 | csrr += f->pr; 758 | csrc += f->pc; 759 | 760 | struct label_bounds bounds = 761 | { 762 | .pr = csrr, 763 | .pc = csrc + 1, 764 | .sr = CONF_READ_MAN_SR, 765 | .sc = CONF_READ_MAN_SC, 766 | }; 767 | if (label_rebound(&bounds, csrc + 1, MAX(0, (long)csrc - 1), csrr)) 768 | { 769 | prompt_show(L"could not find valid anchoring for label!"); 770 | editor_redraw(); 771 | return; 772 | } 773 | 774 | // manpages generally have alphanumeric chars, underscores, hyphens, and 775 | // periods in their names; so only those are supported here. 776 | 777 | if (f->csr >= f->buf->size) 778 | { 779 | prompt_show(L"cannot get manpage for null name!"); 780 | editor_redraw(); 781 | return; 782 | } 783 | 784 | wchar_t wch = buf_get_wch(f->buf, f->csr); 785 | if (!iswalnum(wch) && !wcschr(L"_-.", wch)) 786 | { 787 | prompt_show(L"cannot get manpage for unsupported name!"); 788 | editor_redraw(); 789 | return; 790 | } 791 | 792 | // get currently hovered word. 793 | size_t word_start = f->csr; 794 | while (word_start > 0) 795 | { 796 | wchar_t wch = buf_get_wch(f->buf, word_start - 1); 797 | if (!iswalnum(wch) && !wcschr(L"_-.", wch)) 798 | break; 799 | --word_start; 800 | } 801 | 802 | size_t word_len = 1; 803 | while (word_start + word_len < f->buf->size) 804 | { 805 | wchar_t wch = buf_get_wch(f->buf, word_start + word_len); 806 | if (!iswalnum(wch) && !wcschr(L"_-.", wch)) 807 | break; 808 | ++word_len; 809 | } 810 | 811 | size_t wword_size = sizeof(wchar_t) * (word_len + 1); 812 | wchar_t *wword = malloc(wword_size); 813 | buf_get_wstr(f->buf, wword, word_start, word_len + 1); 814 | 815 | char *word = malloc(wword_size); 816 | wcstombs(word, wword, wword_size); 817 | free(wword); 818 | 819 | // format word into command. 820 | char *cmd = malloc(2); 821 | size_t cmd_len = 0, cmd_cap = 1; 822 | size_t cmd_fmt_len = strlen(CONF_READ_MAN_CMD); 823 | 824 | for (size_t i = 0; i < cmd_fmt_len; ++i) 825 | { 826 | if (CONF_READ_MAN_CMD[i] != '%') 827 | { 828 | if (++cmd_len > cmd_cap) 829 | { 830 | cmd_cap *= 2; 831 | cmd = realloc(cmd, cmd_cap + 1); 832 | } 833 | cmd[cmd_len - 1] = CONF_READ_MAN_CMD[i]; 834 | continue; 835 | } 836 | 837 | switch (CONF_READ_MAN_CMD[i + 1]) 838 | { 839 | case 0: 840 | case '%': 841 | if (++cmd_len > cmd_cap) 842 | { 843 | cmd_cap *= 2; 844 | cmd = realloc(cmd, cmd_cap + 1); 845 | } 846 | cmd[cmd_len - 1] = '%'; 847 | break; 848 | case 'w': 849 | for (size_t j = 0; j < word_len; ++j) 850 | { 851 | if (++cmd_len > cmd_cap) 852 | { 853 | cmd_cap *= 2; 854 | cmd = realloc(cmd, cmd_cap + 1); 855 | } 856 | cmd[cmd_len - 1] = word[j]; 857 | } 858 | break; 859 | default: 860 | break; 861 | } 862 | 863 | ++i; // skip format code. 864 | } 865 | cmd[cmd_len] = 0; 866 | free(word); 867 | 868 | // read man pipe into message buffer. 869 | // terminal size is temporarily set to fit the label bounds in order to 870 | // trick man into generating output of a suitable size and spacing. 871 | // used temporary `winsize` has two extra columns to make man output 872 | // text without right-hand padding, which would be wasted space here. 873 | struct winsize sv_ws; 874 | ioctl(0, TIOCGWINSZ, &sv_ws); 875 | 876 | struct winsize tmp_ws = 877 | { 878 | .ws_row = bounds.sr, 879 | .ws_col = bounds.sc + 1, 880 | }; 881 | editor_ignore_sigwinch = true; 882 | ioctl(0, TIOCSWINSZ, &tmp_ws); 883 | 884 | FILE *man_fp = popen(cmd, "r"); 885 | free(cmd); 886 | 887 | if (!man_fp) 888 | { 889 | ioctl(0, TIOCSWINSZ, &sv_ws); 890 | editor_ignore_sigwinch = false; 891 | prompt_show(L"failed to open pipe to man!"); 892 | editor_redraw(); 893 | return; 894 | } 895 | 896 | // this should maybe eventually be changed. 897 | wchar_t *msg = malloc(sizeof(wchar_t)); 898 | size_t msg_size = 0, msg_cap = 1; 899 | int ch; 900 | while ((ch = fgetc(man_fp)) != EOF) 901 | { 902 | if (++msg_size > msg_cap) 903 | { 904 | msg_cap *= 2; 905 | msg = realloc(msg, sizeof(wchar_t) * (msg_cap + 1)); 906 | } 907 | msg[msg_size - 1] = ch; 908 | } 909 | msg[msg_size] = 0; 910 | ioctl(0, TIOCSWINSZ, &sv_ws); 911 | editor_ignore_sigwinch = false; 912 | 913 | if (pclose(man_fp)) 914 | { 915 | prompt_show(L"man exited with error!"); 916 | editor_redraw(); 917 | free(msg); 918 | return; 919 | } 920 | 921 | // draw label with message. 922 | editor_redraw(); 923 | if (label_show(CONF_READ_MAN_TITLE, msg, &bounds)) 924 | { 925 | prompt_show(L"failed to show label!"); 926 | editor_redraw(); 927 | free(msg); 928 | return; 929 | } 930 | 931 | free(msg); 932 | editor_redraw(); 933 | } 934 | 935 | void 936 | editor_bind_file_exp(void) 937 | { 938 | char path[PATH_MAX + 1] = {0}; 939 | 940 | editor_redraw(); 941 | switch (file_exp_open(path, PATH_MAX, ".")) 942 | { 943 | case FER_SUCCESS: 944 | break; 945 | case FER_FAIL: 946 | prompt_show(L"failed to open file explorer!"); 947 | editor_redraw(); 948 | return; 949 | case FER_QUIT: 950 | editor_redraw(); 951 | return; 952 | } 953 | 954 | size_t wpath_len = strlen(path) + 1; 955 | wchar_t *wpath = malloc(sizeof(wchar_t) * wpath_len); 956 | mbstowcs(wpath, path, wpath_len); 957 | 958 | struct buf buf = buf_from_file(path); 959 | struct frame frame = frame_create(wpath, editor_add_buf(&buf)); 960 | editor_add_frame(&frame); 961 | 962 | editor_cur_frame = editor_frames.size - 1; 963 | editor_reset_binds(); 964 | editor_set_global_mode(); 965 | editor_arrange_frames(); 966 | editor_redraw(); 967 | 968 | free(wpath); 969 | } 970 | -------------------------------------------------------------------------------- /src/file_exp.c: -------------------------------------------------------------------------------- 1 | #include "file_exp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "conf.h" 12 | #include "draw.h" 13 | #include "keybd.h" 14 | #include "util.h" 15 | 16 | #define BIND_RET K_RET 17 | #define BIND_QUIT K_CTL('g') 18 | #define BIND_NAV_DOWN K_CTL('n') 19 | #define BIND_NAV_UP K_CTL('p') 20 | 21 | VEC_DEF_PROTO_STATIC(struct dir_node *, p_dir_node) 22 | 23 | enum dir_node_type 24 | { 25 | DNT_FILE = 0, 26 | DNT_DIR, 27 | }; 28 | 29 | struct dir_node 30 | { 31 | struct dir_node const *parent; 32 | struct vec_p_dir_node children; 33 | char *name; 34 | unsigned char type; 35 | }; 36 | 37 | struct bounds 38 | { 39 | unsigned pr, pc; 40 | unsigned sr, sc; 41 | }; 42 | 43 | static struct dir_node *dir_tree(char const *root_name, char const *root_dir); 44 | static void dir_node_unfold(struct dir_node *node); 45 | static void dir_node_fold(struct dir_node *node); 46 | static void dir_node_path(char *out_path, struct dir_node const *node); 47 | static void dir_node_destroy(struct dir_node *node); 48 | static void draw_box(struct bounds const *bounds, struct dir_node const *root, size_t first, size_t sel); 49 | static struct dir_node const *dir_node_next(struct dir_node const *node); 50 | static size_t dir_node_depth(struct dir_node const *node); 51 | static int dir_node_cmp(void const *vp_lhs, void const *vp_rhs); 52 | static size_t dir_tree_size(struct dir_node const *root); 53 | static struct dir_node *dir_tree_nth(struct dir_node *root, size_t n); 54 | 55 | VEC_DEF_IMPL_STATIC(struct dir_node *, p_dir_node) 56 | 57 | enum file_exp_rc 58 | file_exp_open(char *out_path, size_t n, char const *dir) 59 | { 60 | struct dir_node *root = dir_tree(dir, dir); 61 | if (!root) 62 | return FER_FAIL; 63 | 64 | struct win_size ws = draw_win_size(); 65 | struct bounds bounds = 66 | { 67 | .pr = 0, 68 | .sc = MIN(ws.sc, CONF_FILE_EXP_SC), 69 | .sr = ws.sr, 70 | }; 71 | bounds.pc = ws.sc - bounds.sc; 72 | 73 | size_t first = 0, sel = 0; 74 | for (;;) 75 | { 76 | draw_box(&bounds, root, first, sel); 77 | draw_refresh(); 78 | 79 | wint_t k = keybd_await_key_nb(); 80 | switch (k) 81 | { 82 | case WEOF: 83 | case BIND_QUIT: 84 | dir_node_destroy(root); 85 | return FER_QUIT; 86 | case BIND_NAV_DOWN: 87 | sel += sel < dir_tree_size(root) - 1; 88 | first += sel >= bounds.sr + first; 89 | break; 90 | case BIND_NAV_UP: 91 | sel -= sel > 0; 92 | first -= sel < first; 93 | break; 94 | case BIND_RET: 95 | { 96 | struct dir_node *sel_node = dir_tree_nth(root, sel); 97 | if (!sel_node) 98 | { 99 | dir_node_destroy(root); 100 | return FER_FAIL; 101 | } 102 | 103 | if (sel_node->type == DNT_FILE) 104 | { 105 | dir_node_path(out_path, sel_node); 106 | return FER_SUCCESS; 107 | } 108 | 109 | if (sel_node->children.size) 110 | dir_node_fold(sel_node); 111 | else 112 | dir_node_unfold(sel_node); 113 | 114 | break; 115 | } 116 | default: 117 | break; 118 | } 119 | } 120 | } 121 | 122 | static struct dir_node * 123 | dir_tree(char const *root_name, char const *root_dir) 124 | { 125 | DIR *dir_p = opendir(root_dir); 126 | if (!dir_p) 127 | return NULL; 128 | 129 | struct dir_node *root = malloc(sizeof(struct dir_node)); 130 | *root = (struct dir_node) 131 | { 132 | .parent = NULL, 133 | .children = vec_p_dir_node_create(), 134 | .name = strdup(root_name), 135 | .type = DNT_DIR, 136 | }; 137 | 138 | struct dirent *dir_ent; 139 | while (dir_ent = readdir(dir_p)) 140 | { 141 | if (dir_ent->d_type != DT_DIR && dir_ent->d_type != DT_REG 142 | || !strcmp(".", dir_ent->d_name) 143 | || !strcmp("..", dir_ent->d_name)) 144 | { 145 | continue; 146 | } 147 | 148 | struct dir_node *child = malloc(sizeof(struct dir_node)); 149 | *child = (struct dir_node) 150 | { 151 | .parent = root, 152 | .children = vec_p_dir_node_create(), 153 | .name = strdup(dir_ent->d_name), 154 | .type = dir_ent->d_type == DT_DIR ? DNT_DIR : DNT_FILE, 155 | }; 156 | 157 | vec_p_dir_node_add(&root->children, &child); 158 | } 159 | 160 | qsort(root->children.data, 161 | root->children.size, 162 | sizeof(struct dir_node *), 163 | dir_node_cmp); 164 | 165 | closedir(dir_p); 166 | 167 | return root; 168 | } 169 | 170 | static void 171 | dir_node_unfold(struct dir_node *node) 172 | { 173 | if (node->type != DNT_DIR) 174 | return; 175 | 176 | char path[PATH_MAX + 1]; 177 | dir_node_path(path, node); 178 | 179 | struct dir_node *new = dir_tree(node->name, path); 180 | if (!new) 181 | return; 182 | 183 | vec_p_dir_node_destroy(&node->children); 184 | free(node->name); 185 | 186 | for (size_t i = 0; i < new->children.size; ++i) 187 | new->children.data[i]->parent = node; 188 | 189 | new->parent = node->parent; 190 | *node = *new; 191 | 192 | free(new); 193 | } 194 | 195 | static void 196 | dir_node_fold(struct dir_node *node) 197 | { 198 | if (node->type != DNT_DIR) 199 | return; 200 | 201 | for (size_t i = 0; i < node->children.size; ++i) 202 | dir_node_destroy(node->children.data[i]); 203 | node->children.size = 0; 204 | } 205 | 206 | static void 207 | dir_node_path(char *out_path, struct dir_node const *node) 208 | { 209 | struct vec_str names = vec_str_create(); 210 | for (struct dir_node const *n = node->parent; n; n = n->parent) 211 | { 212 | char *name = strdup(n->name); 213 | vec_str_add(&names, &name); 214 | } 215 | 216 | out_path[0] = 0; 217 | for (size_t i = names.size; i > 0; --i) 218 | { 219 | strcat(out_path, names.data[i - 1]); 220 | strcat(out_path, "/"); 221 | } 222 | strcat(out_path, node->name); 223 | 224 | for (size_t i = 0; i < names.size; ++i) 225 | free(names.data[i]); 226 | vec_str_destroy(&names); 227 | } 228 | 229 | static void 230 | dir_node_destroy(struct dir_node *node) 231 | { 232 | for (size_t i = 0; i < node->children.size; ++i) 233 | dir_node_destroy(node->children.data[i]); 234 | 235 | vec_p_dir_node_destroy(&node->children); 236 | free(node->name); 237 | free(node); 238 | } 239 | 240 | static void 241 | draw_box(struct bounds const *bounds, 242 | struct dir_node const *root, 243 | size_t first, 244 | size_t sel) 245 | { 246 | // fill explorer. 247 | draw_fill(bounds->pr, 248 | bounds->pc, 249 | bounds->sr, 250 | bounds->sc, 251 | L' ', 252 | CONF_A_GHIGH_BG, 253 | CONF_A_GHIGH_FG); 254 | 255 | // draw files and directories. 256 | struct dir_node const *node = root; 257 | for (size_t i = 0; node; ++i, node = dir_node_next(node)) 258 | { 259 | if (i < first) 260 | continue; 261 | 262 | if (i >= bounds->sr + first) 263 | break; 264 | 265 | unsigned depth = CONF_FILE_EXP_NEST_DEPTH * dir_node_depth(node); 266 | size_t name_len = strlen(node->name); 267 | for (unsigned j = 0; j < name_len && j + depth < bounds->sc; ++j) 268 | { 269 | draw_put_wch(bounds->pr + i - first, 270 | bounds->pc + j + depth, 271 | node->name[j]); 272 | } 273 | 274 | if (node->type == DNT_DIR && depth + name_len + 1 < bounds->sc) 275 | { 276 | draw_put_wch(bounds->pr + i - first, 277 | bounds->pc + depth + name_len, 278 | L'/'); 279 | } 280 | 281 | if (i == sel) 282 | { 283 | draw_put_attr(bounds->pr + i - first, 284 | bounds->pc, 285 | CONF_A_GHIGH_FG, 286 | CONF_A_GHIGH_BG, 287 | bounds->sc); 288 | } 289 | } 290 | } 291 | 292 | static struct dir_node const * 293 | dir_node_next(struct dir_node const *node) 294 | { 295 | if (node->children.size) 296 | return node->children.data[0]; 297 | 298 | if (!node->parent) 299 | return NULL; 300 | 301 | size_t node_ind = 0; 302 | while (node->parent->children.data[node_ind] != node) 303 | ++node_ind; 304 | 305 | if (node_ind < node->parent->children.size - 1) 306 | return node->parent->children.data[node_ind + 1]; 307 | 308 | // hack to allow upwards-recursive call to `dir_node_next()` without 309 | // having to implement a bunch of checks, by temporarily tricking the 310 | // parent node into thinking it has no children. 311 | // casting const away isn't an issue since nothing is actually modified in 312 | // the end. 313 | struct dir_node *parent_mut = (struct dir_node *)node->parent; 314 | 315 | size_t sv_nchildren = node->parent->children.size; 316 | parent_mut->children.size = 0; 317 | struct dir_node const *next_parent = dir_node_next(node->parent); 318 | parent_mut->children.size = sv_nchildren; 319 | 320 | return next_parent; 321 | } 322 | 323 | static size_t 324 | dir_node_depth(struct dir_node const *node) 325 | { 326 | if (!node) 327 | return 0; 328 | 329 | size_t depth = 0; 330 | while (node = node->parent) 331 | ++depth; 332 | 333 | return depth; 334 | } 335 | 336 | static int 337 | dir_node_cmp(void const *vp_lhs, void const *vp_rhs) 338 | { 339 | struct dir_node const *const *lhs = vp_lhs, *const *rhs = vp_rhs; 340 | return strcmp((*lhs)->name, (*rhs)->name); 341 | } 342 | 343 | static size_t 344 | dir_tree_size(struct dir_node const *root) 345 | { 346 | size_t nnodes = 0; 347 | for (struct dir_node const *node = root; node; node = dir_node_next(node)) 348 | ++nnodes; 349 | return nnodes; 350 | } 351 | 352 | static struct dir_node * 353 | dir_tree_nth(struct dir_node *root, size_t n) 354 | { 355 | struct dir_node *node = root; 356 | for (size_t i = 0; i < n; ++i) 357 | { 358 | // casting away const is fine since nothing here is actually 359 | // modified, and the caller doesn't care how the nth node is 360 | // obtained as long as it's correct. 361 | node = (struct dir_node *)dir_node_next(node); 362 | if (!node) 363 | return NULL; 364 | } 365 | return node; 366 | } 367 | -------------------------------------------------------------------------------- /src/frame.c: -------------------------------------------------------------------------------- 1 | #include "frame.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "conf.h" 8 | #include "draw.h" 9 | #include "util.h" 10 | 11 | // padding size around line numbers. 12 | #define GUTTER (CONF_GUTTER_LEFT + CONF_GUTTER_RIGHT) 13 | 14 | static void draw_line(struct frame const *f, unsigned *line, size_t *draw_csr); 15 | static void exec_highlight(struct frame const *f, struct highlight const *hl); 16 | 17 | VEC_DEF_IMPL(struct frame, frame) 18 | 19 | struct frame 20 | frame_create(wchar_t const *name, struct buf *buf) 21 | { 22 | struct win_size ws = draw_win_size(); 23 | 24 | unsigned ber, bec; 25 | buf_pos(buf, buf->size, &ber, &bec); 26 | unsigned linum_width = 0; 27 | for (unsigned i = MIN(ber, ws.sr - 1) + 1; i > 0; i /= 10) 28 | ++linum_width; 29 | 30 | char *local_mode; 31 | if (buf->src_type == BST_FILE) 32 | { 33 | char const *buf_ext = file_ext(buf->src); 34 | for (size_t i = 0; i < conf_metab_size; ++i) 35 | { 36 | for (char const **ext = conf_metab[i].exts; *ext; ++ext) 37 | { 38 | if (!strcmp(*ext, buf_ext)) 39 | { 40 | local_mode = strdup(conf_metab[i].localmode); 41 | goto done_find_lm; 42 | } 43 | } 44 | } 45 | local_mode = strdup("\0"); 46 | done_find_lm:; 47 | } 48 | else 49 | local_mode = strdup("\0"); 50 | 51 | return (struct frame) 52 | { 53 | .name = wcsdup(name), 54 | .pr = 0, 55 | .pc = 0, 56 | .sr = ws.sr, 57 | .sc = ws.sc, 58 | .buf = buf, 59 | .csr = 0, 60 | .buf_start = 0, 61 | .csr_want_col = 0, 62 | .linum_width = linum_width, 63 | .local_mode = local_mode, 64 | }; 65 | } 66 | 67 | void 68 | frame_destroy(struct frame *f) 69 | { 70 | free(f->name); 71 | free(f->local_mode); 72 | } 73 | 74 | void 75 | frame_draw(struct frame const *f, unsigned long flags) 76 | { 77 | unsigned bsr, bsc, ber, bec; 78 | buf_pos(f->buf, f->buf_start, &bsr, &bsc); 79 | buf_pos(f->buf, f->buf->size, &ber, &bec); 80 | 81 | unsigned left_edge = GUTTER + f->linum_width; 82 | 83 | // write frame title and frame marks. 84 | size_t name_len = wcslen(f->name); 85 | for (unsigned i = 0; i < f->sc; ++i) 86 | draw_put_wch(f->pr, f->pc + i, i < name_len ? f->name[i] : L' '); 87 | 88 | wchar_t draw_marks[64] = {0}; 89 | 90 | if (f->buf->flags & BF_MODIFIED) 91 | wcscat(draw_marks, CONF_MARK_MOD); 92 | if (flags & FDF_MONO) 93 | wcscat(draw_marks, CONF_MARK_MONO); 94 | 95 | size_t draw_mark_len = wcslen(draw_marks); 96 | if (f->sc >= 0 && f->sc < draw_mark_len + 1) 97 | draw_marks[f->sc] = 0; 98 | 99 | draw_put_wstr(f->pr, f->pc + f->sc - draw_mark_len, draw_marks); 100 | 101 | draw_put_attr(f->pr, 102 | f->pc, 103 | flags & FDF_ACTIVE ? CONF_A_GHIGH_FG : CONF_A_GNORM_FG, 104 | flags & FDF_ACTIVE ? CONF_A_GHIGH_BG : CONF_A_GNORM_BG, 105 | f->sc); 106 | 107 | // fill frame. 108 | draw_fill(f->pr + 1, 109 | f->pc, 110 | f->sr - 1, 111 | f->sc, 112 | L' ', 113 | CONF_A_NORM_FG, 114 | CONF_A_NORM_BG); 115 | 116 | // write margins. 117 | unsigned befr, befc; 118 | frame_pos(f, f->buf->size, &befr, &befc); 119 | for (size_t i = 0; i < conf_mtab_size; ++i) 120 | { 121 | unsigned draw_col = left_edge + conf_mtab[i].col; 122 | if (draw_col >= f->sc) 123 | continue; 124 | 125 | for (unsigned j = 1, end = MIN(befr + 1, f->sr); j < end; ++j) 126 | { 127 | draw_put_wch(f->pr + j, f->pc + draw_col, conf_mtab[i].wch); 128 | 129 | draw_put_attr(f->pr + j, 130 | f->pc + draw_col, 131 | conf_mtab[i].fg, 132 | conf_mtab[i].bg, 133 | 1); 134 | } 135 | } 136 | 137 | // write lines and linums. 138 | size_t draw_csr = f->buf_start; 139 | unsigned linum_ind = 0; 140 | for (unsigned i = 1; i < f->sr; ++i) 141 | { 142 | if (linum_ind++ <= ber - bsr) 143 | { 144 | wchar_t draw_text[16]; 145 | swprintf(draw_text, 16, L"%u", bsr + linum_ind); 146 | draw_put_wstr(f->pr + i, 147 | f->pc + CONF_GUTTER_LEFT + f->linum_width - wcslen(draw_text), 148 | draw_text); 149 | } 150 | 151 | draw_line(f, &i, &draw_csr); 152 | } 153 | 154 | // draw gutter. 155 | for (unsigned i = 1; i < f->sr; ++i) 156 | { 157 | draw_put_attr(f->pr + i, 158 | f->pc, 159 | CONF_A_LINUM_FG, 160 | CONF_A_LINUM_BG, 161 | GUTTER + f->linum_width); 162 | } 163 | 164 | // execute highlight. 165 | for (size_t i = 0; i < conf_htab_size; ++i) 166 | { 167 | if (!strcmp(conf_htab[i].local_mode, f->local_mode)) 168 | { 169 | exec_highlight(f, &conf_htab[i]); 170 | goto done_highlight; 171 | } 172 | } 173 | done_highlight:; 174 | 175 | // draw cursor. 176 | unsigned csrr, csrc; 177 | frame_pos(f, f->csr, &csrr, &csrc); 178 | 179 | draw_put_attr(f->pr + csrr, 180 | f->pc + csrc, 181 | CONF_A_CURSOR_FG, 182 | CONF_A_CURSOR_BG, 183 | 1); 184 | } 185 | 186 | void 187 | frame_pos(struct frame const *f, size_t pos, unsigned *out_r, unsigned *out_c) 188 | { 189 | unsigned right_edge = f->sc - GUTTER - f->linum_width; 190 | pos = MIN(pos, f->buf->size); 191 | 192 | *out_r = 1; 193 | *out_c = 0; 194 | 195 | for (size_t i = f->buf_start; i < pos; ++i) 196 | { 197 | wchar_t wch = buf_get_wch(f->buf, i); 198 | 199 | if (wch == L'\t') 200 | *out_c += CONF_TAB_SIZE - *out_c % CONF_TAB_SIZE - 1; 201 | else if (wch == L'\n' || *out_c >= right_edge - 1) 202 | { 203 | *out_c = 0; 204 | ++*out_r; 205 | continue; 206 | } 207 | 208 | ++*out_c; 209 | } 210 | 211 | *out_c += GUTTER + f->linum_width; 212 | } 213 | 214 | void 215 | frame_mv_csr(struct frame *f, unsigned r, unsigned c) 216 | { 217 | f->csr = 0; 218 | 219 | while (f->csr < f->buf->size && r > 0) 220 | { 221 | if (buf_get_wch(f->buf, f->csr++) == L'\n') 222 | --r; 223 | } 224 | 225 | while (f->csr < f->buf->size 226 | && buf_get_wch(f->buf, f->csr) != L'\n' 227 | && c > 0) 228 | { 229 | ++f->csr; 230 | --c; 231 | } 232 | 233 | frame_comp_boundary(f); 234 | } 235 | 236 | void 237 | frame_mv_csr_rel(struct frame *f, int dr, int dc, bool wrap) 238 | { 239 | int dc_sv = dc; 240 | 241 | if (wrap && dc != 0) 242 | { 243 | int dir = SIGN(dc); 244 | long bs_dst = -(long)f->csr; 245 | long be_dst = f->buf->size - f->csr; 246 | 247 | dc = dir == -1 ? MAX(dc, bs_dst) : MIN(dc, be_dst); 248 | 249 | while (f->csr >= 0 && f->csr <= f->buf->size && dc != 0) 250 | { 251 | f->csr += dir; 252 | dc -= dir; 253 | } 254 | } 255 | 256 | unsigned csrr, csrc; 257 | buf_pos(f->buf, f->csr, &csrr, &csrc); 258 | csrr = (long)csrr + dr < 0 ? 0 : csrr + dr; 259 | 260 | if (dc_sv != 0) 261 | { 262 | csrc = (long)csrc + dc < 0 ? 0 : csrc + dc; 263 | f->csr_want_col = csrc; 264 | } 265 | else 266 | csrc = f->csr_want_col; 267 | 268 | frame_mv_csr(f, csrr, csrc); 269 | } 270 | 271 | void 272 | frame_comp_boundary(struct frame *f) 273 | { 274 | unsigned left_edge = GUTTER + f->linum_width; 275 | unsigned right_edge = f->sc - left_edge; 276 | 277 | // redetermine buffer boundaries for rendering. 278 | unsigned csrr, csrc; 279 | frame_pos(f, f->csr, &csrr, &csrc); 280 | 281 | while (csrr >= f->sr) 282 | { 283 | ++f->buf_start; 284 | while (f->buf_start < f->buf->size 285 | && buf_get_wch(f->buf, f->buf_start - 1) != L'\n') 286 | { 287 | ++f->buf_start; 288 | } 289 | --csrr; 290 | } 291 | 292 | unsigned bsr, bsc; 293 | buf_pos(f->buf, f->buf_start, &bsr, &bsc); 294 | buf_pos(f->buf, f->csr, &csrr, &csrc); 295 | 296 | while (csrr < bsr) 297 | { 298 | --f->buf_start; 299 | while (f->buf_start > 0 300 | && buf_get_wch(f->buf, f->buf_start - 1) != L'\n') 301 | { 302 | --f->buf_start; 303 | } 304 | ++csrr; 305 | } 306 | 307 | // fix linum width. 308 | unsigned ber, bec; 309 | buf_pos(f->buf, f->buf->size, &ber, &bec); 310 | f->linum_width = 0; 311 | for (unsigned i = MIN(ber, bsr + f->sr - 1) + 1; i > 0; i /= 10) 312 | ++f->linum_width; 313 | } 314 | 315 | static void 316 | draw_line(struct frame const *f, unsigned *line, size_t *draw_csr) 317 | { 318 | unsigned left_edge = GUTTER + f->linum_width; 319 | unsigned right_edge = f->sc - left_edge; 320 | unsigned c = 0; 321 | 322 | while (*draw_csr < f->buf->size 323 | && buf_get_wch(f->buf, *draw_csr) != L'\n') 324 | { 325 | if (c >= right_edge) 326 | { 327 | c = 0; 328 | ++*line; 329 | } 330 | 331 | if (*line >= f->sr) 332 | break; 333 | 334 | // 0xfffd used as replacement for non-printing chars. 335 | wchar_t wch = buf_get_wch(f->buf, *draw_csr); 336 | wch = wch == L'\t' || iswprint(wch) ? wch : 0xfffd; 337 | 338 | switch (wch) 339 | { 340 | case L'\t': 341 | { 342 | unsigned nch = CONF_TAB_SIZE - c % CONF_TAB_SIZE; 343 | nch = MIN(nch, right_edge - c); 344 | 345 | for (unsigned i = 0; i < nch && c + i < right_edge; ++i) 346 | draw_put_wch(f->pr + *line, f->pc + left_edge + c + i, L' '); 347 | 348 | draw_put_attr(f->pr + *line, 349 | f->pc + left_edge + c, 350 | CONF_A_NORM_FG, 351 | CONF_A_NORM_BG, 352 | nch); 353 | 354 | c += CONF_TAB_SIZE - c % CONF_TAB_SIZE; 355 | 356 | break; 357 | } 358 | default: 359 | draw_put_wch(f->pr + *line, f->pc + left_edge + c, wch); 360 | 361 | draw_put_attr(f->pr + *line, 362 | f->pc + left_edge + c, 363 | CONF_A_NORM_FG, 364 | CONF_A_NORM_BG, 365 | 1); 366 | 367 | ++c; 368 | break; 369 | } 370 | 371 | ++*draw_csr; 372 | } 373 | 374 | if (c >= right_edge) 375 | ++*line; 376 | 377 | ++*draw_csr; 378 | } 379 | 380 | static void 381 | exec_highlight(struct frame const *f, struct highlight const *hl) 382 | { 383 | unsigned left_edge = GUTTER + f->linum_width; 384 | 385 | size_t off = f->buf_start; 386 | size_t lb, ub; 387 | uint8_t fg, bg; 388 | while (!hl->find(f->buf, off, &lb, &ub, &fg, &bg)) 389 | { 390 | unsigned hlr, hlc; 391 | frame_pos(f, lb, &hlr, &hlc); 392 | if (hlr >= f->sr) 393 | break; 394 | 395 | unsigned c = hlc - left_edge, r = hlr; 396 | for (size_t i = lb; i < ub; ++i) 397 | { 398 | if (c >= f->sc - left_edge) 399 | { 400 | c = 0; 401 | ++r; 402 | } 403 | 404 | if (r >= f->sr) 405 | break; 406 | 407 | unsigned w; 408 | switch (buf_get_wch(f->buf, i)) 409 | { 410 | case L'\n': 411 | c = 0; 412 | ++r; 413 | continue; 414 | case L'\t': 415 | w = CONF_TAB_SIZE - c % CONF_TAB_SIZE; 416 | break; 417 | default: 418 | w = 1; 419 | break; 420 | } 421 | 422 | draw_put_attr(f->pr + r, f->pc + left_edge + c, fg, bg, w); 423 | 424 | c += w; 425 | } 426 | 427 | off = ub; 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/hl/hl_c.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_c.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "conf.h" 8 | 9 | #define A_PREPROC_FG CONF_A_ACCENT_3_FG 10 | #define A_PREPROC_BG CONF_A_ACCENT_3_BG 11 | #define A_KEYWORD_FG CONF_A_ACCENT_1_FG 12 | #define A_KEYWORD_BG CONF_A_ACCENT_1_BG 13 | #define A_COMMENT_FG CONF_A_COMMENT_FG 14 | #define A_COMMENT_BG CONF_A_COMMENT_BG 15 | #define A_STRING_FG CONF_A_STRING_FG 16 | #define A_STRING_BG CONF_A_STRING_BG 17 | #define A_FUNC_FG CONF_A_ACCENT_4_FG 18 | #define A_FUNC_BG CONF_A_ACCENT_4_BG 19 | #define A_MACRO_FG CONF_A_ACCENT_2_FG 20 | #define A_MACRO_BG CONF_A_ACCENT_2_BG 21 | #define A_SPECIAL_FG CONF_A_SPECIAL_FG 22 | #define A_SPECIAL_BG CONF_A_SPECIAL_BG 23 | 24 | #define SPECIAL L"+-()[].<>{}!~*&/%=?:|;,^" 25 | 26 | enum word_type 27 | { 28 | WT_MACRO, 29 | WT_FUNC, 30 | WT_KEYWORD, 31 | WT_BASIC, 32 | }; 33 | 34 | static int hl_preproc(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 35 | static int hl_string(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 36 | static int hl_char(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 37 | static int hl_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 38 | static int hl_special(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 39 | static int hl_word(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 40 | 41 | static wchar_t const *keywords[] = 42 | { 43 | L"auto", 44 | L"break", 45 | L"case", 46 | L"char", 47 | L"const", 48 | L"continue", 49 | L"default", 50 | L"do", 51 | L"double", 52 | L"else", 53 | L"enum", 54 | L"extern", 55 | L"float", 56 | L"for", 57 | L"goto", 58 | L"if", 59 | L"inline", 60 | L"int", 61 | L"long", 62 | L"register", 63 | L"restrict", 64 | L"return", 65 | L"short", 66 | L"signed", 67 | L"sizeof", 68 | L"static", 69 | L"struct", 70 | L"switch", 71 | L"typedef", 72 | L"union", 73 | L"unsigned", 74 | L"void", 75 | L"volatile", 76 | L"while", 77 | L"_Bool", 78 | L"_Complex", 79 | L"_Imaginary", 80 | }; 81 | 82 | int 83 | hl_c_find(struct buf const *buf, 84 | size_t off, 85 | size_t *out_lb, 86 | size_t *out_ub, 87 | uint8_t *out_fg, 88 | uint8_t *out_bg) 89 | { 90 | for (size_t i = off; i < buf->size; ++i) 91 | { 92 | if (buf_get_wch(buf, i) == L'#') 93 | { 94 | if (!hl_preproc(buf, &i, out_lb, out_ub, out_fg, out_bg)) 95 | return 0; 96 | } 97 | else if (buf_get_wch(buf, i) == L'"') 98 | { 99 | if (!hl_string(buf, &i, out_lb, out_ub, out_fg, out_bg)) 100 | return 0; 101 | } 102 | else if (buf_get_wch(buf, i) == L'\'') 103 | { 104 | if (!hl_char(buf, &i, out_lb, out_ub, out_fg, out_bg)) 105 | return 0; 106 | } 107 | else if (i + 1 < buf->size 108 | && buf_get_wch(buf, i) == L'/' 109 | && wcschr(L"/*", buf_get_wch(buf, i + 1))) 110 | { 111 | if (!hl_comment(buf, &i, out_lb, out_ub, out_fg, out_bg)) 112 | return 0; 113 | } 114 | else if (wcschr(SPECIAL, buf_get_wch(buf, i))) 115 | { 116 | if (!hl_special(buf, &i, out_lb, out_ub, out_fg, out_bg)) 117 | return 0; 118 | } 119 | else if (iswalpha(buf_get_wch(buf, i)) 120 | || buf_get_wch(buf, i) == L'_') 121 | { 122 | if (!hl_word(buf, &i, out_lb, out_ub, out_fg, out_bg)) 123 | return 0; 124 | } 125 | } 126 | 127 | return 1; 128 | } 129 | 130 | static int 131 | hl_preproc(struct buf const *buf, 132 | size_t *i, 133 | size_t *out_lb, 134 | size_t *out_ub, 135 | uint8_t *out_fg, 136 | uint8_t *out_bg) 137 | { 138 | size_t j = *i; 139 | while (j < buf->size) 140 | { 141 | ++j; 142 | if (buf_get_wch(buf, j) == L'\\') 143 | { 144 | ++j; 145 | while (iswspace(buf_get_wch(buf, j)) 146 | && buf_get_wch(buf, j) != L'\n') 147 | { 148 | ++j; 149 | } 150 | if (buf_get_wch(buf, j) == L'\n') 151 | ++j; 152 | } 153 | else if (buf_get_wch(buf, j) == L'\n') 154 | break; 155 | } 156 | 157 | *out_lb = *i; 158 | *out_ub = j; 159 | *out_fg = A_PREPROC_FG; 160 | *out_bg = A_PREPROC_BG; 161 | 162 | return 0; 163 | } 164 | 165 | static int 166 | hl_string(struct buf const *buf, 167 | size_t *i, 168 | size_t *out_lb, 169 | size_t *out_ub, 170 | uint8_t *out_fg, 171 | uint8_t *out_bg) 172 | { 173 | size_t j = *i; 174 | while (j < buf->size) 175 | { 176 | ++j; 177 | if (buf_get_wch(buf, j) == L'\\') 178 | { 179 | ++j; 180 | continue; 181 | } 182 | else if (wcschr(L"\"\n", buf_get_wch(buf, j))) 183 | break; 184 | } 185 | 186 | *out_lb = *i; 187 | *out_ub = j + (j < buf->size); 188 | *out_fg = A_STRING_FG; 189 | *out_bg = A_STRING_BG; 190 | 191 | return 0; 192 | } 193 | 194 | static int 195 | hl_char(struct buf const *buf, 196 | size_t *i, 197 | size_t *out_lb, 198 | size_t *out_ub, 199 | uint8_t *out_fg, 200 | uint8_t *out_bg) 201 | { 202 | size_t j = *i + 1; 203 | if (j < buf->size && buf_get_wch(buf, j) == L'\\') 204 | { 205 | ++j; 206 | 207 | if (j < buf->size && wcschr(L"01234567", buf_get_wch(buf, j))) 208 | { 209 | // octal escape sequences. 210 | while (j < buf->size 211 | && wcschr(L"01234567", buf_get_wch(buf, j))) 212 | { 213 | ++j; 214 | } 215 | } 216 | else if (j < buf->size 217 | && wcschr(L"xuU", buf_get_wch(buf, j))) 218 | { 219 | // hexadecimal-based escape seuences. 220 | ++j; 221 | while (j < buf->size && iswxdigit(buf_get_wch(buf, j))) 222 | ++j; 223 | } 224 | else 225 | ++j; 226 | } 227 | else 228 | ++j; 229 | 230 | if (j < buf->size && buf_get_wch(buf, j) == L'\'') 231 | { 232 | *out_lb = *i; 233 | *out_ub = j + 1; 234 | *out_fg = A_STRING_FG; 235 | *out_bg = A_STRING_BG; 236 | 237 | return 0; 238 | } 239 | 240 | return 1; 241 | } 242 | 243 | static int 244 | hl_comment(struct buf const *buf, 245 | size_t *i, 246 | size_t *out_lb, 247 | size_t *out_ub, 248 | uint8_t *out_fg, 249 | uint8_t *out_bg) 250 | { 251 | size_t j = *i + 2; 252 | if (buf_get_wch(buf, *i + 1) == L'/') 253 | { 254 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 255 | ++j; 256 | } 257 | else 258 | { 259 | wchar_t cmp[3]; 260 | while (j + 1 < buf->size 261 | && wcscmp(buf_get_wstr(buf, cmp, j, 3), L"*/")) 262 | { 263 | ++j; 264 | } 265 | } 266 | 267 | *out_lb = *i; 268 | *out_ub = j + 2 * (buf_get_wch(buf, j) == '*'); 269 | *out_fg = A_COMMENT_FG; 270 | *out_bg = A_COMMENT_BG; 271 | 272 | return 0; 273 | } 274 | 275 | static int 276 | hl_special(struct buf const *buf, 277 | size_t *i, 278 | size_t *out_lb, 279 | size_t *out_ub, 280 | uint8_t *out_fg, 281 | uint8_t *out_bg) 282 | { 283 | size_t j = *i + 1; 284 | while (j < buf->size && wcschr(SPECIAL, buf_get_wch(buf, j))) 285 | ++j; 286 | 287 | *out_lb = *i; 288 | *out_ub = j; 289 | *out_fg = A_SPECIAL_FG; 290 | *out_bg = A_SPECIAL_BG; 291 | 292 | return 0; 293 | } 294 | 295 | static int 296 | hl_word(struct buf const *buf, 297 | size_t *i, 298 | size_t *out_lb, 299 | size_t *out_ub, 300 | uint8_t *out_fg, 301 | uint8_t *out_bg) 302 | { 303 | enum word_type wt = WT_MACRO; 304 | 305 | size_t j = *i; 306 | while (j < buf->size) 307 | { 308 | if (buf_get_wch(buf, j) != L'_' 309 | && !iswalnum(buf_get_wch(buf, j))) 310 | { 311 | break; 312 | } 313 | 314 | if (iswlower(buf_get_wch(buf, j))) 315 | wt = WT_BASIC; 316 | 317 | ++j; 318 | } 319 | 320 | if (wt == WT_BASIC) 321 | { 322 | size_t k = j; 323 | while (k < buf->size && iswspace(buf_get_wch(buf, k))) 324 | ++k; 325 | 326 | if (k < buf->size && buf_get_wch(buf, k) == L'(') 327 | wt = WT_FUNC; 328 | } 329 | 330 | for (size_t kw = 0; kw < ARRAY_SIZE(keywords); ++kw) 331 | { 332 | size_t len = j - *i + 1; 333 | if (len > 64) 334 | break; 335 | 336 | wchar_t cmp[64]; 337 | buf_get_wstr(buf, cmp, *i, len); 338 | 339 | if (!wcscmp(keywords[kw], cmp)) 340 | { 341 | wt = WT_KEYWORD; 342 | break; 343 | } 344 | } 345 | 346 | *out_lb = *i; 347 | *out_ub = j; 348 | 349 | switch (wt) 350 | { 351 | case WT_MACRO: 352 | *out_fg = A_MACRO_FG; 353 | *out_bg = A_MACRO_BG; 354 | break; 355 | case WT_FUNC: 356 | *out_fg = A_FUNC_FG; 357 | *out_bg = A_FUNC_BG; 358 | break; 359 | case WT_KEYWORD: 360 | *out_fg = A_KEYWORD_FG; 361 | *out_bg = A_KEYWORD_BG; 362 | break; 363 | case WT_BASIC: 364 | *i = j - 1; 365 | return 1; 366 | } 367 | 368 | return 0; 369 | } 370 | -------------------------------------------------------------------------------- /src/hl/hl_cc.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_cc.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "conf.h" 10 | 11 | #define A_PREPROC_FG CONF_A_ACCENT_3_FG 12 | #define A_PREPROC_BG CONF_A_ACCENT_3_BG 13 | #define A_KEYWORD_FG CONF_A_ACCENT_1_FG 14 | #define A_KEYWORD_BG CONF_A_ACCENT_1_BG 15 | #define A_COMMENT_FG CONF_A_COMMENT_FG 16 | #define A_COMMENT_BG CONF_A_COMMENT_BG 17 | #define A_STRING_FG CONF_A_STRING_FG 18 | #define A_STRING_BG CONF_A_STRING_BG 19 | #define A_FUNC_FG CONF_A_ACCENT_4_FG 20 | #define A_FUNC_BG CONF_A_ACCENT_4_BG 21 | #define A_MACRO_FG CONF_A_ACCENT_2_FG 22 | #define A_MACRO_BG CONF_A_ACCENT_2_BG 23 | #define A_SPECIAL_FG CONF_A_SPECIAL_FG 24 | #define A_SPECIAL_BG CONF_A_SPECIAL_BG 25 | 26 | #define SPECIAL L"+-()[].<>{}!~*&/%=?:|;,^" 27 | 28 | enum word_type 29 | { 30 | WT_MACRO, 31 | WT_FUNC, 32 | WT_KEYWORD, 33 | WT_BASIC, 34 | }; 35 | 36 | static int hl_preproc(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 37 | static int hl_string(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 38 | static int hl_char(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 39 | static int hl_rstring(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 40 | static int hl_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 41 | static int hl_special(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 42 | static int hl_word(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 43 | 44 | static wchar_t const *keywords[] = 45 | { 46 | L"alignas", 47 | L"alignof", 48 | L"and", 49 | L"and_eq", 50 | L"asm", 51 | L"atomic_cancel", 52 | L"atomic_commit", 53 | L"atomic_noexcept", 54 | L"auto", 55 | L"bitand", 56 | L"bitor", 57 | L"bool", 58 | L"break", 59 | L"case", 60 | L"catch", 61 | L"char", 62 | L"char8_t", 63 | L"char16_t", 64 | L"char32_t", 65 | L"class", 66 | L"compl", 67 | L"concept", 68 | L"const", 69 | L"consteval", 70 | L"constexpr", 71 | L"constinit", 72 | L"const_cast", 73 | L"continue", 74 | L"co_await", 75 | L"co_return", 76 | L"co_yield", 77 | L"decltype", 78 | L"default", 79 | L"delete", 80 | L"do", 81 | L"double", 82 | L"dynamic_cast", 83 | L"else", 84 | L"enum", 85 | L"explicit", 86 | L"export", 87 | L"extern", 88 | L"false", 89 | L"float", 90 | L"for", 91 | L"friend", 92 | L"goto", 93 | L"if", 94 | L"inline", 95 | L"int", 96 | L"long", 97 | L"mutable", 98 | L"namespace", 99 | L"new", 100 | L"noexcept", 101 | L"not", 102 | L"not_eq", 103 | L"nullptr", 104 | L"operator", 105 | L"or", 106 | L"or_eq", 107 | L"private", 108 | L"protected", 109 | L"public", 110 | L"reflexpr", 111 | L"register", 112 | L"reinterpret_cast", 113 | L"requires", 114 | L"return", 115 | L"short", 116 | L"signed", 117 | L"sizeof", 118 | L"static", 119 | L"static_assert", 120 | L"static_cast", 121 | L"struct", 122 | L"switch", 123 | L"synchronized", 124 | L"template", 125 | L"this", 126 | L"thread_local", 127 | L"throw", 128 | L"true", 129 | L"try", 130 | L"typedef", 131 | L"typeid", 132 | L"typename", 133 | L"union", 134 | L"unsigned", 135 | L"using", 136 | L"virtual", 137 | L"void", 138 | L"volatile", 139 | L"wchar_t", 140 | L"while", 141 | L"xor", 142 | L"xor_eq", 143 | }; 144 | 145 | int 146 | hl_cc_find(struct buf const *buf, 147 | size_t off, 148 | size_t *out_lb, 149 | size_t *out_ub, 150 | uint8_t *out_fg, 151 | uint8_t *out_bg) 152 | { 153 | wchar_t cmp_buf[20]; // max rstr compare length (19) plus 1. 154 | 155 | for (size_t i = off; i < buf->size; ++i) 156 | { 157 | if (buf_get_wch(buf, i) == L'#') 158 | { 159 | if (!hl_preproc(buf, &i, out_lb, out_ub, out_fg, out_bg)) 160 | return 0; 161 | } 162 | else if (i + 2 < buf->size && !wcscmp(buf_get_wstr(buf, cmp_buf, i, 3), L"R\"") 163 | || i + 3 < buf->size && !wcscmp(buf_get_wstr(buf, cmp_buf, i, 4), L"LR\"") 164 | || i + 4 < buf->size && !wcscmp(buf_get_wstr(buf, cmp_buf, i, 5), L"u8R\"") 165 | || i + 3 < buf->size && !wcscmp(buf_get_wstr(buf, cmp_buf, i, 4), L"uR\"") 166 | || i + 3 < buf->size && !wcscmp(buf_get_wstr(buf, cmp_buf, i, 4), L"UR\"")) 167 | { 168 | if (!hl_rstring(buf, &i, out_lb, out_ub, out_fg, out_bg)) 169 | return 0; 170 | } 171 | else if (buf_get_wch(buf, i) == L'"') 172 | { 173 | if (!hl_string(buf, &i, out_lb, out_ub, out_fg, out_bg)) 174 | return 0; 175 | } 176 | else if (buf_get_wch(buf, i) == L'\'') 177 | { 178 | if (!hl_char(buf, &i, out_lb, out_ub, out_fg, out_bg)) 179 | return 0; 180 | } 181 | else if (i + 1 < buf->size 182 | && buf_get_wch(buf, i) == L'/' 183 | && wcschr(L"/*", buf_get_wch(buf, i + 1))) 184 | { 185 | if (!hl_comment(buf, &i, out_lb, out_ub, out_fg, out_bg)) 186 | return 0; 187 | } 188 | else if (wcschr(SPECIAL, buf_get_wch(buf, i))) 189 | { 190 | if (!hl_special(buf, &i, out_lb, out_ub, out_fg, out_bg)) 191 | return 0; 192 | } 193 | else if (iswalpha(buf_get_wch(buf, i)) 194 | || buf_get_wch(buf, i) == L'_') 195 | { 196 | if (!hl_word(buf, &i, out_lb, out_ub, out_fg, out_bg)) 197 | return 0; 198 | } 199 | } 200 | 201 | return 1; 202 | } 203 | 204 | static int 205 | hl_preproc(struct buf const *buf, 206 | size_t *i, 207 | size_t *out_lb, 208 | size_t *out_ub, 209 | uint8_t *out_fg, 210 | uint8_t *out_bg) 211 | { 212 | size_t j = *i; 213 | while (j < buf->size) 214 | { 215 | ++j; 216 | if (buf_get_wch(buf, j) == L'\\') 217 | { 218 | ++j; 219 | while (iswspace(buf_get_wch(buf, j)) 220 | && buf_get_wch(buf, j) != L'\n') 221 | { 222 | ++j; 223 | } 224 | if (buf_get_wch(buf, j) == L'\n') 225 | ++j; 226 | } 227 | else if (buf_get_wch(buf, j) == L'\n') 228 | break; 229 | } 230 | 231 | *out_lb = *i; 232 | *out_ub = j; 233 | *out_fg = A_PREPROC_FG; 234 | *out_bg = A_PREPROC_BG; 235 | 236 | return 0; 237 | } 238 | 239 | static int 240 | hl_string(struct buf const *buf, 241 | size_t *i, 242 | size_t *out_lb, 243 | size_t *out_ub, 244 | uint8_t *out_fg, 245 | uint8_t *out_bg) 246 | { 247 | size_t j = *i; 248 | while (j < buf->size) 249 | { 250 | ++j; 251 | if (buf_get_wch(buf, j) == L'\\') 252 | { 253 | ++j; 254 | continue; 255 | } 256 | else if (wcschr(L"\"\n", buf_get_wch(buf, j))) 257 | break; 258 | } 259 | 260 | *out_lb = *i; 261 | *out_ub = j + (j < buf->size); 262 | *out_fg = A_STRING_FG; 263 | *out_bg = A_STRING_BG; 264 | 265 | return 0; 266 | } 267 | 268 | static int 269 | hl_char(struct buf const *buf, 270 | size_t *i, 271 | size_t *out_lb, 272 | size_t *out_ub, 273 | uint8_t *out_fg, 274 | uint8_t *out_bg) 275 | { 276 | // C++23 escape sequences not handled here. 277 | // they should be trivial to add if actually needed. 278 | 279 | size_t j = *i + 1; 280 | if (j < buf->size && buf_get_wch(buf, j) == L'\\') 281 | { 282 | ++j; 283 | 284 | if (j < buf->size && wcschr(L"01234567", buf_get_wch(buf, j))) 285 | { 286 | // octal escape sequences. 287 | while (j < buf->size 288 | && wcschr(L"01234567", buf_get_wch(buf, j))) 289 | { 290 | ++j; 291 | } 292 | } 293 | else if (j < buf->size 294 | && wcschr(L"xuU", buf_get_wch(buf, j))) 295 | { 296 | // hexadecimal-based escape seuences. 297 | ++j; 298 | while (j < buf->size && iswxdigit(buf_get_wch(buf, j))) 299 | ++j; 300 | } 301 | else 302 | ++j; 303 | } 304 | else 305 | ++j; 306 | 307 | if (j < buf->size && buf_get_wch(buf, j) == L'\'') 308 | { 309 | *out_lb = *i; 310 | *out_ub = j + 1; 311 | *out_fg = A_STRING_FG; 312 | *out_bg = A_STRING_BG; 313 | 314 | return 0; 315 | } 316 | 317 | return 1; 318 | } 319 | 320 | static int 321 | hl_rstring(struct buf const *buf, 322 | size_t *i, 323 | size_t *out_lb, 324 | size_t *out_ub, 325 | uint8_t *out_fg, 326 | uint8_t *out_bg) 327 | { 328 | while (buf_get_wch(buf, *i) != L'"') 329 | ++*i; 330 | 331 | size_t j = *i + 1; 332 | while (j < buf->size 333 | && j - *i <= 16 334 | && !wcschr(L"(\"", buf_get_wch(buf, j))) 335 | { 336 | ++j; 337 | } 338 | 339 | if (j >= buf->size || buf_get_wch(buf, j) != L'(') 340 | return 1; 341 | 342 | // `d-char-seq` is max 16 chars, plus 3 for '"', ')', and null. 343 | wchar_t term_seq[19] = {0}; 344 | size_t d_char_seq_len = j - *i - 1; 345 | 346 | for (size_t k = 0; k < d_char_seq_len; ++k) 347 | term_seq[k + 1] = buf_get_wch(buf, *i + k + 1); 348 | term_seq[0] = L')'; 349 | term_seq[d_char_seq_len + 1] = L'"'; 350 | 351 | while (j + d_char_seq_len + 2 < buf->size) 352 | { 353 | wchar_t cmp_buf[19]; 354 | buf_get_wstr(buf, cmp_buf, j, d_char_seq_len + 3); 355 | if (!wcscmp(cmp_buf, term_seq)) 356 | break; 357 | ++j; 358 | } 359 | 360 | if (j + d_char_seq_len + 2 >= buf->size) 361 | return 1; 362 | 363 | *out_lb = *i; 364 | *out_ub = j + d_char_seq_len + 2; 365 | *out_fg = A_STRING_FG; 366 | *out_bg = A_STRING_BG; 367 | 368 | return 0; 369 | } 370 | 371 | static int 372 | hl_comment(struct buf const *buf, 373 | size_t *i, 374 | size_t *out_lb, 375 | size_t *out_ub, 376 | uint8_t *out_fg, 377 | uint8_t *out_bg) 378 | { 379 | size_t j = *i + 2; 380 | if (buf_get_wch(buf, *i + 1) == L'/') 381 | { 382 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 383 | ++j; 384 | } 385 | else 386 | { 387 | wchar_t cmp[3]; 388 | while (j + 1 < buf->size 389 | && wcscmp(buf_get_wstr(buf, cmp, j, 3), L"*/")) 390 | { 391 | ++j; 392 | } 393 | } 394 | 395 | *out_lb = *i; 396 | *out_ub = j + 2 * (buf_get_wch(buf, j) == '*'); 397 | *out_fg = A_COMMENT_FG; 398 | *out_bg = A_COMMENT_BG; 399 | 400 | return 0; 401 | } 402 | 403 | static int 404 | hl_special(struct buf const *buf, 405 | size_t *i, 406 | size_t *out_lb, 407 | size_t *out_ub, 408 | uint8_t *out_fg, 409 | uint8_t *out_bg) 410 | { 411 | size_t j = *i + 1; 412 | while (j < buf->size && wcschr(SPECIAL, buf_get_wch(buf, j))) 413 | ++j; 414 | 415 | *out_lb = *i; 416 | *out_ub = j; 417 | *out_fg = A_SPECIAL_FG; 418 | *out_bg = A_SPECIAL_BG; 419 | 420 | return 0; 421 | } 422 | 423 | static int 424 | hl_word(struct buf const *buf, 425 | size_t *i, 426 | size_t *out_lb, 427 | size_t *out_ub, 428 | uint8_t *out_fg, 429 | uint8_t *out_bg) 430 | { 431 | enum word_type wt = WT_MACRO; 432 | 433 | size_t j = *i; 434 | while (j < buf->size) 435 | { 436 | if (buf_get_wch(buf, j) != L'_' 437 | && !iswalnum(buf_get_wch(buf, j))) 438 | { 439 | break; 440 | } 441 | 442 | if (iswlower(buf_get_wch(buf, j))) 443 | wt = WT_BASIC; 444 | 445 | ++j; 446 | } 447 | 448 | if (wt == WT_BASIC) 449 | { 450 | size_t k = j; 451 | while (k < buf->size && iswspace(buf_get_wch(buf, k))) 452 | ++k; 453 | 454 | // very dumb, does not do string or comment checking. 455 | // generally people don't put strings in function template 456 | // arguments anyway so that's probably fine. 457 | // at least, *I* never do that, and this is *my* editor. 458 | // fight me. 459 | if (k < buf->size && buf_get_wch(buf, k) == L'<') 460 | { 461 | ++k; 462 | for (unsigned nopen = 1; k < buf->size && nopen > 0; ++k) 463 | { 464 | wchar_t wch = buf_get_wch(buf, k); 465 | nopen += wch == L'<'; 466 | nopen -= wch == L'>'; 467 | } 468 | } 469 | 470 | while (k < buf->size && iswspace(buf_get_wch(buf, k))) 471 | ++k; 472 | 473 | if (k < buf->size && buf_get_wch(buf, k) == L'(') 474 | wt = WT_FUNC; 475 | } 476 | 477 | for (size_t kw = 0; kw < ARRAY_SIZE(keywords); ++kw) 478 | { 479 | size_t len = j - *i + 1; 480 | if (len > 64) 481 | break; 482 | 483 | wchar_t cmp[64]; 484 | buf_get_wstr(buf, cmp, *i, len); 485 | 486 | if (!wcscmp(keywords[kw], cmp)) 487 | { 488 | wt = WT_KEYWORD; 489 | break; 490 | } 491 | } 492 | 493 | *out_lb = *i; 494 | *out_ub = j; 495 | 496 | switch (wt) 497 | { 498 | case WT_MACRO: 499 | *out_fg = A_MACRO_FG; 500 | *out_bg = A_MACRO_BG; 501 | break; 502 | case WT_FUNC: 503 | *out_fg = A_FUNC_FG; 504 | *out_bg = A_FUNC_BG; 505 | break; 506 | case WT_KEYWORD: 507 | *out_fg = A_KEYWORD_FG; 508 | *out_bg = A_KEYWORD_BG; 509 | break; 510 | case WT_BASIC: 511 | *i = j - 1; 512 | return 1; 513 | } 514 | 515 | return 0; 516 | } 517 | -------------------------------------------------------------------------------- /src/hl/hl_cs.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_cs.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "conf.h" 8 | #include "util.h" 9 | 10 | #define A_PREPROC_FG CONF_A_ACCENT_3_FG 11 | #define A_PREPROC_BG CONF_A_ACCENT_3_BG 12 | #define A_KEYWORD_FG CONF_A_ACCENT_1_FG 13 | #define A_KEYWORD_BG CONF_A_ACCENT_1_BG 14 | #define A_COMMENT_FG CONF_A_COMMENT_FG 15 | #define A_COMMENT_BG CONF_A_COMMENT_BG 16 | #define A_STRING_FG CONF_A_STRING_FG 17 | #define A_STRING_BG CONF_A_STRING_BG 18 | #define A_FUNC_FG CONF_A_ACCENT_4_FG 19 | #define A_FUNC_BG CONF_A_ACCENT_4_BG 20 | #define A_SPECIAL_FG CONF_A_SPECIAL_FG 21 | #define A_SPECIAL_BG CONF_A_SPECIAL_BG 22 | 23 | #define SPECIAL L"+-()[].<>{}!~*&/%=?:|;,^" 24 | 25 | enum word_type 26 | { 27 | WT_FUNC, 28 | WT_KEYWORD, 29 | WT_BASIC, 30 | }; 31 | 32 | static int hl_preproc(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 33 | static int hl_string(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 34 | static int hl_char(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 35 | static int hl_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 36 | static int hl_special(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 37 | static int hl_word(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 38 | 39 | static wchar_t const *keywords[] = 40 | { 41 | L"abstract", 42 | L"add", 43 | L"alias", 44 | L"and", 45 | L"args", 46 | L"as", 47 | L"ascending", 48 | L"async", 49 | L"await", 50 | L"base", 51 | L"bool", 52 | L"break", 53 | L"by", 54 | L"byte", 55 | L"case", 56 | L"catch", 57 | L"char", 58 | L"checked", 59 | L"class", 60 | L"const", 61 | L"continue", 62 | L"decimal", 63 | L"default", 64 | L"delegate", 65 | L"descending", 66 | L"do", 67 | L"double", 68 | L"dynamic", 69 | L"else", 70 | L"enum", 71 | L"equals", 72 | L"event", 73 | L"explicit", 74 | L"extern", 75 | L"false", 76 | L"file", 77 | L"finally", 78 | L"fixed", 79 | L"float", 80 | L"for", 81 | L"foreach", 82 | L"from", 83 | L"get", 84 | L"global", 85 | L"goto", 86 | L"group", 87 | L"if", 88 | L"implicit", 89 | L"in", 90 | L"init", 91 | L"int", 92 | L"interface", 93 | L"internal", 94 | L"into", 95 | L"is", 96 | L"join", 97 | L"let", 98 | L"lock", 99 | L"long", 100 | L"managed", 101 | L"nameof", 102 | L"namespace", 103 | L"new", 104 | L"nint", 105 | L"not", 106 | L"notnull", 107 | L"nuint", 108 | L"null", 109 | L"object", 110 | L"on", 111 | L"operator", 112 | L"or", 113 | L"orderby", 114 | L"out", 115 | L"override", 116 | L"params", 117 | L"partial", 118 | L"private", 119 | L"protected", 120 | L"public", 121 | L"readonly", 122 | L"record", 123 | L"ref", 124 | L"remove", 125 | L"required", 126 | L"return", 127 | L"sbyte", 128 | L"scoped", 129 | L"sealed", 130 | L"select", 131 | L"set", 132 | L"short", 133 | L"sizeof", 134 | L"stackalloc", 135 | L"static", 136 | L"string", 137 | L"struct", 138 | L"switch", 139 | L"this", 140 | L"throw", 141 | L"true", 142 | L"try", 143 | L"typeof", 144 | L"uint", 145 | L"ulong", 146 | L"unchecked", 147 | L"unmanaged", 148 | L"unsafe", 149 | L"ushort", 150 | L"using", 151 | L"value", 152 | L"var", 153 | L"virtual", 154 | L"void", 155 | L"volatile", 156 | L"when", 157 | L"where", 158 | L"while", 159 | L"with", 160 | L"yield", 161 | }; 162 | 163 | int 164 | hl_cs_find(struct buf const *buf, 165 | size_t off, 166 | size_t *out_lb, 167 | size_t *out_ub, 168 | uint8_t *out_fg, 169 | uint8_t *out_bg) 170 | { 171 | for (size_t i = off; i < buf->size; ++i) 172 | { 173 | if (buf_get_wch(buf, i) == L'#') 174 | { 175 | if (!hl_preproc(buf, &i, out_lb, out_ub, out_fg, out_bg)) 176 | return 0; 177 | } 178 | else if (buf_get_wch(buf, i) == L'"') 179 | { 180 | if (!hl_string(buf, &i, out_lb, out_ub, out_fg, out_bg)) 181 | return 0; 182 | } 183 | else if (buf_get_wch(buf, i) == L'\'') 184 | { 185 | if (!hl_char(buf, &i, out_lb, out_ub, out_fg, out_bg)) 186 | return 0; 187 | } 188 | else if (i + 1 < buf->size 189 | && buf_get_wch(buf, i) == L'/' 190 | && wcschr(L"/*", buf_get_wch(buf, i + 1))) 191 | { 192 | if (!hl_comment(buf, &i, out_lb, out_ub, out_fg, out_bg)) 193 | return 0; 194 | } 195 | else if (wcschr(SPECIAL, buf_get_wch(buf, i))) 196 | { 197 | if (!hl_special(buf, &i, out_lb, out_ub, out_fg, out_bg)) 198 | return 0; 199 | } 200 | else if (iswalpha(buf_get_wch(buf, i)) 201 | || buf_get_wch(buf, i) == L'_') 202 | { 203 | if (!hl_word(buf, &i, out_lb, out_ub, out_fg, out_bg)) 204 | return 0; 205 | } 206 | } 207 | 208 | return 1; 209 | } 210 | 211 | static int 212 | hl_preproc(struct buf const *buf, 213 | size_t *i, 214 | size_t *out_lb, 215 | size_t *out_ub, 216 | uint8_t *out_fg, 217 | uint8_t *out_bg) 218 | { 219 | size_t j = *i; 220 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 221 | ++j; 222 | 223 | *out_lb = *i; 224 | *out_ub = j; 225 | *out_fg = A_PREPROC_FG; 226 | *out_bg = A_PREPROC_BG; 227 | 228 | return 0; 229 | } 230 | 231 | static int 232 | hl_string(struct buf const *buf, 233 | size_t *i, 234 | size_t *out_lb, 235 | size_t *out_ub, 236 | uint8_t *out_fg, 237 | uint8_t *out_bg) 238 | { 239 | // TODO: implement. 240 | // maybe a separate function should be used for raw string highlight. 241 | return 1; 242 | } 243 | 244 | static int 245 | hl_char(struct buf const *buf, 246 | size_t *i, 247 | size_t *out_lb, 248 | size_t *out_ub, 249 | uint8_t *out_fg, 250 | uint8_t *out_bg) 251 | { 252 | size_t j = *i + 1; 253 | if (j < buf->size && buf_get_wch(buf, j) == L'\\') 254 | { 255 | ++j; 256 | if (j < buf->size && wcschr(L"xu", buf_get_wch(buf, j))) 257 | { 258 | ++j; 259 | while (j < buf->size && iswxdigit(buf_get_wch(buf, j))) 260 | ++j; 261 | } 262 | else 263 | ++j; 264 | } 265 | else 266 | ++j; 267 | 268 | if (j < buf->size && buf_get_wch(buf, j) == L'\'') 269 | { 270 | *out_lb = *i; 271 | *out_ub = j + 1; 272 | *out_fg = A_STRING_FG; 273 | *out_bg = A_STRING_BG; 274 | 275 | return 0; 276 | } 277 | 278 | return 1; 279 | } 280 | 281 | static int 282 | hl_comment(struct buf const *buf, 283 | size_t *i, 284 | size_t *out_lb, 285 | size_t *out_ub, 286 | uint8_t *out_fg, 287 | uint8_t *out_bg) 288 | { 289 | size_t j = *i + 2; 290 | if (buf_get_wch(buf, *i + 1) == L'/') 291 | { 292 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 293 | ++j; 294 | } 295 | else 296 | { 297 | while (j + 1 < buf->size) 298 | { 299 | wchar_t cmp[3]; 300 | if (!wcscmp(buf_get_wstr(buf, cmp, j, 3), L"*/")) 301 | break; 302 | ++j; 303 | } 304 | } 305 | 306 | *out_lb = *i; 307 | *out_ub = j + 2 * (buf_get_wch(buf, j) == '*'); 308 | *out_fg = A_COMMENT_FG; 309 | *out_bg = A_COMMENT_BG; 310 | 311 | return 0; 312 | } 313 | 314 | static int 315 | hl_special(struct buf const *buf, 316 | size_t *i, 317 | size_t *out_lb, 318 | size_t *out_ub, 319 | uint8_t *out_fg, 320 | uint8_t *out_bg) 321 | { 322 | size_t j = *i + 1; 323 | while (j < buf->size && wcschr(SPECIAL, buf_get_wch(buf, j))) 324 | ++j; 325 | 326 | *out_lb = *i; 327 | *out_ub = j; 328 | *out_fg = A_SPECIAL_FG; 329 | *out_bg = A_SPECIAL_BG; 330 | 331 | return 0; 332 | } 333 | 334 | static int 335 | hl_word(struct buf const *buf, 336 | size_t *i, 337 | size_t *out_lb, 338 | size_t *out_ub, 339 | uint8_t *out_fg, 340 | uint8_t *out_bg) 341 | { 342 | enum word_type wt = WT_BASIC; 343 | bool ident_pfx = *i > 0 && buf_get_wch(buf, *i - 1) == L'@'; 344 | 345 | size_t j = *i; 346 | while (j < buf->size) 347 | { 348 | if (buf_get_wch(buf, j) != L'_' 349 | && !iswalnum(buf_get_wch(buf, j))) 350 | { 351 | break; 352 | } 353 | ++j; 354 | } 355 | 356 | // check for (generic) function. 357 | // similarly to C++ highlight mode template checking, this is very 358 | // stupid and doesn't account for embedded comments. 359 | // however, this defect arguably doesn't even slightly matter. 360 | 361 | size_t k = j; 362 | while (k < buf->size && iswspace(buf_get_wch(buf, k))) 363 | ++k; 364 | 365 | if (k < buf->size && buf_get_wch(buf, k) == L'<') 366 | { 367 | ++k; 368 | for (unsigned nopen = 1; k < buf->size && nopen > 0; ++k) 369 | { 370 | wchar_t wch = buf_get_wch(buf, k); 371 | nopen += wch == L'<'; 372 | nopen -= wch == L'>'; 373 | } 374 | } 375 | 376 | while (k < buf->size && iswspace(buf_get_wch(buf, k))) 377 | ++k; 378 | 379 | if (k < buf->size && buf_get_wch(buf, k) == L'(') 380 | wt = WT_FUNC; 381 | 382 | if (!ident_pfx) 383 | { 384 | for (size_t kw = 0; kw < ARRAY_SIZE(keywords); ++kw) 385 | { 386 | size_t len = j - *i + 1; 387 | if (len > 64) 388 | break; 389 | 390 | wchar_t cmp[64]; 391 | buf_get_wstr(buf, cmp, *i, len); 392 | 393 | if (!wcscmp(keywords[kw], cmp)) 394 | { 395 | wt = WT_KEYWORD; 396 | break; 397 | } 398 | } 399 | } 400 | 401 | *out_lb = *i; 402 | *out_ub = j; 403 | 404 | switch (wt) 405 | { 406 | case WT_FUNC: 407 | *out_fg = A_FUNC_FG; 408 | *out_bg = A_FUNC_BG; 409 | break; 410 | case WT_KEYWORD: 411 | *out_fg = A_KEYWORD_FG; 412 | *out_bg = A_KEYWORD_BG; 413 | break; 414 | case WT_BASIC: 415 | *i = j - 1; 416 | return 1; 417 | } 418 | 419 | return 0; 420 | } 421 | -------------------------------------------------------------------------------- /src/hl/hl_html.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_html.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "conf.h" 7 | 8 | #define A_TAG_FG CONF_A_ACCENT_1_FG 9 | #define A_TAG_BG CONF_A_ACCENT_1_BG 10 | #define A_ENT_FG CONF_A_ACCENT_2_FG 11 | #define A_ENT_BG CONF_A_ACCENT_2_BG 12 | #define A_COMMENT_FG CONF_A_COMMENT_FG 13 | #define A_COMMENT_BG CONF_A_COMMENT_BG 14 | 15 | static int hl_tag(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 16 | static int hl_ent(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 17 | static int hl_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 18 | 19 | int 20 | hl_html_find(struct buf const *buf, 21 | size_t off, 22 | size_t *out_lb, 23 | size_t *out_ub, 24 | uint8_t *out_fg, 25 | uint8_t *out_bg) 26 | { 27 | for (size_t i = off; i < buf->size; ++i) 28 | { 29 | wchar_t cmp[5]; 30 | 31 | if (i + 3 < buf->size 32 | && !wcscmp(buf_get_wstr(buf, cmp, i, 5), L"")) 111 | ++j; 112 | 113 | if (j == buf->size) 114 | { 115 | *i += 3; 116 | return 1; 117 | } 118 | 119 | *out_lb = *i; 120 | *out_ub = j + 3; 121 | *out_fg = A_COMMENT_FG; 122 | *out_bg = A_COMMENT_BG; 123 | 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /src/hl/hl_md.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_md.h" 2 | 3 | #include 4 | 5 | #include "conf.h" 6 | 7 | #define A_CODE_BLOCK_FG CONF_A_ACCENT_3_FG 8 | #define A_CODE_BLOCK_BG CONF_A_ACCENT_3_BG 9 | #define A_HEADING_FG CONF_A_ACCENT_2_FG 10 | #define A_HEADING_BG CONF_A_ACCENT_2_BG 11 | #define A_BLOCK_FG CONF_A_ACCENT_4_FG 12 | #define A_BLOCK_BG CONF_A_ACCENT_4_BG 13 | #define A_ULIST_FG CONF_A_ACCENT_1_FG 14 | #define A_ULIST_BG CONF_A_ACCENT_1_BG 15 | #define A_OLIST_FG CONF_A_ACCENT_1_FG 16 | #define A_OLIST_BG CONF_A_ACCENT_1_BG 17 | 18 | static int hl_code_block(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 19 | static int hl_heading(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 20 | static int hl_block(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 21 | static int hl_ulist(struct buf const *buf_t, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 22 | static int hl_olist(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 23 | static size_t first_ln_ch(struct buf const *buf, size_t pos); 24 | static size_t para_end(struct buf const *buf, size_t pos); 25 | 26 | int 27 | hl_md_find(struct buf const *buf, 28 | size_t off, 29 | size_t *out_lb, 30 | size_t *out_ub, 31 | uint8_t *out_fg, 32 | uint8_t *out_bg) 33 | { 34 | for (size_t i = off; i < buf->size; ++i) 35 | { 36 | wchar_t cmp_buf[4]; 37 | 38 | if (buf_get_wch(buf, i) == L'#') 39 | { 40 | if (!hl_heading(buf, &i, out_lb, out_ub, out_fg, out_bg)) 41 | return 0; 42 | } 43 | else if (buf_get_wch(buf, i) == L'>') 44 | { 45 | if (!hl_block(buf, &i, out_lb, out_ub, out_fg, out_bg)) 46 | return 0; 47 | } 48 | else if (i + 2 < buf->size 49 | && !wcscmp(buf_get_wstr(buf, cmp_buf, i, 4), L"```")) 50 | { 51 | if (!hl_code_block(buf, &i, out_lb, out_ub, out_fg, out_bg)) 52 | return 0; 53 | } 54 | else if (wcschr(L"*-", buf_get_wch(buf, i))) 55 | { 56 | if (!hl_ulist(buf, &i, out_lb, out_ub, out_fg, out_bg)) 57 | return 0; 58 | } 59 | else if (iswdigit(buf_get_wch(buf, i))) 60 | { 61 | if (!hl_olist(buf, &i, out_lb, out_ub, out_fg, out_bg)) 62 | return 0; 63 | } 64 | else if (buf_get_wch(buf, i) == L'\\') 65 | { 66 | ++i; 67 | continue; 68 | } 69 | } 70 | 71 | return 1; 72 | } 73 | 74 | static int 75 | hl_code_block(struct buf const *buf, 76 | size_t *i, 77 | size_t *out_lb, 78 | size_t *out_ub, 79 | uint8_t *out_fg, 80 | uint8_t *out_bg) 81 | { 82 | if (first_ln_ch(buf, *i) != *i) 83 | return 1; 84 | 85 | for (size_t j = *i + 3; j + 2 < buf->size; ++j) 86 | { 87 | wchar_t cmp_buf[4]; 88 | 89 | if (buf_get_wch(buf, j) == L'\\') 90 | { 91 | ++j; 92 | continue; 93 | } 94 | else if (!wcscmp(buf_get_wstr(buf, cmp_buf, j, 4), L"```")) 95 | { 96 | if (first_ln_ch(buf, j) != j) 97 | continue; 98 | 99 | size_t k = j + 3; 100 | while (k < buf->size && buf_get_wch(buf, k) != L'\n') 101 | { 102 | if (!iswspace(buf_get_wch(buf, k))) 103 | goto skip; 104 | ++k; 105 | } 106 | 107 | *out_lb = *i; 108 | *out_ub = j + 3; 109 | *out_fg = A_CODE_BLOCK_FG; 110 | *out_bg = A_CODE_BLOCK_BG; 111 | 112 | return 0; 113 | } 114 | skip:; 115 | } 116 | 117 | return 1; 118 | } 119 | 120 | static int 121 | hl_heading(struct buf const *buf, 122 | size_t *i, 123 | size_t *out_lb, 124 | size_t *out_ub, 125 | uint8_t *out_fg, 126 | uint8_t *out_bg) 127 | { 128 | if (first_ln_ch(buf, *i) != *i) 129 | return 1; 130 | 131 | size_t j = *i + 1; 132 | while (j < buf->size && buf_get_wch(buf, j) == L'#') 133 | ++j; 134 | if (j >= buf->size 135 | || !iswspace(buf_get_wch(buf, j)) 136 | || buf_get_wch(buf, j) == L'\n') 137 | { 138 | return 1; 139 | } 140 | 141 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 142 | ++j; 143 | 144 | *out_lb = *i; 145 | *out_ub = j; 146 | *out_fg = A_HEADING_FG; 147 | *out_bg = A_HEADING_BG; 148 | 149 | return 0; 150 | } 151 | 152 | static int 153 | hl_block(struct buf const *buf, 154 | size_t *i, 155 | size_t *out_lb, 156 | size_t *out_ub, 157 | uint8_t *out_fg, 158 | uint8_t *out_bg) 159 | { 160 | if (first_ln_ch(buf, *i) != *i) 161 | return 1; 162 | 163 | size_t j = *i + 1; 164 | while (j < buf->size 165 | && iswspace(buf_get_wch(buf, j)) 166 | && buf_get_wch(buf, j) != L'\n') 167 | { 168 | ++j; 169 | } 170 | if (j >= buf->size || iswspace(buf_get_wch(buf, j))) 171 | return 1; 172 | 173 | *out_lb = *i; 174 | *out_ub = para_end(buf, j); 175 | *out_fg = A_BLOCK_FG; 176 | *out_bg = A_BLOCK_BG; 177 | 178 | return 0; 179 | } 180 | 181 | static int 182 | hl_ulist(struct buf const *buf, 183 | size_t *i, 184 | size_t *out_lb, 185 | size_t *out_ub, 186 | uint8_t *out_fg, 187 | uint8_t *out_bg) 188 | { 189 | if (first_ln_ch(buf, *i) != *i) 190 | return 1; 191 | 192 | size_t j = *i + 1; 193 | while (j < buf->size && buf_get_wch(buf, j) == buf_get_wch(buf, *i)) 194 | ++j; 195 | if (j >= buf->size 196 | || !iswspace(buf_get_wch(buf, j)) 197 | || buf_get_wch(buf, j) == L'\n') 198 | { 199 | return 1; 200 | } 201 | 202 | *out_lb = *i; 203 | *out_ub = para_end(buf, j); 204 | *out_fg = A_ULIST_FG; 205 | *out_bg = A_ULIST_BG; 206 | 207 | return 0; 208 | } 209 | 210 | static int 211 | hl_olist(struct buf const *buf, 212 | size_t *i, 213 | size_t *out_lb, 214 | size_t *out_ub, 215 | uint8_t *out_fg, 216 | uint8_t *out_bg) 217 | { 218 | if (first_ln_ch(buf, *i) != *i) 219 | return 1; 220 | 221 | size_t j = *i + 1; 222 | while (j < buf->size && iswdigit(buf_get_wch(buf, j))) 223 | ++j; 224 | if (j + 1 >= buf->size 225 | || buf_get_wch(buf, j) != L'.' 226 | || !iswspace(buf_get_wch(buf, j + 1)) 227 | || buf_get_wch(buf, j + 1) == L'\n') 228 | { 229 | return 1; 230 | } 231 | 232 | *out_lb = *i; 233 | *out_ub = para_end(buf, j + 1); 234 | *out_fg = A_OLIST_FG; 235 | *out_bg = A_OLIST_BG; 236 | 237 | return 0; 238 | } 239 | 240 | static size_t 241 | first_ln_ch(struct buf const *buf, size_t pos) 242 | { 243 | size_t ln = pos; 244 | while (ln > 0 && buf_get_wch(buf, ln) != L'\n') 245 | --ln; 246 | 247 | size_t first_ch = ln; 248 | while (first_ch < buf->size && iswspace(buf_get_wch(buf, first_ch))) 249 | ++first_ch; 250 | 251 | return first_ch; 252 | } 253 | 254 | static size_t 255 | para_end(struct buf const *buf, size_t pos) 256 | { 257 | size_t i = pos; 258 | 259 | for (;;) 260 | { 261 | while (i < buf->size && buf_get_wch(buf, i) != L'\n') 262 | ++i; 263 | 264 | i += i < buf->size; 265 | while (i < buf->size 266 | && iswspace(buf_get_wch(buf, i)) 267 | && buf_get_wch(buf, i) != L'\n') 268 | { 269 | ++i; 270 | } 271 | 272 | if (i >= buf->size || buf_get_wch(buf, i) == L'\n') 273 | break; 274 | } 275 | 276 | return i; 277 | } 278 | -------------------------------------------------------------------------------- /src/hl/hl_rs.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_rs.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "conf.h" 7 | 8 | #define A_SPECIAL_FG CONF_A_SPECIAL_FG 9 | #define A_SPECIAL_BG CONF_A_SPECIAL_BG 10 | #define A_COMMENT_FG CONF_A_COMMENT_FG 11 | #define A_COMMENT_BG CONF_A_COMMENT_BG 12 | #define A_STRING_FG CONF_A_STRING_FG 13 | #define A_STRING_BG CONF_A_STRING_BG 14 | #define A_CONST_FG CONF_A_ACCENT_2_FG 15 | #define A_CONST_BG CONF_A_ACCENT_2_BG 16 | #define A_TYPE_FG CONF_A_ACCENT_2_FG 17 | #define A_TYPE_BG CONF_A_ACCENT_2_BG 18 | #define A_FUNC_FG CONF_A_ACCENT_4_FG 19 | #define A_FUNC_BG CONF_A_ACCENT_4_BG 20 | #define A_KEYWORD_FG CONF_A_ACCENT_1_FG 21 | #define A_KEYWORD_BG CONF_A_ACCENT_1_BG 22 | 23 | #define SPECIAL L"!=%&*+,->./:;<@^|?#$(){}[]" 24 | 25 | enum word_type 26 | { 27 | WT_CONST, 28 | WT_TYPE, 29 | WT_FUNC, 30 | WT_KEYWORD, 31 | WT_BASIC, 32 | }; 33 | 34 | static int hl_string(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 35 | static int hl_rstring(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 36 | static int hl_quote(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 37 | static int hl_ln_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 38 | static int hl_blk_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 39 | static int hl_special(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 40 | static int hl_word(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 41 | 42 | static wchar_t const *keywords[] = 43 | { 44 | L"as", 45 | L"async", 46 | L"await", 47 | L"break", 48 | L"const", 49 | L"continue", 50 | L"crate", 51 | L"dyn", 52 | L"else", 53 | L"enum", 54 | L"extern", 55 | L"false", 56 | L"fn", 57 | L"for", 58 | L"if", 59 | L"impl", 60 | L"in", 61 | L"let", 62 | L"loop", 63 | L"match", 64 | L"mod", 65 | L"move", 66 | L"mut", 67 | L"pub", 68 | L"ref", 69 | L"return", 70 | L"Self", 71 | L"self", 72 | L"static", 73 | L"struct", 74 | L"super", 75 | L"trait", 76 | L"type", 77 | L"union", 78 | L"unsafe", 79 | L"use", 80 | L"where", 81 | L"while", 82 | L"abstract", 83 | L"become", 84 | L"box", 85 | L"do", 86 | L"final", 87 | L"macro", 88 | L"override", 89 | L"priv", 90 | L"try", 91 | L"typeof", 92 | L"unsized", 93 | L"virtual", 94 | L"yield", 95 | }; 96 | 97 | int 98 | hl_rs_find(struct buf const *buf, 99 | size_t off, 100 | size_t *out_lb, 101 | size_t *out_ub, 102 | uint8_t *out_fg, 103 | uint8_t *out_bg) 104 | { 105 | for (size_t i = off; i < buf->size; ++i) 106 | { 107 | if (buf_get_wch(buf, i) == L'"') 108 | { 109 | if (!hl_string(buf, &i, out_lb, out_ub, out_fg, out_bg)) 110 | return 0; 111 | } 112 | else if (i + 1 < buf->size 113 | && buf_get_wch(buf, i) == L'r' 114 | && wcschr(L"\"#", buf_get_wch(buf, i + 1))) 115 | { 116 | if (!hl_rstring(buf, &i, out_lb, out_ub, out_fg, out_bg)) 117 | return 0; 118 | } 119 | else if (buf_get_wch(buf, i) == L'\'') 120 | { 121 | if (!hl_quote(buf, &i, out_lb, out_ub, out_fg, out_bg)) 122 | return 0; 123 | } 124 | else if (i + 1 < buf->size 125 | && buf_get_wch(buf, i) == L'/' 126 | && wcschr(L"/*", buf_get_wch(buf, i + 1))) 127 | { 128 | switch (buf_get_wch(buf, i + 1)) 129 | { 130 | case L'/': 131 | if (!hl_ln_comment(buf, &i, out_lb, out_ub, out_fg, out_bg)) 132 | return 0; 133 | break; 134 | case L'*': 135 | if (!hl_blk_comment(buf, &i, out_lb, out_ub, out_fg, out_bg)) 136 | return 0; 137 | break; 138 | default: 139 | break; 140 | } 141 | } 142 | else if (wcschr(SPECIAL, buf_get_wch(buf, i))) 143 | { 144 | if (!hl_special(buf, &i, out_lb, out_ub, out_fg, out_bg)) 145 | return 0; 146 | } 147 | else if (iswalpha(buf_get_wch(buf, i)) 148 | || buf_get_wch(buf, i) == L'_') 149 | { 150 | if (!hl_word(buf, &i, out_lb, out_ub, out_fg, out_bg)) 151 | return 0; 152 | } 153 | } 154 | 155 | return 1; 156 | } 157 | 158 | static int 159 | hl_string(struct buf const *buf, 160 | size_t *i, 161 | size_t *out_lb, 162 | size_t *out_ub, 163 | uint8_t *out_fg, 164 | uint8_t *out_bg) 165 | { 166 | size_t j = *i; 167 | while (j < buf->size) 168 | { 169 | ++j; 170 | if (buf_get_wch(buf, j) == L'\\') 171 | { 172 | ++j; 173 | continue; 174 | } 175 | else if (buf_get_wch(buf, j) == L'"') 176 | break; 177 | } 178 | 179 | *out_lb = *i; 180 | *out_ub = j + (j < buf->size); 181 | *out_fg = A_STRING_FG; 182 | *out_bg = A_STRING_BG; 183 | 184 | return 0; 185 | } 186 | 187 | static int 188 | hl_rstring(struct buf const *buf, 189 | size_t *i, 190 | size_t *out_lb, 191 | size_t *out_ub, 192 | uint8_t *out_fg, 193 | uint8_t *out_bg) 194 | { 195 | size_t j = *i + 1; 196 | while (j < buf->size && buf_get_wch(buf, j) == L'#') 197 | ++j; 198 | if (j >= buf->size || buf_get_wch(buf, j) != L'"') 199 | return 1; 200 | j += j < buf->size; 201 | 202 | unsigned nhash = (j - 1) - (*i + 1); 203 | 204 | wchar_t *search = malloc(sizeof(wchar_t) * (2 + nhash)); 205 | search[0] = L'"'; 206 | search[1 + nhash] = 0; 207 | for (unsigned i = 1; i < 1 + nhash; ++i) 208 | search[i] = L'#'; 209 | 210 | while (j < buf->size) 211 | { 212 | // probably segfault if 64 or more hashes are used in a raw 213 | // string. 214 | // this is a non-issue probably not worth fully fixing. 215 | // TODO: make this not crash. 216 | wchar_t cmp[64]; 217 | 218 | if (!wcscmp(buf_get_wstr(buf, cmp, j, 2 + nhash), search)) 219 | { 220 | *out_lb = *i; 221 | *out_ub = j + nhash + 1; 222 | *out_fg = A_STRING_FG; 223 | *out_bg = A_STRING_BG; 224 | 225 | free(search); 226 | return 0; 227 | } 228 | ++j; 229 | } 230 | 231 | free(search); 232 | return 1; 233 | } 234 | 235 | static int 236 | hl_quote(struct buf const *buf, 237 | size_t *i, 238 | size_t *out_lb, 239 | size_t *out_ub, 240 | uint8_t *out_fg, 241 | uint8_t *out_bg) 242 | { 243 | size_t j = *i + 1; 244 | if (j < buf->size && buf_get_wch(buf, j) == L'\\') 245 | j += 2; 246 | else 247 | ++j; 248 | 249 | *out_lb = *i; 250 | if (j < buf->size && buf_get_wch(buf, j) == L'\'') 251 | { 252 | *out_ub = j + 1; 253 | *out_fg = A_STRING_FG; 254 | *out_bg = A_STRING_BG; 255 | } 256 | else 257 | { 258 | *out_ub = *i + 1; 259 | *out_fg = A_SPECIAL_FG; 260 | *out_bg = A_SPECIAL_BG; 261 | } 262 | 263 | return 0; 264 | } 265 | 266 | static int 267 | hl_ln_comment(struct buf const *buf, 268 | size_t *i, 269 | size_t *out_lb, 270 | size_t *out_ub, 271 | uint8_t *out_fg, 272 | uint8_t *out_bg) 273 | { 274 | size_t j = *i + 2; 275 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 276 | ++j; 277 | 278 | *out_lb = *i; 279 | *out_ub = j; 280 | *out_fg = A_COMMENT_FG; 281 | *out_bg = A_COMMENT_BG; 282 | 283 | return 0; 284 | } 285 | 286 | static int 287 | hl_blk_comment(struct buf const *buf, 288 | size_t *i, 289 | size_t *out_lb, 290 | size_t *out_ub, 291 | uint8_t *out_fg, 292 | uint8_t *out_bg) 293 | { 294 | unsigned nopen = 1; 295 | size_t j = *i + 2; 296 | while (j + 1 < buf->size) 297 | { 298 | wchar_t cmp_buf[3]; 299 | buf_get_wstr(buf, cmp_buf, j, 3); 300 | 301 | if (!wcscmp(cmp_buf, L"*/")) 302 | { 303 | --nopen; 304 | ++j; 305 | } 306 | else if (!wcscmp(cmp_buf, L"/*")) 307 | { 308 | ++nopen; 309 | ++j; 310 | } 311 | 312 | if (nopen == 0) 313 | { 314 | *out_lb = *i; 315 | *out_ub = j + 1; 316 | *out_fg = A_COMMENT_FG; 317 | *out_bg = A_COMMENT_BG; 318 | 319 | return 0; 320 | } 321 | 322 | ++j; 323 | } 324 | 325 | return 1; 326 | } 327 | 328 | static int 329 | hl_special(struct buf const *buf, 330 | size_t *i, 331 | size_t *out_lb, 332 | size_t *out_ub, 333 | uint8_t *out_fg, 334 | uint8_t *out_bg) 335 | { 336 | size_t j = *i + 1; 337 | while (j < buf->size && wcschr(SPECIAL, buf_get_wch(buf, j))) 338 | ++j; 339 | 340 | *out_lb = *i; 341 | *out_ub = j; 342 | *out_fg = A_SPECIAL_FG; 343 | *out_bg = A_SPECIAL_BG; 344 | 345 | return 0; 346 | } 347 | 348 | static int 349 | hl_word(struct buf const *buf, 350 | size_t *i, 351 | size_t *out_lb, 352 | size_t *out_ub, 353 | uint8_t *out_fg, 354 | uint8_t *out_bg) 355 | { 356 | enum word_type wt = WT_BASIC; 357 | 358 | size_t j = *i; 359 | unsigned nunder = 0, nlower = 0, nupper = 0; 360 | while (j < buf->size) 361 | { 362 | wchar_t wch = buf_get_wch(buf, j); 363 | 364 | if (wch != L'_' && !iswalnum(wch)) 365 | break; 366 | 367 | if (wch == L'_') 368 | ++nunder; 369 | else if (iswlower(wch)) 370 | ++nlower; 371 | else if (iswupper(wch)) 372 | ++nupper; 373 | 374 | ++j; 375 | } 376 | 377 | if (!nunder && nlower && nupper) 378 | wt = WT_TYPE; 379 | else if (!nlower && nupper) 380 | wt = WT_CONST; 381 | else 382 | wt = WT_BASIC; 383 | 384 | if (j - *i == 1 && buf_get_wch(buf, *i) == L'_') 385 | wt = WT_BASIC; 386 | 387 | if (wt == WT_BASIC) 388 | { 389 | size_t k = j; 390 | 391 | // whitespace scenario is not handled here like in C highlight 392 | // mode. 393 | // this is because seeing something like: 394 | // ``` 395 | // function_call (...); // notice the space between `l` and `(`. 396 | // ``` 397 | // is common enough in C that it merits inclusion in the 398 | // highlight handling, but noone really ever does that in Rust, 399 | // so it is not handled. 400 | 401 | wchar_t cmp_buf[4]; 402 | if (k + 2 < buf->size 403 | && !wcscmp(buf_get_wstr(buf, cmp_buf, k, 4), L"::<")) 404 | { 405 | k += 3; 406 | unsigned nopen = 1; 407 | while (k < buf->size && nopen > 0) 408 | { 409 | wchar_t wch = buf_get_wch(buf, k); 410 | nopen += wch == L'<'; 411 | nopen -= wch == L'>'; 412 | ++k; 413 | } 414 | } 415 | 416 | if (k < buf->size && buf_get_wch(buf, k) == L'(') 417 | wt = WT_FUNC; 418 | } 419 | 420 | for (size_t kw = 0; kw < ARRAY_SIZE(keywords); ++kw) 421 | { 422 | size_t len = j - *i + 1; 423 | if (len > 64) 424 | break; 425 | 426 | wchar_t cmp[64]; 427 | buf_get_wstr(buf, cmp, *i, len); 428 | 429 | if (!wcscmp(keywords[kw], cmp)) 430 | { 431 | wt = WT_KEYWORD; 432 | break; 433 | } 434 | } 435 | 436 | *out_lb = *i; 437 | *out_ub = j; 438 | 439 | switch (wt) 440 | { 441 | case WT_CONST: 442 | *out_fg = A_CONST_FG; 443 | *out_bg = A_CONST_BG; 444 | break; 445 | case WT_TYPE: 446 | *out_fg = A_TYPE_FG; 447 | *out_bg = A_TYPE_BG; 448 | break; 449 | case WT_FUNC: 450 | *out_fg = A_FUNC_FG; 451 | *out_bg = A_FUNC_BG; 452 | break; 453 | case WT_KEYWORD: 454 | *out_fg = A_KEYWORD_FG; 455 | *out_bg = A_KEYWORD_BG; 456 | break; 457 | case WT_BASIC: 458 | *i = j - 1; 459 | return 1; 460 | } 461 | 462 | return 0; 463 | } 464 | -------------------------------------------------------------------------------- /src/hl/hl_s.c: -------------------------------------------------------------------------------- 1 | #include "hl/hl_s.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "conf.h" 7 | 8 | // implemented only for x86_64 since that's the only assembly I write. 9 | // instruction mnemonics are not highlighted since there are way too many of 10 | // them to do a basic keyword highlighting system. 11 | 12 | #define A_PREPROC_FG CONF_A_ACCENT_3_FG 13 | #define A_PREPROC_BG CONF_A_ACCENT_3_BG 14 | #define A_REG_FG CONF_A_ACCENT_1_FG 15 | #define A_REG_BG CONF_A_ACCENT_1_BG 16 | #define A_COMMENT_FG CONF_A_COMMENT_FG 17 | #define A_COMMENT_BG CONF_A_COMMENT_BG 18 | #define A_STRING_FG CONF_A_STRING_FG 19 | #define A_STRING_BG CONF_A_STRING_BG 20 | #define A_MACRO_FG CONF_A_ACCENT_2_FG 21 | #define A_MACRO_BG CONF_A_ACCENT_2_BG 22 | #define A_SPECIAL_FG CONF_A_SPECIAL_FG 23 | #define A_SPECIAL_BG CONF_A_SPECIAL_BG 24 | 25 | // same as C special chars but missing `.` for preprocessor directives. 26 | #define SPECIAL L"+-()[]<>{}!~*&/%=?:|;,^" 27 | 28 | enum word_type 29 | { 30 | WT_MACRO, 31 | WT_REG, 32 | WT_BASIC, 33 | }; 34 | 35 | static int hl_preproc(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 36 | static int hl_comment(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 37 | static int hl_string(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 38 | static int hl_char(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 39 | static int hl_word(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 40 | static int hl_special(struct buf const *buf, size_t *i, size_t *out_lb, size_t *out_ub, uint8_t *out_fg, uint8_t *out_bg); 41 | 42 | static wchar_t const *regs[] = 43 | { 44 | L"rax", 45 | L"eax", 46 | L"ax", 47 | L"ah", 48 | L"al", 49 | L"rbx", 50 | L"ebx", 51 | L"bx", 52 | L"bh", 53 | L"bl", 54 | L"rcx", 55 | L"ecx", 56 | L"cx", 57 | L"ch", 58 | L"cl", 59 | L"rdx", 60 | L"edx", 61 | L"dx", 62 | L"dh", 63 | L"dl", 64 | L"rsi", 65 | L"esi", 66 | L"si", 67 | L"sil", 68 | L"rdi", 69 | L"edi", 70 | L"di", 71 | L"dil", 72 | L"rsp", 73 | L"esp", 74 | L"sp", 75 | L"spl", 76 | L"rbp", 77 | L"ebp", 78 | L"bp", 79 | L"bpl", 80 | L"r8", 81 | L"r8d", 82 | L"r8w", 83 | L"r8b", 84 | L"r9", 85 | L"r9d", 86 | L"r9w", 87 | L"r9b", 88 | L"r10", 89 | L"r10d", 90 | L"r10w", 91 | L"r10b", 92 | L"r11", 93 | L"r11d", 94 | L"r11w", 95 | L"r11b", 96 | L"r12", 97 | L"r12d", 98 | L"r12w", 99 | L"r12b", 100 | L"r13", 101 | L"r13d", 102 | L"r13w", 103 | L"r13b", 104 | L"r14", 105 | L"r14d", 106 | L"r14w", 107 | L"r14b", 108 | L"r15", 109 | L"r15d", 110 | L"r15w", 111 | L"r15b", 112 | L"rip", 113 | L"eip", 114 | L"ip", 115 | L"cs", 116 | L"ds", 117 | L"es", 118 | L"fs", 119 | L"gs", 120 | L"ss", 121 | L"cr0", 122 | L"cr1", 123 | L"cr2", 124 | L"cr3", 125 | L"cr4", 126 | L"cr5", 127 | L"cr6", 128 | L"cr7", 129 | L"cr8", 130 | L"cr9", 131 | L"cr10", 132 | L"cr11", 133 | L"cr12", 134 | L"cr13", 135 | L"cr14", 136 | L"cr15", 137 | L"dr0", 138 | L"dr1", 139 | L"dr2", 140 | L"dr3", 141 | L"dr4", 142 | L"dr5", 143 | L"dr6", 144 | L"dr7", 145 | }; 146 | 147 | int 148 | hl_s_find(struct buf const *buf, 149 | size_t off, 150 | size_t *out_lb, 151 | size_t *out_ub, 152 | uint8_t *out_fg, 153 | uint8_t *out_bg) 154 | { 155 | for (size_t i = off; i < buf->size; ++i) 156 | { 157 | if (buf_get_wch(buf, i) == L'.') 158 | { 159 | if (!hl_preproc(buf, &i, out_lb, out_ub, out_fg, out_bg)) 160 | return 0; 161 | } 162 | else if (buf_get_wch(buf, i) == L'"') 163 | { 164 | if (!hl_string(buf, &i, out_lb, out_ub, out_fg, out_bg)) 165 | return 0; 166 | } 167 | else if (buf_get_wch(buf, i) == L'\'') 168 | { 169 | if (!hl_char(buf, &i, out_lb, out_ub, out_fg, out_bg)) 170 | return 0; 171 | } 172 | else if (i + 1 < buf->size 173 | && buf_get_wch(buf, i) == L'/' 174 | && wcschr(L"/*", buf_get_wch(buf, i + 1))) 175 | { 176 | if (!hl_comment(buf, &i, out_lb, out_ub, out_fg, out_bg)) 177 | return 0; 178 | } 179 | else if (wcschr(SPECIAL, buf_get_wch(buf, i))) 180 | { 181 | if (!hl_special(buf, &i, out_lb, out_ub, out_fg, out_bg)) 182 | return 0; 183 | } 184 | else if (iswalpha(buf_get_wch(buf, i)) 185 | || buf_get_wch(buf, i) == L'_') 186 | { 187 | if (!hl_word(buf, &i, out_lb, out_ub, out_fg, out_bg)) 188 | return 0; 189 | } 190 | } 191 | 192 | return 1; 193 | } 194 | 195 | static int 196 | hl_preproc(struct buf const *buf, 197 | size_t *i, 198 | size_t *out_lb, 199 | size_t *out_ub, 200 | uint8_t *out_fg, 201 | uint8_t *out_bg) 202 | { 203 | size_t j = *i + 1; 204 | while (j < buf->size) 205 | { 206 | if (buf_get_wch(buf, j) != L'_' 207 | && !iswalnum(buf_get_wch(buf, j))) 208 | { 209 | break; 210 | } 211 | ++j; 212 | } 213 | 214 | *out_lb = *i; 215 | *out_ub = j; 216 | *out_fg = A_PREPROC_FG; 217 | *out_bg = A_PREPROC_BG; 218 | 219 | return 0; 220 | } 221 | 222 | static int 223 | hl_comment(struct buf const *buf, 224 | size_t *i, 225 | size_t *out_lb, 226 | size_t *out_ub, 227 | uint8_t *out_fg, 228 | uint8_t *out_bg) 229 | { 230 | size_t j = *i + 2; 231 | if (buf_get_wch(buf, *i + 1) == L'/') 232 | { 233 | while (j < buf->size && buf_get_wch(buf, j) != L'\n') 234 | ++j; 235 | } 236 | else 237 | { 238 | while (j < buf->size) 239 | { 240 | wchar_t cmp[3]; 241 | if (!wcscmp(buf_get_wstr(buf, cmp, j, 3), L"*/")) 242 | break; 243 | ++j; 244 | } 245 | } 246 | 247 | *out_lb = *i; 248 | *out_ub = j + 2 * (buf_get_wch(buf, j) == '*'); 249 | *out_fg = A_COMMENT_FG; 250 | *out_bg = A_COMMENT_BG; 251 | 252 | return 0; 253 | } 254 | 255 | static int 256 | hl_string(struct buf const *buf, 257 | size_t *i, 258 | size_t *out_lb, 259 | size_t *out_ub, 260 | uint8_t *out_fg, 261 | uint8_t *out_bg) 262 | { 263 | size_t j = *i; 264 | while (j < buf->size) 265 | { 266 | ++j; 267 | if (buf_get_wch(buf, j) == L'\\') 268 | { 269 | ++j; 270 | continue; 271 | } 272 | else if (wcschr(L"\"\n", buf_get_wch(buf, j))) 273 | break; 274 | } 275 | 276 | *out_lb = *i; 277 | *out_ub = j + (j < buf->size); 278 | *out_fg = A_STRING_FG; 279 | *out_bg = A_STRING_BG; 280 | 281 | return 0; 282 | } 283 | 284 | static int 285 | hl_char(struct buf const *buf, 286 | size_t *i, 287 | size_t *out_lb, 288 | size_t *out_ub, 289 | uint8_t *out_fg, 290 | uint8_t *out_bg) 291 | { 292 | size_t j = *i + 1; 293 | if (j < buf->size && buf_get_wch(buf, j) == L'\\') 294 | j += 2; 295 | else 296 | ++j; 297 | 298 | if (j < buf->size && buf_get_wch(buf, j) == L'\'') 299 | { 300 | *out_lb = *i; 301 | *out_ub = j + 1; 302 | *out_fg = A_STRING_FG; 303 | *out_bg = A_STRING_BG; 304 | 305 | return 0; 306 | } 307 | 308 | return 1; 309 | } 310 | 311 | static int 312 | hl_word(struct buf const *buf, 313 | size_t *i, 314 | size_t *out_lb, 315 | size_t *out_ub, 316 | uint8_t *out_fg, 317 | uint8_t *out_bg) 318 | { 319 | enum word_type wt = WT_MACRO; 320 | 321 | size_t j = *i; 322 | while (j < buf->size) 323 | { 324 | if (buf_get_wch(buf, j) != L'_' 325 | && !iswalnum(buf_get_wch(buf, j))) 326 | { 327 | break; 328 | } 329 | 330 | if (iswlower(buf_get_wch(buf, j))) 331 | wt = WT_BASIC; 332 | 333 | ++j; 334 | } 335 | 336 | for (size_t reg = 0; reg < ARRAY_SIZE(regs); ++reg) 337 | { 338 | size_t len = j - *i + 1; 339 | if (len > 64) 340 | break; 341 | 342 | wchar_t cmp[64]; 343 | buf_get_wstr(buf, cmp, *i, len); 344 | 345 | if (!wcscmp(regs[reg], cmp)) 346 | { 347 | wt = WT_REG; 348 | break; 349 | } 350 | } 351 | 352 | *out_lb = *i; 353 | *out_ub = j; 354 | 355 | switch (wt) 356 | { 357 | case WT_MACRO: 358 | *out_fg = A_MACRO_FG; 359 | *out_bg = A_MACRO_BG; 360 | break; 361 | case WT_REG: 362 | *out_fg = A_REG_FG; 363 | *out_bg = A_REG_BG; 364 | break; 365 | case WT_BASIC: 366 | *i = j - 1; 367 | return 1; 368 | } 369 | 370 | return 0; 371 | } 372 | 373 | static int 374 | hl_special(struct buf const *buf, 375 | size_t *i, 376 | size_t *out_lb, 377 | size_t *out_ub, 378 | uint8_t *out_fg, 379 | uint8_t *out_bg) 380 | { 381 | size_t j = *i + 1; 382 | while (j < buf->size && wcschr(SPECIAL, buf_get_wch(buf, j))) 383 | ++j; 384 | 385 | *out_lb = *i; 386 | *out_ub = j; 387 | *out_fg = A_SPECIAL_FG; 388 | *out_bg = A_SPECIAL_BG; 389 | 390 | return 0; 391 | } 392 | -------------------------------------------------------------------------------- /src/keybd.c: -------------------------------------------------------------------------------- 1 | #include "keybd.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "conf.h" 9 | #include "util.h" 10 | 11 | struct bind 12 | { 13 | int const *key_seq; 14 | size_t len; 15 | void (*fn)(void); 16 | }; 17 | 18 | VEC_DEF_PROTO_STATIC(struct bind, bind) 19 | 20 | static int cmp_binds(void const *a, void const *b); 21 | 22 | static struct vec_bind binds; 23 | static int cur_bind[KEYBD_MAX_BIND_LEN], cur_mac[KEYBD_MAX_MAC_LEN]; 24 | static size_t cur_bind_len = 0, cur_mac_len = 0, cur_mac_exec = 0; 25 | static bool rec_mac = false, exec_mac = false; 26 | 27 | void 28 | keybd_init(void) 29 | { 30 | binds = vec_bind_create(); 31 | } 32 | 33 | void 34 | keybd_quit(void) 35 | { 36 | cur_bind_len = 0; 37 | vec_bind_destroy(&binds); 38 | } 39 | 40 | void 41 | keybd_bind(int const *key_seq, void (*fn)(void)) 42 | { 43 | size_t len = 0; 44 | while (key_seq[len] != -1) 45 | ++len; 46 | 47 | for (size_t i = 0; i < binds.size; ++i) 48 | { 49 | if (binds.data[i].len == len 50 | && !memcmp(key_seq, binds.data[i].key_seq, sizeof(int) * len)) 51 | { 52 | binds.data[i].fn = fn; 53 | return; 54 | } 55 | } 56 | 57 | struct bind new = 58 | { 59 | .key_seq = key_seq, 60 | .len = len, 61 | .fn = fn, 62 | }; 63 | 64 | vec_bind_add(&binds, &new); 65 | } 66 | 67 | void 68 | keybd_organize(void) 69 | { 70 | qsort(binds.data, binds.size, sizeof(struct bind), cmp_binds); 71 | } 72 | 73 | void 74 | keybd_rec_mac_begin(void) 75 | { 76 | if (!exec_mac) 77 | { 78 | rec_mac = true; 79 | cur_mac_len = 0; 80 | } 81 | } 82 | 83 | bool 84 | keybd_is_rec_mac(void) 85 | { 86 | return rec_mac; 87 | } 88 | 89 | void 90 | keybd_rec_mac_end(void) 91 | { 92 | if (!exec_mac) 93 | rec_mac = false; 94 | } 95 | 96 | void 97 | keybd_exec_mac(void) 98 | { 99 | if (!rec_mac && cur_mac_len) 100 | { 101 | cur_mac_exec = 0; 102 | exec_mac = true; 103 | } 104 | } 105 | 106 | bool 107 | keybd_is_exec_mac(void) 108 | { 109 | return exec_mac; 110 | } 111 | 112 | wint_t 113 | keybd_await_key_nb(void) 114 | { 115 | wint_t k; 116 | if (exec_mac) 117 | { 118 | // ignore the last key of the macro, as it is supposedly the one 119 | // that invoked the macro execution, so not ignoring it causes 120 | // infinite macro invocation. 121 | // `cur_bind_len` is reset for safety, in case the bind buffer 122 | // still contains part of the macro invocation bind. 123 | if (cur_mac_exec < cur_mac_len - 1) 124 | k = cur_mac[cur_mac_exec++]; 125 | else 126 | { 127 | k = getwchar(); 128 | exec_mac = false; 129 | cur_bind_len = 0; 130 | } 131 | } 132 | else 133 | k = getwchar(); 134 | 135 | if (rec_mac) 136 | { 137 | if (cur_mac_len < KEYBD_MAX_MAC_LEN) 138 | cur_mac[cur_mac_len++] = k; 139 | } 140 | 141 | return k; 142 | } 143 | 144 | wint_t 145 | keybd_await_key(void) 146 | { 147 | wint_t k = keybd_await_key_nb(); 148 | 149 | if (cur_bind_len < KEYBD_MAX_BIND_LEN) 150 | cur_bind[cur_bind_len++] = k; 151 | else 152 | cur_bind_len = 0; 153 | 154 | ssize_t low = 0, high = binds.size - 1, mid; 155 | while (low <= high) 156 | { 157 | mid = (low + high) / 2; 158 | 159 | struct bind cur_bind_b = 160 | { 161 | .key_seq = cur_bind, 162 | .len = cur_bind_len, 163 | }; 164 | 165 | int cmp = cmp_binds(&cur_bind_b, &binds.data[mid]); 166 | switch (cmp) 167 | { 168 | case -1: 169 | high = mid - 1; 170 | break; 171 | case 0: 172 | goto found; 173 | case 1: 174 | low = mid + 1; 175 | break; 176 | } 177 | } 178 | 179 | mid = -1; 180 | found: 181 | if (mid != -1) 182 | { 183 | struct bind const *b = &binds.data[mid]; 184 | 185 | if (b->len == cur_bind_len) 186 | { 187 | b->fn(); 188 | cur_bind_len = 0; 189 | } 190 | 191 | return KEYBD_IGNORE; 192 | } 193 | 194 | cur_bind_len = 0; 195 | return k; 196 | } 197 | 198 | void 199 | keybd_key_dpy(wchar_t *out, int const *kbuf, size_t nk) 200 | { 201 | *out = 0; 202 | 203 | for (size_t i = 0; i < nk; ++i) 204 | { 205 | wchar_t const *spec_k_name; 206 | switch (kbuf[i]) 207 | { 208 | case K_BACKSPC: 209 | spec_k_name = L"BACKSPC"; 210 | break; 211 | case K_ESC: 212 | spec_k_name = L"ESC"; 213 | break; 214 | case K_RET: 215 | spec_k_name = L"RET"; 216 | break; 217 | case K_SPC: 218 | spec_k_name = L"SPC"; 219 | break; 220 | case K_TAB: 221 | spec_k_name = L"TAB"; 222 | break; 223 | default: 224 | spec_k_name = NULL; 225 | break; 226 | } 227 | 228 | if (spec_k_name) 229 | wcscat(out, spec_k_name); 230 | else if (kbuf[i] <= 26) 231 | { 232 | wchar_t new[] = {L'C', L'-', L'a' + kbuf[i] - 1, 0}; 233 | wcscat(out, new); 234 | } 235 | else 236 | { 237 | wchar_t new[] = {kbuf[i], 0}; 238 | wcscat(out, new); 239 | } 240 | 241 | if (i < nk - 1) 242 | wcscat(out, L" "); 243 | } 244 | } 245 | 246 | int const * 247 | keybd_cur_bind(size_t *out_len) 248 | { 249 | if (out_len) 250 | *out_len = cur_bind_len; 251 | 252 | return cur_bind_len ? cur_bind : NULL; 253 | } 254 | 255 | int const * 256 | keybd_cur_mac(size_t *out_len) 257 | { 258 | if (out_len) 259 | *out_len = cur_mac_len; 260 | 261 | return cur_mac_len ? cur_mac : NULL; 262 | } 263 | 264 | VEC_DEF_IMPL_STATIC(struct bind, bind) 265 | 266 | static int 267 | cmp_binds(void const *a, void const *b) 268 | { 269 | struct bind const *bind_a = a, *bind_b = b; 270 | 271 | for (size_t i = 0; i < bind_a->len && i < bind_b->len; ++i) 272 | { 273 | if (bind_a->key_seq[i] > bind_b->key_seq[i]) 274 | return 1; 275 | else if (bind_a->key_seq[i] < bind_b->key_seq[i]) 276 | return -1; 277 | } 278 | 279 | return 0; 280 | } 281 | -------------------------------------------------------------------------------- /src/label.c: -------------------------------------------------------------------------------- 1 | #include "label.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "conf.h" 7 | #include "draw.h" 8 | #include "editor.h" 9 | #include "keybd.h" 10 | #include "util.h" 11 | 12 | #define BIND_QUIT K_CTL('g') 13 | #define BIND_NAV_DOWN K_CTL('n') 14 | #define BIND_NAV_UP K_CTL('p') 15 | 16 | static void draw_box(wchar_t const *name, wchar_t const *msg, size_t off, struct label_bounds const *bounds); 17 | 18 | int 19 | label_rebound(struct label_bounds *bounds, 20 | unsigned anchor_l, 21 | unsigned anchor_r, 22 | unsigned anchor_t) 23 | { 24 | struct win_size ws = draw_win_size(); 25 | struct label_bounds new = 26 | { 27 | .sr = bounds->sr, 28 | .sc = bounds->sc, 29 | }; 30 | 31 | // left-hand anchoring is preferred. 32 | if (anchor_l + bounds->sc <= ws.sc) 33 | new.pc = anchor_l; 34 | else if (anchor_r <= ws.sc + bounds->sc) 35 | new.pc = MAX((long)anchor_r - (long)bounds->sc + 1, 0); 36 | else 37 | { 38 | // could not get valid position with either horizontal anchor. 39 | return 1; 40 | } 41 | 42 | new.pr = anchor_t; 43 | while (new.pr > 0 && new.pr + bounds->sr > ws.sr) 44 | --new.pr; 45 | 46 | // could not get valid position with vertical anchor. 47 | if (new.pr + bounds->sr > ws.sr) 48 | return 1; 49 | 50 | *bounds = new; 51 | return 0; 52 | } 53 | 54 | int 55 | label_show(wchar_t const *name, 56 | wchar_t const *msg, 57 | struct label_bounds const *bounds) 58 | { 59 | struct win_size ws = draw_win_size(); 60 | 61 | // refuse to display if there isn't enough space to view the label at a 62 | // specific, set size. 63 | if (bounds->pc + bounds->sc > ws.sc || bounds->pr + bounds->sr > ws.sr) 64 | return 1; 65 | 66 | size_t off = 0; 67 | for (;;) 68 | { 69 | draw_box(name, msg, off, bounds); 70 | draw_refresh(); 71 | 72 | wint_t k = keybd_await_key_nb(); 73 | switch (k) 74 | { 75 | case WEOF: 76 | case BIND_QUIT: 77 | return 0; 78 | case BIND_NAV_DOWN: 79 | { 80 | size_t sv_off = off; 81 | 82 | while (msg[off] && msg[off] != L'\n') 83 | ++off; 84 | 85 | if (!msg[off]) 86 | { 87 | off = sv_off; 88 | break; 89 | } 90 | 91 | // only advance if not on last line. 92 | if (!msg[++off]) 93 | { 94 | off = sv_off; 95 | break; 96 | } 97 | 98 | break; 99 | } 100 | case BIND_NAV_UP: 101 | if (off == 0) 102 | break; 103 | --off; 104 | while (off > 0 && msg[off - 1] != L'\n') 105 | --off; 106 | break; 107 | default: 108 | break; 109 | } 110 | } 111 | } 112 | 113 | static void 114 | draw_box(wchar_t const *name, 115 | wchar_t const *msg, 116 | size_t off, 117 | struct label_bounds const *bounds) 118 | { 119 | // write label title. 120 | size_t name_len = wcslen(name); 121 | for (unsigned i = 0; i < bounds->sc; ++i) 122 | { 123 | draw_put_wch(bounds->pr, 124 | bounds->pc + i, 125 | i < name_len ? name[i] : L' '); 126 | } 127 | draw_put_attr(bounds->pr, 128 | bounds->pc, 129 | CONF_A_GHIGH_FG, 130 | CONF_A_GHIGH_BG, 131 | bounds->sc); 132 | 133 | // fill label. 134 | draw_fill(bounds->pr + 1, 135 | bounds->pc, 136 | bounds->sr - 1, 137 | bounds->sc, 138 | L' ', 139 | CONF_A_GHIGH_BG, 140 | CONF_A_GHIGH_FG); 141 | 142 | // write message. 143 | unsigned r = 1, c = 0; 144 | for (size_t i = off; msg[i]; ++i) 145 | { 146 | if (msg[i] == L'\n' || c >= bounds->sc) 147 | { 148 | c = 0; 149 | ++r; 150 | continue; 151 | } 152 | 153 | if (r >= bounds->sr) 154 | break; 155 | 156 | // here, just ignore non-printing chars. 157 | // they are not needed, and nothing is lost if the user doesn't 158 | // know about their existence. 159 | if (msg[i] != L'\t' && !iswprint(msg[i])) 160 | continue; 161 | 162 | switch (msg[i]) 163 | { 164 | case L'\t': 165 | c += CONF_TAB_SIZE - c % CONF_TAB_SIZE; 166 | break; 167 | default: 168 | draw_put_wch(bounds->pr + r, bounds->pc + c, msg[i]); 169 | ++c; 170 | break; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "draw.h" 9 | #include "editor.h" 10 | #include "util.h" 11 | 12 | #define LOG_FILE "medioed.log" 13 | #define LOCALE "C.UTF-8" 14 | 15 | static void usage(char const *name); 16 | 17 | bool flag_c = false, flag_d = false, flag_r = false; 18 | 19 | int 20 | main(int argc, char const *argv[]) 21 | { 22 | int ch; 23 | while ((ch = getopt(argc, (char *const *)argv, "cdhr")) != -1) 24 | { 25 | switch (ch) 26 | { 27 | case 'c': 28 | flag_c = true; 29 | break; 30 | case 'd': 31 | flag_d = true; 32 | break; 33 | case 'h': 34 | usage(argv[0]); 35 | return 0; 36 | case 'r': 37 | flag_r = true; 38 | break; 39 | default: 40 | return 1; 41 | } 42 | } 43 | 44 | if (flag_c && flag_r) 45 | { 46 | fputs("cannot specify -c and -r at the same time!\n", stderr); 47 | return 1; 48 | } 49 | 50 | FILE *log_fp = fopen(flag_d ? LOG_FILE : "/dev/null", "w"); 51 | if (!log_fp) 52 | { 53 | fputs("cannot open logfile: " LOG_FILE "!\n", stderr); 54 | return 1; 55 | } 56 | stderr = log_fp; 57 | 58 | if (!setlocale(LC_ALL, LOCALE)) 59 | { 60 | fputs("failed on setlocale() to " LOCALE "!\n", stderr); 61 | fclose(log_fp); 62 | return 1; 63 | } 64 | 65 | struct termios old; 66 | if (tcgetattr(STDIN_FILENO, &old)) 67 | { 68 | fputs("failed on tcgetattr() for stdin!\n", stderr); 69 | fclose(log_fp); 70 | return 1; 71 | } 72 | 73 | struct termios new = old; 74 | new.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN); 75 | new.c_iflag &= ~(ICRNL | IXON | ISTRIP); 76 | new.c_oflag &= ~OPOST; 77 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new)) 78 | { 79 | fputs("failed on tcsetattr() making stdin raw!\n", stderr); 80 | fclose(log_fp); 81 | return 1; 82 | } 83 | 84 | setvbuf(stdin, NULL, _IONBF, 0); 85 | 86 | draw_init(); 87 | 88 | if (editor_init(argc, argv)) 89 | { 90 | fputs("failed on editor_init()!\n", stderr); 91 | fclose(log_fp); 92 | return 1; 93 | } 94 | editor_main_loop(); 95 | editor_quit(); 96 | 97 | draw_quit(); 98 | 99 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old)) 100 | { 101 | fputs("failed on tcsetattr() restoring old stdin!\n", stderr); 102 | fclose(log_fp); 103 | return 1; 104 | } 105 | 106 | fclose(log_fp); 107 | 108 | return 0; 109 | } 110 | 111 | static void 112 | usage(char const *name) 113 | { 114 | printf("usage:\n" 115 | "\t%s [options] file ...\n" 116 | "\t%s [options]\n" 117 | "options:\n" 118 | "\t-c create opened file if it doesn't exist\n" 119 | "\t-d try to write debug output to a logfile\n" 120 | "\t-h display this menu\n" 121 | "\t-r existing files will be opened read-only\n", name, name); 122 | } 123 | -------------------------------------------------------------------------------- /src/mode.c: -------------------------------------------------------------------------------- 1 | #include "mode.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "conf.h" 9 | 10 | static ssize_t cur_mode = -1; 11 | 12 | struct mode const * 13 | mode_get(void) 14 | { 15 | return cur_mode == -1 ? NULL : &conf_lmtab[cur_mode]; 16 | } 17 | 18 | void 19 | mode_set(char const *name, struct frame *f) 20 | { 21 | struct mode const *old_mode = mode_get(); 22 | 23 | if (!name) 24 | { 25 | if (old_mode) 26 | old_mode->quit(); 27 | cur_mode = -1; 28 | return; 29 | } 30 | 31 | for (size_t i = 0; i < conf_lmtab_size; ++i) 32 | { 33 | if (!strcmp(name, conf_lmtab[i].name)) 34 | { 35 | if (old_mode) 36 | old_mode->quit(); 37 | 38 | cur_mode = i; 39 | conf_lmtab[i].init(f); 40 | 41 | return; 42 | } 43 | } 44 | 45 | if (old_mode) 46 | old_mode->quit(); 47 | cur_mode = -1; 48 | } 49 | 50 | void 51 | mode_update(void) 52 | { 53 | if (cur_mode != -1) 54 | conf_lmtab[cur_mode].update(); 55 | } 56 | 57 | void 58 | mode_key_update(wint_t k) 59 | { 60 | if (cur_mode != -1) 61 | conf_lmtab[cur_mode].keypress(k); 62 | } 63 | -------------------------------------------------------------------------------- /src/mode/mode_clike.c: -------------------------------------------------------------------------------- 1 | #include "mode/mode_clike.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "buf.h" 7 | #include "conf.h" 8 | #include "keybd.h" 9 | #include "mode/mutil.h" 10 | 11 | static void bind_new_line(void); 12 | static void bind_tab_align(void); 13 | static void expand_pair(unsigned ntab); 14 | 15 | static struct frame *mf; 16 | 17 | static int clike_bind_tab_align[] = {K_CTL('w'), K_CTL('a'), -1}; 18 | 19 | void 20 | mode_clike_init(struct frame *f) 21 | { 22 | mf = f; 23 | mu_init(f); 24 | 25 | mu_set_base(); 26 | mu_set_pairing(PF_PAREN | PF_BRACKET | PF_BRACE | PF_SQUOTE | PF_DQUOTE); 27 | 28 | keybd_bind(conf_bind_new_line, bind_new_line); 29 | keybd_bind(clike_bind_tab_align, bind_tab_align); 30 | 31 | keybd_organize(); 32 | } 33 | 34 | void 35 | mode_clike_quit(void) 36 | { 37 | } 38 | 39 | void 40 | mode_clike_update(void) 41 | { 42 | } 43 | 44 | void 45 | mode_clike_keypress(wint_t k) 46 | { 47 | } 48 | 49 | static void 50 | bind_new_line(void) 51 | { 52 | if (!(mf->buf->flags & BF_WRITABLE)) 53 | return; 54 | 55 | size_t ln = mf->csr; 56 | while (ln > 0 && buf_get_wch(mf->buf, ln - 1) != L'\n') 57 | --ln; 58 | 59 | unsigned ntab = 0; 60 | while (ln + ntab < mf->buf->size 61 | && buf_get_wch(mf->buf, ln + ntab) == L'\t') 62 | { 63 | ++ntab; 64 | } 65 | 66 | unsigned nspace = 0; 67 | while (ln + ntab + nspace < mf->buf->size 68 | && buf_get_wch(mf->buf, ln + ntab + nspace) == L' ') 69 | { 70 | ++nspace; 71 | } 72 | 73 | if (mf->csr > 0) 74 | { 75 | wchar_t cmp_buf[3]; 76 | buf_get_wstr(mf->buf, cmp_buf, mf->csr - 1, 3); 77 | 78 | if (!wcscmp(L"()", cmp_buf) 79 | || !wcscmp(L"[]", cmp_buf) 80 | || !wcscmp(L"{}", cmp_buf)) 81 | { 82 | expand_pair(ntab); 83 | return; 84 | } 85 | } 86 | 87 | buf_write_wch(mf->buf, mf->csr, L'\n'); 88 | for (unsigned i = 0; i < ntab; ++i) 89 | buf_write_wch(mf->buf, mf->csr + 1 + i, L'\t'); 90 | for (unsigned i = 0; i < nspace; ++i) 91 | buf_write_wch(mf->buf, mf->csr + 1 + ntab + i, L' '); 92 | 93 | ++mf->csr; 94 | mf->csr_want_col = ntab + nspace; 95 | frame_mv_csr_rel(mf, 0, ntab + nspace, false); 96 | } 97 | 98 | static void 99 | bind_tab_align(void) 100 | { 101 | if (!(mf->buf->flags & BF_WRITABLE)) 102 | return; 103 | 104 | size_t ln = mf->csr; 105 | while (ln > 0 && buf_get_wch(mf->buf, ln - 1) != L'\n') 106 | --ln; 107 | 108 | unsigned ntab = 0; 109 | while (ln + ntab < mf->buf->size 110 | && buf_get_wch(mf->buf, ln + ntab) == L'\t') 111 | { 112 | ++ntab; 113 | } 114 | 115 | unsigned nspace = 0; 116 | while (ln + ntab + nspace < mf->buf->size 117 | && buf_get_wch(mf->buf, ln + ntab + nspace) == L' ') 118 | { 119 | ++nspace; 120 | } 121 | 122 | buf_erase(mf->buf, ln + ntab, ln + ntab + nspace); 123 | if (mf->csr >= ln + ntab + nspace) 124 | { 125 | mf->csr -= nspace; 126 | mf->csr_want_col -= nspace; 127 | } 128 | else if (mf->csr > ln + ntab) 129 | { 130 | mf->csr = ln + ntab; 131 | mf->csr_want_col = ln + ntab; 132 | } 133 | } 134 | 135 | static void 136 | expand_pair(unsigned ntab) 137 | { 138 | size_t write_pos = mf->csr; 139 | 140 | buf_write_wch(mf->buf, write_pos++, L'\n'); 141 | for (unsigned i = 0; i < ntab + 1; ++i) 142 | buf_write_wch(mf->buf, write_pos++, L'\t'); 143 | 144 | buf_write_wch(mf->buf, write_pos++, L'\n'); 145 | for (unsigned i = 0; i < ntab; ++i) 146 | buf_write_wch(mf->buf, write_pos++, L'\t'); 147 | 148 | mf->csr += 2; 149 | frame_mv_csr_rel(mf, 0, ntab + 1, false); 150 | } 151 | -------------------------------------------------------------------------------- /src/mode/mode_html.c: -------------------------------------------------------------------------------- 1 | #include "mode/mode_html.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "buf.h" 10 | #include "conf.h" 11 | #include "editor.h" 12 | #include "keybd.h" 13 | #include "mode/mutil.h" 14 | #include "prompt.h" 15 | 16 | // HTML code typically gets significantly more indented than other computer 17 | // languages, so instead of indenting with tabs (which would be ideal), a 18 | // configurable number of spaces is used to allow a lesser indent than normal. 19 | // also, this slightly better mirrors emacs behavior. 20 | #define INDENT_SIZE 2 21 | 22 | static void bind_indent(void); 23 | static void bind_new_line(void); 24 | static void bind_mk_tag(void); 25 | 26 | static struct frame *mf; 27 | 28 | static int html_bind_indent[] = {K_TAB, -1}; 29 | static int html_bind_new_line[] = {K_RET, -1}; 30 | static int html_bind_mk_tag[] = {K_CTL('w'), K_CTL('t'), -1}; 31 | 32 | void 33 | mode_html_init(struct frame *f) 34 | { 35 | mf = f; 36 | mu_init(f); 37 | 38 | mu_set_base(); 39 | mu_set_pairing(PF_ANGLE | PF_DQUOTE); 40 | 41 | keybd_bind(html_bind_indent, bind_indent); 42 | keybd_bind(conf_bind_new_line, bind_new_line); 43 | keybd_bind(html_bind_mk_tag, bind_mk_tag); 44 | 45 | keybd_organize(); 46 | } 47 | 48 | void 49 | mode_html_quit(void) 50 | { 51 | } 52 | 53 | void 54 | mode_html_update(void) 55 | { 56 | } 57 | 58 | void 59 | mode_html_keypress(wint_t k) 60 | { 61 | } 62 | 63 | static void 64 | bind_indent(void) 65 | { 66 | if (!(mf->buf->flags & BF_WRITABLE)) 67 | return; 68 | 69 | // TODO: implement. 70 | } 71 | 72 | static void 73 | bind_new_line(void) 74 | { 75 | if (!(mf->buf->flags & BF_WRITABLE)) 76 | return; 77 | 78 | // TODO: implement. 79 | } 80 | 81 | static void 82 | bind_mk_tag(void) 83 | { 84 | if (!(mf->buf->flags & BF_WRITABLE)) 85 | return; 86 | 87 | ask_again:; 88 | wchar_t *tag = prompt_ask(L"make tag: ", NULL); 89 | editor_redraw(); 90 | if (!tag) 91 | return; 92 | 93 | if (!*tag) 94 | { 95 | free(tag); 96 | prompt_show(L"cannot insert blank tag!"); 97 | editor_redraw(); 98 | goto ask_again; 99 | } 100 | 101 | size_t tag_len = wcslen(tag); 102 | 103 | for (size_t i = 0; i < tag_len; ++i) 104 | { 105 | if (wcschr(L"<>", tag[i])) 106 | { 107 | free(tag); 108 | prompt_show(L"tag contains invalid characters!"); 109 | editor_redraw(); 110 | goto ask_again; 111 | } 112 | } 113 | 114 | size_t cls_tag_len = 0; 115 | while (iswalnum(tag[cls_tag_len])) 116 | ++cls_tag_len; 117 | 118 | if (!cls_tag_len) 119 | { 120 | free(tag); 121 | prompt_show(L"cannot insert unclosable tag!"); 122 | editor_redraw(); 123 | goto ask_again; 124 | } 125 | 126 | wchar_t *cls_tag = malloc(sizeof(wchar_t) * (cls_tag_len + 1)); 127 | memcpy(cls_tag, tag, sizeof(wchar_t) * cls_tag_len); 128 | cls_tag[cls_tag_len] = 0; 129 | 130 | size_t open_off = mf->csr; 131 | buf_write_wch(mf->buf, open_off, L'<'); 132 | ++open_off; 133 | buf_write_wstr(mf->buf, open_off, tag); 134 | open_off += tag_len; 135 | buf_write_wch(mf->buf, open_off, L'>'); 136 | ++open_off; 137 | 138 | size_t cls_off = open_off; 139 | buf_write_wstr(mf->buf, cls_off, L"buf, cls_off, cls_tag); 142 | cls_off += cls_tag_len; 143 | buf_write_wch(mf->buf, cls_off, L'>'); 144 | ++cls_off; 145 | 146 | frame_mv_csr_rel(mf, 0, open_off - mf->csr, false); 147 | } 148 | -------------------------------------------------------------------------------- /src/mode/mode_null.c: -------------------------------------------------------------------------------- 1 | #include "mode/mode_null.h" 2 | 3 | void 4 | mode_null_init(struct frame *f) 5 | { 6 | } 7 | 8 | void 9 | mode_null_quit(void) 10 | { 11 | } 12 | 13 | void 14 | mode_null_update(void) 15 | { 16 | } 17 | 18 | void 19 | mode_null_keypress(wint_t k) 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/mode/mode_s.c: -------------------------------------------------------------------------------- 1 | #include "mode/mode_s.h" 2 | 3 | #include "mode/mutil.h" 4 | 5 | static struct frame *mf; 6 | 7 | void 8 | mode_s_init(struct frame *f) 9 | { 10 | mf = f; 11 | mu_init(f); 12 | 13 | mu_set_base(); 14 | mu_set_pairing(PF_BRACKET | PF_SQUOTE | PF_DQUOTE); 15 | } 16 | 17 | void 18 | mode_s_quit(void) 19 | { 20 | } 21 | 22 | void 23 | mode_s_update(void) 24 | { 25 | } 26 | 27 | void 28 | mode_s_keypress(wint_t k) 29 | { 30 | } 31 | -------------------------------------------------------------------------------- /src/mode/mutil.c: -------------------------------------------------------------------------------- 1 | #include "mode/mutil.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "buf.h" 11 | #include "conf.h" 12 | #include "keybd.h" 13 | 14 | static void bind_pair_open_pn(void); 15 | static void bind_pair_open_bk(void); 16 | static void bind_pair_open_bc(void); 17 | static void bind_pair_open_ag(void); 18 | static void bind_pair_close_pn(void); 19 | static void bind_pair_close_bk(void); 20 | static void bind_pair_close_bc(void); 21 | static void bind_pair_close_ag(void); 22 | static void bind_pair_sq(void); 23 | static void bind_pair_dq(void); 24 | static void bind_del_back_ch(void); 25 | 26 | static struct frame *mf; 27 | static bool opt_base = false, opt_pairing = false; 28 | static unsigned long pair_flags = 0; 29 | 30 | static int mu_bind_pair_open_pn[] = {'(', -1}; 31 | static int mu_bind_pair_open_bk[] = {'[', -1}; 32 | static int mu_bind_pair_open_bc[] = {'{', -1}; 33 | static int mu_bind_pair_open_ag[] = {'<', -1}; 34 | static int mu_bind_pair_close_pn[] = {')', -1}; 35 | static int mu_bind_pair_close_bk[] = {']', -1}; 36 | static int mu_bind_pair_close_bc[] = {'}', -1}; 37 | static int mu_bind_pair_close_ag[] = {'>', -1}; 38 | static int mu_bind_pair_sq[] = {'\'', -1}; 39 | static int mu_bind_pair_dq[] = {'"', -1}; 40 | 41 | void 42 | mu_init(struct frame *f) 43 | { 44 | mf = f; 45 | opt_base = false; 46 | opt_pairing = false; 47 | } 48 | 49 | void 50 | mu_set_base(void) 51 | { 52 | if (opt_base) 53 | return; 54 | 55 | opt_base = true; 56 | 57 | keybd_bind(conf_bind_del_back_ch, bind_del_back_ch); 58 | } 59 | 60 | void 61 | mu_set_pairing(unsigned long flags) 62 | { 63 | if (opt_pairing) 64 | return; 65 | 66 | opt_pairing = true; 67 | pair_flags = flags; 68 | 69 | if (flags & PF_PAREN) 70 | { 71 | keybd_bind(mu_bind_pair_open_pn, bind_pair_open_pn); 72 | keybd_bind(mu_bind_pair_close_pn, bind_pair_close_pn); 73 | } 74 | 75 | if (flags & PF_BRACKET) 76 | { 77 | keybd_bind(mu_bind_pair_open_bk, bind_pair_open_bk); 78 | keybd_bind(mu_bind_pair_close_bk, bind_pair_close_bk); 79 | } 80 | 81 | if (flags & PF_BRACE) 82 | { 83 | keybd_bind(mu_bind_pair_open_bc, bind_pair_open_bc); 84 | keybd_bind(mu_bind_pair_close_bc, bind_pair_close_bc); 85 | } 86 | 87 | if (flags & PF_ANGLE) 88 | { 89 | keybd_bind(mu_bind_pair_open_ag, bind_pair_open_ag); 90 | keybd_bind(mu_bind_pair_close_ag, bind_pair_close_ag); 91 | } 92 | 93 | if (flags & PF_SQUOTE) 94 | keybd_bind(mu_bind_pair_sq, bind_pair_sq); 95 | 96 | if (flags & PF_DQUOTE) 97 | keybd_bind(mu_bind_pair_dq, bind_pair_dq); 98 | 99 | keybd_organize(); 100 | } 101 | 102 | static void 103 | bind_pair_open_pn(void) 104 | { 105 | buf_write_wstr(mf->buf, mf->csr, L"()"); 106 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 107 | } 108 | 109 | static void 110 | bind_pair_open_bk(void) 111 | { 112 | buf_write_wstr(mf->buf, mf->csr, L"[]"); 113 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 114 | } 115 | 116 | static void 117 | bind_pair_open_bc(void) 118 | { 119 | buf_write_wstr(mf->buf, mf->csr, L"{}"); 120 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 121 | } 122 | 123 | static void 124 | bind_pair_open_ag(void) 125 | { 126 | buf_write_wstr(mf->buf, mf->csr, L"<>"); 127 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 128 | } 129 | 130 | static void 131 | bind_pair_close_pn(void) 132 | { 133 | if (mf->csr >= mf->buf->size || buf_get_wch(mf->buf, mf->csr) != L')') 134 | buf_write_wch(mf->buf, mf->csr, L')'); 135 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 136 | } 137 | 138 | static void 139 | bind_pair_close_bk(void) 140 | { 141 | if (mf->csr >= mf->buf->size || buf_get_wch(mf->buf, mf->csr) != L']') 142 | buf_write_wch(mf->buf, mf->csr, L']'); 143 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 144 | } 145 | 146 | static void 147 | bind_pair_close_bc(void) 148 | { 149 | if (mf->csr >= mf->buf->size || buf_get_wch(mf->buf, mf->csr) != L'}') 150 | buf_write_wch(mf->buf, mf->csr, L'}'); 151 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 152 | } 153 | 154 | static void 155 | bind_pair_close_ag(void) 156 | { 157 | if (mf->csr >= mf->buf->size || buf_get_wch(mf->buf, mf->csr) != L'>') 158 | buf_write_wch(mf->buf, mf->csr, L'>'); 159 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 160 | } 161 | 162 | static void 163 | bind_pair_sq(void) 164 | { 165 | buf_write_wstr(mf->buf, mf->csr, L"''"); 166 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 167 | } 168 | 169 | static void 170 | bind_pair_dq(void) 171 | { 172 | buf_write_wstr(mf->buf, mf->csr, L"\"\""); 173 | frame_mv_csr_rel(mf, 0, !!(mf->buf->flags & BF_WRITABLE), true); 174 | } 175 | 176 | static void 177 | bind_del_back_ch(void) 178 | { 179 | if (mf->csr > 0 && mf->buf->flags & BF_WRITABLE) 180 | { 181 | frame_mv_csr_rel(mf, 0, -1, true); 182 | 183 | size_t nch = 1; 184 | 185 | if (opt_pairing 186 | && mf->buf->size > 1 187 | && mf->csr < mf->buf->size - 1) 188 | { 189 | wchar_t cmp_buf[3]; 190 | buf_get_wstr(mf->buf, cmp_buf, mf->csr, 3); 191 | 192 | if (!wcscmp(cmp_buf, L"()") && (pair_flags & PF_PAREN) 193 | || !wcscmp(cmp_buf, L"[]") && (pair_flags & PF_BRACKET) 194 | || !wcscmp(cmp_buf, L"{}") && (pair_flags & PF_BRACE) 195 | || !wcscmp(cmp_buf, L"<>") && (pair_flags & PF_ANGLE) 196 | || !wcscmp(cmp_buf, L"\"\"") && (pair_flags & PF_DQUOTE) 197 | || !wcscmp(cmp_buf, L"''") && (pair_flags & PF_SQUOTE)) 198 | { 199 | nch = 2; 200 | } 201 | } 202 | 203 | buf_erase(mf->buf, mf->csr, mf->csr + nch); 204 | frame_comp_boundary(mf); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/prompt.c: -------------------------------------------------------------------------------- 1 | #include "prompt.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "conf.h" 12 | #include "draw.h" 13 | #include "keybd.h" 14 | #include "util.h" 15 | 16 | #define BIND_RET K_RET 17 | #define BIND_QUIT K_CTL('g') 18 | #define BIND_NAV_FWD K_CTL('f') 19 | #define BIND_NAV_BACK K_CTL('b') 20 | #define BIND_DEL K_BACKSPC 21 | #define BIND_COMPLETE K_TAB 22 | 23 | static void draw_box(wchar_t const *text); 24 | 25 | void 26 | prompt_show(wchar_t const *msg) 27 | { 28 | draw_box(msg); 29 | draw_refresh(); 30 | 31 | for (;;) 32 | { 33 | wint_t k = keybd_await_key_nb(); 34 | if (k == WEOF || k == BIND_QUIT) 35 | break; 36 | } 37 | } 38 | 39 | wchar_t * 40 | prompt_ask(wchar_t const *msg, void (*comp)(wchar_t **, size_t *, size_t *)) 41 | { 42 | draw_box(msg); 43 | 44 | struct win_size ws = draw_win_size(); 45 | 46 | // determine where the response should be rendered. 47 | unsigned render_row = ws.sr - 1, render_col = 0; 48 | for (wchar_t const *c = msg; *c; ++c) 49 | { 50 | if (*c == L'\n' || ++render_col > ws.sc) 51 | render_col = 0; 52 | } 53 | 54 | // a faux cursor is drawn before entering the keyboard loop, so that it 55 | // doesn't look like it spontaneously appears upon a keypress. 56 | draw_put_attr(render_row, render_col, CONF_A_GHIGH_FG, CONF_A_GHIGH_BG, 1); 57 | draw_refresh(); 58 | 59 | wchar_t *resp = malloc(sizeof(wchar_t)); 60 | size_t resp_len = 0; 61 | size_t csr = 0, draw_start = 0; 62 | 63 | wint_t k; 64 | while ((k = keybd_await_key_nb()) != BIND_RET) 65 | { 66 | // gather response. 67 | switch (k) 68 | { 69 | case WEOF: 70 | case BIND_QUIT: 71 | free(resp); 72 | return NULL; 73 | case BIND_NAV_FWD: 74 | csr += csr < resp_len; 75 | break; 76 | case BIND_NAV_BACK: 77 | csr -= csr > 0; 78 | break; 79 | case BIND_DEL: 80 | if (csr > 0) 81 | { 82 | memmove(resp + csr - 1, 83 | resp + csr, 84 | sizeof(wchar_t) * (resp_len - csr)); 85 | --resp_len; 86 | --csr; 87 | } 88 | break; 89 | case BIND_COMPLETE: 90 | if (comp) 91 | comp(&resp, &resp_len, &csr); 92 | break; 93 | default: 94 | resp = realloc(resp, sizeof(wchar_t) * (++resp_len + 1)); 95 | memmove(resp + csr + 1, 96 | resp + csr, 97 | sizeof(wchar_t) * (resp_len - csr)); 98 | resp[csr++] = k; 99 | break; 100 | } 101 | 102 | // interactively render response. 103 | if (csr < draw_start) 104 | draw_start = csr; 105 | else if (csr - draw_start >= ws.sc - render_col - 1) 106 | draw_start = csr - ws.sc + render_col + 1; 107 | 108 | draw_fill(ws.sr - 1, 109 | render_col, 1, 110 | ws.sc - render_col, 111 | L' ', 112 | CONF_A_GNORM_FG, 113 | CONF_A_GNORM_BG); 114 | 115 | for (size_t i = 0; i < resp_len - draw_start && i < ws.sc - render_col; ++i) 116 | draw_put_wch(render_row, render_col + i, resp[draw_start + i]); 117 | 118 | draw_put_attr(render_row, 119 | render_col + csr - draw_start, 120 | CONF_A_GHIGH_FG, 121 | CONF_A_GHIGH_BG, 122 | 1); 123 | 124 | draw_refresh(); 125 | } 126 | 127 | resp[resp_len] = 0; 128 | return resp; 129 | } 130 | 131 | int 132 | prompt_yes_no(wchar_t const *msg, bool deflt) 133 | { 134 | size_t full_msg_len = wcslen(msg) + 8; 135 | wchar_t *full_msg = malloc(sizeof(wchar_t) * full_msg_len); 136 | wchar_t const *deflt_mk = deflt ? L"(Y/n)" : L"(y/N)"; 137 | swprintf(full_msg, full_msg_len, L"%ls %ls ", msg, deflt_mk); 138 | 139 | ask_again:; 140 | wchar_t *resp = prompt_ask(full_msg, NULL); 141 | if (!resp) 142 | { 143 | free(full_msg); 144 | return -1; 145 | } 146 | 147 | if (!wcscmp(resp, L"y") || deflt && !*resp) 148 | { 149 | free(resp); 150 | free(full_msg); 151 | return 1; 152 | } 153 | else if (!wcscmp(resp, L"n") || !deflt && !*resp) 154 | { 155 | free(resp); 156 | free(full_msg); 157 | return 0; 158 | } 159 | else 160 | { 161 | free(resp); 162 | prompt_show(L"expected either 'y' or 'n'!"); 163 | goto ask_again; 164 | } 165 | } 166 | 167 | void 168 | prompt_comp_path(wchar_t **resp, size_t *rlen, size_t *csr) 169 | { 170 | if (*csr != *rlen) 171 | return; 172 | 173 | // 16 bytes added to path to avoid buffer overflow when, e.g. a 3 byte 174 | // character is written and `path_bytes` is `PATH_MAX` - 1. 175 | char path[PATH_MAX + 16] = {0}; 176 | 177 | size_t path_bytes = 0, path_len = 0; 178 | while (path_len < *rlen && path_bytes < PATH_MAX) 179 | { 180 | int nbytes = wctomb(&path[path_bytes], (*resp)[path_len]); 181 | if (nbytes == -1) 182 | return; 183 | 184 | ++path_len; 185 | path_bytes += nbytes; 186 | } 187 | 188 | if (path_bytes > PATH_MAX) 189 | return; 190 | 191 | char name[PATH_MAX] = {0}, dir[PATH_MAX] = {0}; 192 | strncpy(dir, path, PATH_MAX); 193 | char *first_slash = strchr(dir, '/'), *last_slash = strrchr(dir, '/'); 194 | 195 | char *first_ch = dir; 196 | while (*first_ch && iswspace(*first_ch)) 197 | ++first_ch; 198 | 199 | if (!last_slash) 200 | { 201 | dir[0] = '.'; 202 | dir[1] = 0; 203 | strncpy(name, path, PATH_MAX); 204 | } 205 | else if (first_ch 206 | && first_ch == first_slash 207 | && first_slash == last_slash) 208 | { 209 | strncpy(name, last_slash + 1, PATH_MAX); 210 | *(last_slash + 1) = 0; 211 | } 212 | else 213 | { 214 | strncpy(name, last_slash + 1, PATH_MAX); 215 | *last_slash = 0; 216 | } 217 | 218 | size_t dir_len = strlen(dir), name_len = strlen(name); 219 | 220 | DIR *dir_p = opendir(dir); 221 | if (!dir_p) 222 | return; 223 | 224 | struct dirent *dir_ent; 225 | while (dir_ent = readdir(dir_p)) 226 | { 227 | if (strncasecmp(name, dir_ent->d_name, name_len) 228 | || dir_ent->d_type != DT_DIR && dir_ent->d_type != DT_REG 229 | || !strcmp(".", dir_ent->d_name) 230 | || !strcmp("..", dir_ent->d_name)) 231 | { 232 | continue; 233 | } 234 | 235 | char new_path[PATH_MAX]; 236 | strcpy(new_path, dir); 237 | 238 | // only add '/' if not root, otherwise you'd get "//...". 239 | if (first_ch && first_ch != last_slash) 240 | strcat(new_path, "/"); 241 | 242 | strcat(new_path, dir_ent->d_name); 243 | if (dir_ent->d_type == DT_DIR) 244 | strcat(new_path, "/"); 245 | size_t new_path_bytes = strlen(new_path); 246 | 247 | // temporarily allocate potentially too much memory, then reduce 248 | // when done. 249 | // this allows one pass to be done when converting to wide chars 250 | // rather than two, saving computation. 251 | *resp = realloc(*resp, sizeof(wchar_t) * (PATH_MAX + 1)); 252 | 253 | // this assumes path name will be valid when read as multibyte 254 | // string in locale. 255 | size_t new_path_len = 0, new_path_bytes_read = 0; 256 | while (new_path_bytes_read < new_path_bytes) 257 | { 258 | wchar_t wch; 259 | size_t nbytes = mbstowcs(&wch, &new_path[new_path_bytes_read], 1); 260 | if (nbytes == (size_t)-1) 261 | { 262 | closedir(dir_p); 263 | return; 264 | } 265 | 266 | (*resp)[new_path_len++] = wch; 267 | new_path_bytes_read += nbytes; 268 | } 269 | 270 | *csr = *rlen = new_path_len; 271 | *resp = realloc(*resp, sizeof(wchar_t) * (new_path_len + 1)); 272 | 273 | closedir(dir_p); 274 | 275 | return; 276 | } 277 | 278 | closedir(dir_p); 279 | } 280 | 281 | static void 282 | draw_box(wchar_t const *text) 283 | { 284 | struct win_size ws = draw_win_size(); 285 | 286 | size_t text_rows = 1, line_width = 0; 287 | for (wchar_t const *c = text; *c; ++c) 288 | { 289 | if (++line_width >= ws.sc || *c == L'\n') 290 | { 291 | line_width = 0; 292 | ++text_rows; 293 | } 294 | } 295 | 296 | size_t box_top = ws.sr - text_rows; 297 | draw_fill(box_top, 298 | 0, 299 | text_rows, 300 | ws.sc, 301 | L' ', 302 | CONF_A_GNORM_FG, 303 | CONF_A_GNORM_BG); 304 | draw_put_wstr(box_top, 0, text); 305 | } 306 | -------------------------------------------------------------------------------- /src/utf8.c: -------------------------------------------------------------------------------- 1 | #include "utf8.h" 2 | 3 | int 4 | uc32scmp(uchar32 const *lhs, uchar32 const *rhs) 5 | { 6 | for (size_t i = 0; lhs[i] || rhs[i]; ++i) 7 | { 8 | if (lhs[i] > rhs[1]) 9 | return 1; 10 | else if (lhs[i] < rhs[i]) 11 | return -1; 12 | } 13 | 14 | return 0; 15 | } 16 | 17 | int 18 | uc32sncmp(uchar32 const *lhs, uchar32 const *rhs, size_t n) 19 | { 20 | for (size_t i = 0; i < n && (lhs[i] || rhs[i]); ++i) 21 | { 22 | if (lhs[i] > rhs[1]) 23 | return 1; 24 | else if (lhs[i] < rhs[i]) 25 | return -1; 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | uchar32 32 | utf8_decode_ch(uint8_t const *bytes) 33 | { 34 | if (!(bytes[0] & 0x80)) 35 | return bytes[0]; 36 | 37 | int nbytes = 0; 38 | for (uint8_t mask = 0x80; mask && bytes[0] & mask; mask >>= 1) 39 | ++nbytes; 40 | 41 | if (nbytes == 1 || nbytes > 4) 42 | return UTF8_BAD_CH; 43 | 44 | // byte 0 needs special copying logic since the top part can't just be 45 | // masked out with a constant value. 46 | uint8_t byte_vals[4] = {0}; 47 | for (int i = 0; i < 7 - nbytes; ++i) 48 | byte_vals[0] |= bytes[0] & 1 << i; 49 | 50 | for (int i = 1; i < nbytes; ++i) 51 | { 52 | if (!(bytes[i] & 0x80) || bytes[i] & 0x40) 53 | return UTF8_BAD_CH; 54 | byte_vals[i] = bytes[i] & ~0x80; 55 | } 56 | 57 | uchar32 ch = 0; 58 | for (int i = 1; i < nbytes; ++i) 59 | ch |= (uint32_t)byte_vals[nbytes - i] << 6 * (i - 1); 60 | ch |= (uint32_t)byte_vals[0] << 6 * (nbytes - 1); 61 | 62 | return ch; 63 | } 64 | 65 | uint8_t * 66 | utf8_encode_ch(uint8_t *out_bytes, uchar32 ch) 67 | { 68 | } 69 | 70 | int 71 | utf8_decode_str(uchar32 *out_str, uint8_t const *bytes) 72 | { 73 | } 74 | 75 | int 76 | utf8_encode_str(uint8_t *out_bytes, uchar32 const *str) 77 | { 78 | } 79 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | VEC_DEF_IMPL(char *, str) 12 | 13 | char const * 14 | file_ext(char const *path) 15 | { 16 | char const *ext = strrchr(path, '.'); 17 | return ext && ext != path ? ext + 1 : "\0"; 18 | } 19 | 20 | int mk_dir_rec(char const *dir) 21 | { 22 | char *wd = strdup(dir); 23 | size_t wd_len = strlen(wd); 24 | if (!wd_len) 25 | { 26 | free(wd); 27 | return 1; 28 | } 29 | 30 | for (size_t i = 0, len = 0; i < wd_len; ++i) 31 | { 32 | if (wd[i] != '/') 33 | { 34 | ++len; 35 | continue; 36 | } 37 | 38 | if (!len) 39 | continue; 40 | 41 | wd[i] = 0; 42 | struct stat s; 43 | if (!stat(wd, &s) && S_ISDIR(s.st_mode)) 44 | { 45 | wd[i] = '/'; 46 | continue; 47 | } 48 | 49 | mode_t pm = umask(0); 50 | int rc = mkdir(wd, 0755); 51 | umask(pm); 52 | 53 | wd[i] = '/'; 54 | 55 | if (rc) 56 | { 57 | free(wd); 58 | return 1; 59 | } 60 | 61 | len = 0; 62 | } 63 | 64 | free(wd); 65 | return 0; 66 | } 67 | 68 | int 69 | mk_file(char const *path) 70 | { 71 | struct stat s; 72 | if (!stat(path, &s)) 73 | return !S_ISREG(s.st_mode); 74 | 75 | char *dir = strdup(path); 76 | if (strrchr(dir, '/')) 77 | *(strrchr(dir, '/') + 1) = 0; 78 | 79 | int rc = mk_dir_rec(dir); 80 | free(dir); 81 | if (rc) 82 | return 1; 83 | 84 | FILE *fp = fopen(path, "w"); 85 | if (!fp) 86 | return 1; 87 | fclose(fp); 88 | 89 | // generally will be caused if the user tried to use `-c` when opening a 90 | // directory, like `$ medioed hello/`. 91 | if (stat(path, &s) || !S_ISREG(s.st_mode)) 92 | return 1; 93 | 94 | return 0; 95 | } 96 | 97 | bool 98 | is_path_same(char const *pa, char const *pb) 99 | { 100 | struct stat sa, sb; 101 | if (stat(pa, &sa) || stat(pb, &sb)) 102 | return false; 103 | return sa.st_dev == sb.st_dev && sa.st_ino == sb.st_ino; 104 | } 105 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $EUID -ne 0 ] 4 | then 5 | echo "uninstall procedure requires root permissions" 6 | exit 1 7 | fi 8 | 9 | rm /usr/bin/medioed 10 | if [ $? -ne 0 ] 11 | then 12 | echo "cannot uninstall program that was never installed" 13 | exit 1 14 | fi 15 | --------------------------------------------------------------------------------