se

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 06a5020778af8f4b4014f8ed872b1da15d590430
parent d10c7baf68af22417aca93aec7fbba6bfe5a41f1
Author: Samdal <samdal@protonmail.com>
Date:   Thu, 20 Jan 2022 18:49:39 +0100

restructured files, file browser, finished multiple windows

Diffstat:
M.clang_complete | 2+-
M.gitignore | 3+--
MMakefile | 62+++++++++++++++++++++++++++++++-------------------------------
Aconfig.def.c | 716+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dconfig.def.h | 199-------------------------------------------------------------------------------
Mconfig.mk | 12++++++------
Ase.c | 1666+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ase.h | 278+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dst.c | 1001-------------------------------------------------------------------------------
Dst.h | 213-------------------------------------------------------------------------------
Dwin.h | 35-----------------------------------
Mx.c | 1174+++++++++++++++++++++++++++++++++----------------------------------------------
Ax.h | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 3310 insertions(+), 2178 deletions(-)

diff --git a/.clang_complete b/.clang_complete @@ -4,4 +4,4 @@ -I/usr/include/glib-2.0 -I/usr/include/libpng16 -I/usr/lib/glib-2.0/include --D_XOPEN_SOURCE +-D_XOPEN_SOURCE=600 diff --git a/.gitignore b/.gitignore @@ -1,5 +1,4 @@ *.o *.out -st se -config.h +config.c diff --git a/Makefile b/Makefile @@ -4,54 +4,54 @@ include config.mk -SRC = st.c x.c +SRC = se.c x.c config.c OBJ = $(SRC:.c=.o) -all: options st +all: options se options: - @echo st build options: - @echo "CFLAGS = $(STCFLAGS)" - @echo "LDFLAGS = $(STLDFLAGS)" + @echo se build options: + @echo "CFLAGS = $(SECFLAGS)" + @echo "LDFLAGS = $(SELDFLAGS)" @echo "CC = $(CC)" -config.h: - cp config.def.h config.h +config.c: + cp config.def.c config.c .c.o: - $(CC) $(STCFLAGS) -c $< + $(CC) $(SECFLAGS) -c $< -st.o: config.h st.h win.h -x.o: config.h st.h win.h +se.o: se.h x.h +x.o: se.h x.h -$(OBJ): config.h config.mk +$(OBJ): config.c config.mk -st: $(OBJ) - $(CC) -o $@ $(OBJ) $(STLDFLAGS) +se: $(OBJ) + $(CC) -o $@ $(OBJ) $(SELDFLAGS) clean: - rm -f st $(OBJ) st-$(VERSION).tar.gz + rm -f se $(OBJ) se-$(VERSION).tar.gz dist: clean - mkdir -p st-$(VERSION) - cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ - config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ - st-$(VERSION) - tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz - rm -rf st-$(VERSION) - -install: st + mkdir -p se-$(VERSION) + cp -R LICENSE Makefile README config.mk\ + config.def.c se.h x.h $(SRC)\ + se-$(VERSION) + tar -cf - se-$(VERSION) | gzip > se-$(VERSION).tar.gz + rm -rf se-$(VERSION) + +install: se mkdir -p $(DESTDIR)$(PREFIX)/bin - cp -f st $(DESTDIR)$(PREFIX)/bin - chmod 755 $(DESTDIR)$(PREFIX)/bin/st - mkdir -p $(DESTDIR)$(MANPREFIX)/man1 - sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 - chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 - tic -sx st.info - @echo Please see the README file regarding the terminfo entry of st. + cp -f se $(DESTDIR)$(PREFIX)/bin + chmod 755 $(DESTDIR)$(PREFIX)/bin/se +# mkdir -p $(DESTDIR)$(MANPREFIX)/man1 +# sed "s/VERSION/$(VERSION)/g" < se.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 +# chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 +# tic -sx se.info +# @echo Please see the README file regarding the terminfo entry of se. uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/st - rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 + rm -f $(DESTDIR)$(PREFIX)/bin/se +# rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 .PHONY: all options clean dist install uninstall diff --git a/config.def.c b/config.def.c @@ -0,0 +1,716 @@ +#include <assert.h> +#include <time.h> + +#include "x.h" + +//////////////////////////////////////// +// apperance +// + +// font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html +char *font = "Iosevka:pixelsize=16:antialias=true:autohint=true"; + +// pixels of border around the window +int border_px = 2; + +// default size of the editor +unsigned int default_cols = 80; +unsigned int default_rows = 24; + +// Kerning / character bounding-box multipliers +float cw_scale = 1.0; +float ch_scale = 1.0; + +int wrap_buffer = 0; + +// spaces per tab (tabs will self align) +unsigned int tabspaces = 8; + +/////////////////////////////////////////// +// colour scheme +// this color scheme is gruvbox +// see colors at: https://github.com/morhetz/gruvbox +// + +enum colour_names { + bg, + bg0_h, + fg, + sel, + line, + red, + dark_green, + green, + teal, + yellow, + orange, + blue, + purple, + aqua, + gray, +}; + +const char * const colors[] = { + [bg] = "#282828", + [bg0_h] = "#1d2021", + [fg] = "#fbf1c7", + [sel] = "#504945", + [line] = "#32302f", + [red] = "#cc251d", + [dark_green] = "#98971a", + [green] = "#b8bb26", + [teal] = "#8ec07c", + [yellow] = "#fabd2f", + [orange] = "#d65d0e", + [blue] = "#458588", + [purple] = "#b16286", + [aqua] = "#83a598", + [gray] = "#a89984", + NULL +}; + +// default colors +Glyph default_attributes = {.fg = fg, .bg = bg}; +unsigned int mouse_fg = fg; +unsigned int mouse_bg = bg; +unsigned int selection_bg = sel; +unsigned int mouse_line_bg = line; + +// Default shape of cursor +// 2: Block ("█") +// 4: Underline ("_") +// 6: Bar ("|") +// 7: Snowman ("☃") +unsigned int cursor_shape = 2; + +// thickness of underline and bar cursors +unsigned int cursor_thickness = 2; + +////////////////////////////////////////// +// Color scheme +// +#define str_color gray +#define comment_color gray +#define type_color teal +#define keyword_color green +#define macro_color yellow +#define operator_color yellow +#define constants_color dark_green +#define number_color gray +#define color_macro(_str) {COLOR_WORD,{_str}, macro_color} +#define color_keyword(_str) {COLOR_WORD,{_str}, keyword_color} +#define color_type(_str) {COLOR_WORD,{_str}, type_color} +#define color_number(_num) \ + {COLOR_WORD_STARTING_WITH_STR, {_num}, number_color}, \ + {COLOR_WORD_ENDING_WITH_STR, {_num".f"},number_color} + +// (order matters) +const struct color_scheme_entry c_color_scheme[] = { + // Coloring type arguments Color + + // strings + {COLOR_AROUND_TO_LINE, {"\"", "\""}, str_color}, + {COLOR_STR, {"''"}, fg}, + {COLOR_AROUND_TO_LINE, {"'", "'"}, str_color}, + {COLOR_INSIDE_TO_LINE, {"#include <", ">"}, str_color}, + {COLOR_INSIDE_TO_LINE, {"#include<", ">"}, str_color}, + // comments + {COLOR_AROUND, {"/*", "*/"}, comment_color}, + {COLOR_AROUND, {"//", "\n"}, comment_color}, + // macros + {COLOR_UPPER_CASE_WORD, {0}, constants_color}, + {COLOR_STR_AFTER_WORD, {"#ifdef"}, constants_color}, + {COLOR_STR_AFTER_WORD, {"#ifndef"}, constants_color}, + {COLOR_STR_AFTER_WORD, {"#define"}, constants_color}, + {COLOR_STR_AFTER_WORD, {"#undef"}, constants_color}, + {COLOR_WORD_STARTING_WITH_STR, {"#"}, macro_color}, + color_macro("sizeof"), + color_macro("alignof"), + color_macro("offsetof"), + color_macro("va_arg"), + color_macro("va_start"), + color_macro("va_end"), + color_macro("va_copy"), + {COLOR_STR_AFTER_WORD, {"defined"}, constants_color}, + color_macro("defined"), + // operators + {COLOR_STR, {"!="}, fg}, + {COLOR_STR, {"!"}, operator_color}, + {COLOR_STR, {"~"}, operator_color}, + {COLOR_STR, {"?"}, operator_color}, + // keywords + {COLOR_WORD_STR, {"...", ")"}, keyword_color}, + {COLOR_WORD_STR, {"struct", "{"},keyword_color}, + {COLOR_WORD_STR, {"union", "{"}, keyword_color}, + {COLOR_WORD_STR, {"enum", "{"}, keyword_color}, + {COLOR_STR_AFTER_WORD, {"struct"}, type_color}, + {COLOR_STR_AFTER_WORD, {"union"}, type_color}, + {COLOR_STR_AFTER_WORD, {"enum"}, type_color}, + {COLOR_STR_AFTER_WORD, {"goto"}, constants_color}, + {COLOR_WORD_INSIDE, {"}", ":"}, constants_color}, + {COLOR_WORD_INSIDE, {"{", ":"}, constants_color}, + {COLOR_WORD_INSIDE, {";", ":"}, constants_color}, + color_keyword("struct"), + color_keyword("enum"), + color_keyword("union"), + color_keyword("const"), + color_keyword("typedef"), + color_keyword("extern"), + color_keyword("static"), + color_keyword("inline"), + color_keyword("if"), + color_keyword("else"), + color_keyword("for"), + color_keyword("while"), + color_keyword("case"), + color_keyword("switch"), + color_keyword("do"), + color_keyword("return"), + color_keyword("break"), + color_keyword("continue"), + color_keyword("goto"), + color_keyword("restrict"), + color_keyword("register"), + // functions + //{COLOR_WORD_BEFORE_STR, {"("}, aqua}, + // types + color_type("int"), + color_type("unsigned"), + color_type("long"), + color_type("short"), + color_type("char"), + color_type("void"), + color_type("float"), + color_type("double"), + color_type("complex"), + color_type("bool"), + color_type("_Bool"), + color_type("FILE"), + color_type("va_list"), + {COLOR_WORD_ENDING_WITH_STR, {"_t"}, type_color}, + {COLOR_WORD_ENDING_WITH_STR, {"_type"}, type_color}, + {COLOR_WORD_ENDING_WITH_STR, {"T"}, type_color}, + // numbers + color_number("0"), + color_number("1"), + color_number("2"), + color_number("3"), + color_number("4"), + color_number("5"), + color_number("6"), + color_number("7"), + color_number("8"), + color_number("9"), +}; + +#define default_word_seperators "., \n\t*+-/%!~<>=(){}[]\"^&|\\\'?:;" + +const struct color_scheme color_schemes[] = { + {".c", default_word_seperators, c_color_scheme, LEN(c_color_scheme)}, + {".h", default_word_seperators, c_color_scheme, LEN(c_color_scheme)}, + {0}, +}; + +/////////////////////////////////////////////////// +// Declarations +// + +typedef union { + int i; + uint ui; + float f; + int vec2i[2]; + const void *v; + const char *s; +} Arg; + +static void numlock(const Arg* arg); +static void window_split(const Arg* arg); +static void window_resize(const Arg *arg); +static void window_delete(const Arg *arg); +static void window_change(const Arg* arg); +static void zoom(const Arg* arg); +static void zoomabs(const Arg* arg); +static void zoomreset(const Arg* arg); +static void cursor_move_x_relative(const Arg* arg); +static void cursor_move_y_relative(const Arg* arg); +static void save_buffer(const Arg* arg); +static void toggle_selection(const Arg* arg); +static void move_cursor_to_offset(const Arg* arg); +static void move_cursor_to_end_of_buffer(const Arg* arg); +static void clipboard_copy(const Arg* arg); +static void clipboard_paste(const Arg* arg); +static void undo(const Arg* arg); +static void redo(const Arg* arg); + +static void cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason); +static void buffer_content_callback(struct file_buffer* buffer, int offset, enum buffer_content_reason reason); +static int keypress_actions(KeySym keysym, int modkey); +static void string_insert_callback(const char* buf, int buflen); +static void xunloadfonts(void); +static void xunloadfont(Font *); +static void buffer_copy_ub_to_current(struct window_buffer* buffer); +static void keep_cursor_col(struct window_buffer* buf, enum cursor_reason callback_reason); +static void move_selection(struct window_buffer* buf, enum cursor_reason callback_reason); +static void add_to_undo_buffer(struct file_buffer* buffer, int offset, enum buffer_content_reason reason); + +///////////////////////////////////////// +// Shortcuts +// + +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg* arg); + const Arg arg; +} Shortcut; + +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +const Shortcut shortcuts[] = { +// mask keysym function argument + { 0, XK_Right, cursor_move_x_relative, {.i = +1} }, + { 0, XK_Left, cursor_move_x_relative, {.i = -1} }, + { 0, XK_Down, cursor_move_y_relative, {.i = +1} }, + { 0, XK_Up, cursor_move_y_relative, {.i = -1} }, + { ControlMask, XK_Right, window_change, {.i = MOVE_RIGHT} }, + { ControlMask, XK_Left, window_change, {.i = MOVE_LEFT} }, + { ControlMask, XK_Down, window_change, {.i = MOVE_DOWN} }, + { ControlMask, XK_Up, window_change, {.i = MOVE_UP} }, + { TERMMOD, XK_Right, window_resize, {.i = MOVE_RIGHT} }, + { TERMMOD, XK_Left, window_resize, {.i = MOVE_LEFT} }, + { TERMMOD, XK_Down, window_resize, {.i = MOVE_DOWN} }, + { TERMMOD, XK_Up, window_resize, {.i = MOVE_UP} }, + { ControlMask, XK_m, toggle_selection, {0} }, + { ControlMask, XK_g, move_cursor_to_offset, {0} }, + { TERMMOD, XK_G, move_cursor_to_end_of_buffer, {0} }, + { ControlMask, XK_l, window_split, {.i = WINDOW_HORISONTAL}}, + { ControlMask, XK_k, window_split, {.i = WINDOW_VERTICAL} }, + { ControlMask, XK_d, window_delete, {0} }, + { ControlMask, XK_z, undo, {0} }, + { TERMMOD, XK_Z, redo, {0} }, + { ControlMask, XK_s, save_buffer, {0} }, + { ControlMask, XK_c, clipboard_copy, {0} }, + { ControlMask, XK_v, clipboard_paste,{0} }, + { TERMMOD, XK_Prior, zoom, {.f = +1} }, + { TERMMOD, XK_Next, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +///////////////////////////////////////////////// +// callbacks +// + +void(*cursor_movement_callback)(struct window_buffer*, enum cursor_reason) = cursor_callback; +void(*buffer_contents_updated)(struct file_buffer*, int, enum buffer_content_reason) = buffer_content_callback; +int(*keypress_callback)(KeySym, int) = keypress_actions; +void(*string_input_callback)(const char*, int) = string_insert_callback; +void(*draw_callback)(void) = NULL; +void(*buffer_written_to_screen_callback)(struct file_buffer*, int, int, int, int, int, int) = NULL; + + +//////////////////////////////////////////////// +// external globals +// + +extern struct window_split_node root_node; +extern struct window_buffer* focused_window; +extern struct window_split_node* focused_node; +extern TermWindow win; +extern XWindow xw; +extern DC dc; + +extern Term term; + +extern Fontcache *frc; +extern int frclen; +extern Atom xtarget; +extern double defaultfontsize; +extern double usedfontsize; +extern char* copy_buffer; +extern int copy_len; + +///////////////////////////////////////////////// +// function implementations +// + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void window_split(const Arg *arg) +{ + window_node_split(focused_node, 0.5, arg->i); + focused_node = focused_node->node2; + focused_window = &focused_node->window; +} + +void window_resize(const Arg *arg) +{ + window_node_resize(focused_node, arg->i); +} + +void window_delete(const Arg *arg) +{ + struct window_split_node* new_node = window_node_delete(focused_node); + while (new_node->mode != WINDOW_SINGULAR) + new_node = new_node->node1; + focused_node = new_node; + focused_window = &focused_node->window; +} + +void window_change(const Arg *arg) +{ + focused_node = window_switch_to_window(focused_node, arg->i); + focused_window = &focused_node->window; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(font, arg->f); + cresize(0, 0); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +cursor_move_x_relative(const Arg* arg) +{ + buffer_move_on_line(focused_window, arg->i, CURSOR_RIGHT_LEFT_MOVEMENT); +} + +void +cursor_move_y_relative(const Arg* arg) +{ + buffer_move_lines(focused_window, arg->i, 0); + buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); +} + +void +save_buffer(const Arg* arg) +{ + buffer_write_to_filepath(get_file_buffer(focused_window)); +} + +void +toggle_selection(const Arg* arg) +{ + struct file_buffer* fb = get_file_buffer(focused_window); + if (fb->mode & BUFFER_SELECTION_ON) { + fb->mode &= ~(BUFFER_SELECTION_ON); + } else { + fb->mode |= BUFFER_SELECTION_ON; + fb->s1o = fb->s2o = focused_window->cursor_offset; + } +} + +void +move_cursor_to_offset(const Arg* arg) +{ + focused_window->cursor_offset = arg->i; +} + +void +move_cursor_to_end_of_buffer(const Arg* arg) +{ + focused_window->cursor_offset = get_file_buffer(focused_window)->len-1; +} + +void +clipboard_copy(const Arg* arg) +{ + struct file_buffer* fb = get_file_buffer(focused_window); + int len; + char* temp = buffer_get_selection(fb, &len); + if (!temp) + return; + if (copy_buffer) + free(copy_buffer); + copy_buffer = temp; + copy_len = len; + + buffer_move_cursor_to_selection_start(focused_window); + fb->mode &= ~BUFFER_SELECTION_ON; + + Atom clipboard; + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); +} + +void +clipboard_paste(const Arg* arg) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xtarget, clipboard, + xw.win, CurrentTime); +} + +void +buffer_copy_ub_to_current(struct window_buffer* buffer) +{ + struct file_buffer* fb = get_file_buffer(buffer); + struct undo_buffer* cub = &fb->ub[fb->current_undo_buffer]; + assert(cub->contents); + + fb->contents = xrealloc(fb->contents, cub->capacity); + memcpy(fb->contents, cub->contents, cub->capacity); + fb->len = cub->len; + fb->capacity = cub->capacity; + + buffer_move_to_offset(buffer, cub->cursor_offset, CURSOR_SNAPPED); + buffer->y_scroll = cub->y_scroll; +} + +void +undo(const Arg* arg) +{ + struct file_buffer* fb = get_file_buffer(focused_window); + if (fb->current_undo_buffer == 0) + return; + fb->current_undo_buffer--; + fb->available_redo_buffers++; + + buffer_copy_ub_to_current(focused_window); +} + +void +redo(const Arg* arg) +{ + struct file_buffer* fb = get_file_buffer(focused_window); + if (fb->available_redo_buffers == 0) + return; + fb->available_redo_buffers--; + fb->current_undo_buffer++; + + buffer_copy_ub_to_current(focused_window); +} + +void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +////////////////////////////////////////////////////////7 +// Callbacks +// + +void +keep_cursor_col(struct window_buffer* buf, enum cursor_reason callback_reason) +{ + static int last_cursor_col; + + if (callback_reason == CURSOR_COMMAND_MOVEMENT || callback_reason == CURSOR_RIGHT_LEFT_MOVEMENT) { + int y; + buffer_offset_to_xy(buf, buf->cursor_offset, -1, &buf->cursor_col, &y); + last_cursor_col = buf->cursor_col; + } +} + +void move_selection(struct window_buffer* buf, enum cursor_reason callback_reason) +{ + struct file_buffer* fb = get_file_buffer(buf); + if (fb->mode & BUFFER_SELECTION_ON) { + fb->s2o = buf->cursor_offset; + } +} + +void +cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason) +{ + keep_cursor_col(buf, callback_reason); + move_selection(buf, callback_reason); + + printf("mved to: %d | reason: %d\n", buf->cursor_offset, callback_reason); +} + +void +add_to_undo_buffer(struct file_buffer* buffer, int offset, enum buffer_content_reason reason) +{ + static time_t last_normal_edit; + static int edits; + + if (reason == BUFFER_CONTENT_NORMAL_EDIT) { + time_t previous_time = last_normal_edit; + last_normal_edit = time(NULL); + + if (last_normal_edit - previous_time < 2 && edits < 30) { + edits++; + goto copy_undo_buffer; + } else { + edits = 0; + } + } else if (reason == BUFFER_CONTENT_INIT) { + goto copy_undo_buffer; + } + + if (buffer->available_redo_buffers > 0) { + buffer->available_redo_buffers = 0; + buffer->current_undo_buffer++; + goto copy_undo_buffer; + } + + if (buffer->current_undo_buffer == UNDO_BUFFERS_COUNT-1) { + char* begin_buffer = buffer->ub[0].contents; + memmove(buffer->ub, &(buffer->ub[1]), (UNDO_BUFFERS_COUNT-1) * sizeof(struct undo_buffer)); + buffer->ub[buffer->current_undo_buffer].contents = begin_buffer; + } else { + buffer->current_undo_buffer++; + } + +copy_undo_buffer: ; + struct undo_buffer* cub = &buffer->ub[buffer->current_undo_buffer]; + + cub->contents = xrealloc(cub->contents, buffer->capacity); + memcpy(cub->contents, buffer->contents, buffer->capacity); + cub->len = buffer->len; + cub->capacity = buffer->capacity; + cub->cursor_offset = offset; + if (focused_window) + cub->y_scroll = focused_window->y_scroll; + else + cub->y_scroll = 0; +} + +void +buffer_content_callback(struct file_buffer* buffer, int offset, enum buffer_content_reason reason) +{ + add_to_undo_buffer(buffer, offset, reason); +} + +int +keypress_actions(KeySym keysym, int modkey) +{ + // check shortcuts + for (int i = 0; i < LEN(shortcuts); i++) { + if (keysym == shortcuts[i].keysym && match(shortcuts[i].mod, modkey)) { + shortcuts[i].func(&(shortcuts[i].arg)); + return 1; + } + } + + // default actions + + int offset = focused_window->cursor_offset; + struct file_buffer* fb = get_file_buffer(focused_window); + + switch (keysym) { + int move; + case XK_BackSpace: + if (delete_selection(fb)) return 1; + if (offset <= 0) return 1; + + if (fb->contents[offset-1] == '\n') + buffer_move_lines(focused_window, -1, CURSOR_COMMAND_MOVEMENT); + else + buffer_move_on_line(focused_window, -1, CURSOR_COMMAND_MOVEMENT); + + offset = focused_window->cursor_offset; + + // FALLTHROUGH + case XK_Delete: + + if (delete_selection(fb)) return 1; + + move = buffer_remove(fb, offset, 1, 0, 0); + window_move_all_cursors_on_same_buf(&root_node, focused_node, focused_window->buffer_index, offset, + buffer_move_offset_relative, -move, CURSOR_COMMAND_MOVEMENT); + return 1; + case XK_Return: + delete_selection(fb); + + buffer_insert(fb, "\n", 1, offset, 0); + window_move_all_cursors_on_same_buf(&root_node, NULL, focused_window->buffer_index, offset, + buffer_move_offset_relative, 1, CURSOR_COMMAND_MOVEMENT); + window_move_all_yscrolls(&root_node, focused_node, focused_window->buffer_index, offset, 1); + return 1; + case XK_Home: { + int new_offset = buffer_seek_char_backwards(fb, offset, '\n'); + if (new_offset < 0) + new_offset = 0; + buffer_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT); + return 1; + } + case XK_End: { + int new_offset = buffer_seek_char(fb, offset, '\n'); + if (new_offset < 0) + new_offset = fb->len-1; + buffer_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT); + return 1; + } + case XK_Page_Down: + buffer_move_lines(focused_window, (term.row-1) / 2, 0); + buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + focused_window->y_scroll += (term.row-1) / 2; + return 1; + case XK_Page_Up: + buffer_move_lines(focused_window, -((term.row-1) / 2), 0); + buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + focused_window->y_scroll -= (term.row-1) / 2; + return 1; + case XK_Tab: + buffer_insert(fb, "\t", 1, offset, 0); + window_move_all_cursors_on_same_buf(&root_node, NULL, focused_window->buffer_index, offset, + buffer_move_on_line, 1, CURSOR_COMMAND_MOVEMENT); + return 1; + } + return 0; +} + +void string_insert_callback(const char* buf, int buflen) +{ + struct file_buffer* fb = get_file_buffer(focused_window); + + // TODO: allow blocking of the bufferwrite, redirecting to keybinds with multiple characther length + if (buf[0] >= 32) { + delete_selection(fb); + buffer_insert(fb, buf, buflen, focused_window->cursor_offset, 0); + window_move_all_cursors_on_same_buf(&root_node, NULL, focused_window->buffer_index, focused_window->cursor_offset, + buffer_move_offset_relative, buflen, CURSOR_COMMAND_MOVEMENT); + } else { + printf("unhandled control character %x\n", buf[0]); + } +} diff --git a/config.def.h b/config.def.h @@ -1,199 +0,0 @@ -/* See LICENSE file for copyright and license details. */ - -#include "win.h" -#undef Glyph -#include <unistd.h> -#include <X11/cursorfont.h> -#include <X11/Xft/Xft.h> - -/* types used in config.h */ -typedef struct { - uint mod; - KeySym keysym; - void (*func)(const Arg *); - const Arg arg; -} Shortcut; - -/* X modifiers */ -#define XK_ANY_MOD UINT_MAX -#define XK_NO_MOD 0 -#define XK_SWITCH_MOD (1<<13|1<<14) - -// default functions used for the config -// for the funtions implementation see the x.c file -static void numlock(const Arg *); -static void zoom(const Arg *); -static void zoomabs(const Arg *); -static void zoomreset(const Arg *); -static void cursor_move_x_relative(const Arg* arg); -static void cursor_move_y_relative(const Arg* arg); -static void save_buffer(const Arg* arg); -static void toggle_selection(const Arg* arg); -static void move_cursor_to_offset(const Arg* arg); -static void move_cursor_to_end_of_buffer(const Arg* arg); -static void clipboard_copy(const Arg* arg); -static void clipboard_paste(const Arg* arg); -static void undo(const Arg* arg); -static void redo(const Arg* arg); - -static void cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason); - -static void buffer_content_callback(struct file_buffer* buffer, int offset, enum buffer_content_reason reason); - -//TODO: make this file friendly to IDE's - -/* - * appearance - * - * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html - */ -static char *font = "Iosevka:pixelsize=16:antialias=true:autohint=true"; -static int borderpx = 2; - -/* Kerning / character bounding-box multipliers */ -static float cwscale = 1.0; -static float chscale = 1.0; - - -/* - * thickness of underline and bar cursors - */ -static unsigned int cursorthickness = 2; - -/* default TERM value */ -char *termname = "st-256color"; - -// spaces per tab -// tabs will self align -unsigned int tabspaces = 8; - -/* Terminal colors (16 first used in escape sequence) */ -static const char *colorname[] = { - /* 8 normal colors */ - "#282828", - "red3", - "green3", - "yellow3", - "blue2", - "magenta3", - "cyan3", - "#fbf1c7", - - /* 8 bright colors */ - "gray50", - "red", - "green", - "yellow", - "#5c5cff", - "magenta", - "cyan", - "white", - - [255] = 0, - - /* more colors can be added after 255 to use with DefaultXX */ - "#cccccc", - "#555555", -}; - - -/* - * Default colors (colorname index) - * foreground, background, cursor, reverse cursor - */ -Glyph_ default_attributes = {.bg = 0, .fg = 7}; -static unsigned int defaultcs = 256; - -int undo_buffers = 32; - -/* - * Default shape of cursor - * 2: Block ("█") - * 4: Underline ("_") - * 6: Bar ("|") - * 7: Snowman ("☃") - */ -static unsigned int cursorshape = 2; - -/* - * Default columns and rows numbers - */ - -static unsigned int cols = 80; -static unsigned int rows = 24; - -/* - * Default colour and shape of the mouse cursor - */ -static unsigned int mouseshape = XC_xterm; -static unsigned int mousefg = 7; -static unsigned int mousebg = 0; - -/* - * Color used to display font attributes when fontconfig selected a font which - * doesn't match the ones requested. - */ -static unsigned int defaultattr = 11; - -void(*cursor_movement_callback)(struct window_buffer*, enum cursor_reason) = cursor_callback; -void(*buffer_contents_updated)(struct file_buffer*, int, enum buffer_content_reason) = buffer_content_callback; - -/* Internal keyboard shortcuts. */ -#define MODKEY Mod1Mask -#define TERMMOD (ControlMask|ShiftMask) - -static Shortcut shortcuts[] = { - /* mask keysym function argument */ - { 0, XK_Right, cursor_move_x_relative, {.i = +1} }, - { 0, XK_Left, cursor_move_x_relative, {.i = -1} }, - { 0, XK_Down, cursor_move_y_relative, {.i = +1} }, - { 0, XK_Up, cursor_move_y_relative, {.i = -1} }, - { ControlMask, XK_m, toggle_selection, {0} }, - { ControlMask, XK_g, move_cursor_to_offset, {0} }, - { TERMMOD, XK_G, move_cursor_to_end_of_buffer, {0} }, - { ControlMask, XK_z, undo, {0} }, - { TERMMOD, XK_Z, redo, {0} }, - { ControlMask, XK_s, save_buffer, {0} }, - { ControlMask, XK_c, clipboard_copy, {0} }, - { ControlMask, XK_v, clipboard_paste,{0} }, - { TERMMOD, XK_Prior, zoom, {.f = +1} }, - { TERMMOD, XK_Next, zoom, {.f = -1} }, - { TERMMOD, XK_Home, zoomreset, {.f = 0} }, - { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, -}; - -/* - * Special keys (change & recompile st.info accordingly) - * - * Mask value: - * * Use XK_ANY_MOD to match the key no matter modifiers state - * * Use XK_NO_MOD to match the key alone (no modifiers) - * appkey value: - * * 0: no value - * * > 0: keypad application mode enabled - * * = 2: term.numlock = 1 - * * < 0: keypad application mode disabled - * appcursor value: - * * 0: no value - * * > 0: cursor application mode enabled - * * < 0: cursor application mode disabled - * - * Be careful with the order of the definitions because st searches in - * this table sequentially, so any XK_ANY_MOD must be in the last - * position for a key. - */ - -/* - * State bits to ignore when matching key or button events. By default, - * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. - */ -static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; - -/* - * Printable characters in ASCII, used to estimate the advance width - * of single wide characters. - */ -static char ascii_printable[] = - " !\"#$%&'()*+,-./0123456789:;<=>?" - "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" - "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/config.mk b/config.mk @@ -1,5 +1,5 @@ -# st version -VERSION = 0.8.4 +# se version +VERSION = 0.1 # Customize below to fit your system @@ -21,9 +21,9 @@ LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ `$(PKG_CONFIG) --libs freetype2` # flags -STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -STCFLAGS = $(INCS) $(STCPPFLAGS) $(CPPFLAGS) -Os -g3 -Wall -Wpedantic -STLDFLAGS = $(LIBS) $(LDFLAGS) +SECPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 +SECFLAGS = $(INCS) $(SECPPFLAGS) $(CPPFLAGS) -g -Wall -Wpedantic -Os +SELDFLAGS = $(LIBS) $(LDFLAGS) # OpenBSD: #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE @@ -32,4 +32,4 @@ STLDFLAGS = $(LIBS) $(LDFLAGS) # `$(PKG_CONFIG) --libs freetype2` # compiler and linker -# CC = c99 +# CC = clang diff --git a/se.c b/se.c @@ -0,0 +1,1666 @@ +/* See LICENSE for license details. */ + +/* +** This file mainly contains the functionality of handling the +** "buffer". There should a good amount of customisation you +** cand do wihtout tuching this file, but adding or changing +** functionality to fit your needs shouldn't be too hard. +*/ + +#include <errno.h> +#include <assert.h> +#include <ctype.h> +#include <dirent.h> + +#include "se.h" +#include "x.h" + +/////////////////////////////////////////// +// config.c variables and globals +// + +// default colors +extern Glyph default_attributes; +extern unsigned int alternate_bg_bright; +extern unsigned int alternate_bg_dark; + +extern unsigned int cursor_fg; +extern unsigned int cursor_bg; +extern unsigned int mouse_line_bg; + +extern unsigned int selection_bg; +extern unsigned int highlight_color; +extern unsigned int path_color; + +extern unsigned int error_color; +extern unsigned int warning_color; +extern unsigned int ok_color; + +// other +extern unsigned int tabspaces; +extern int wrap_buffer; +extern const struct color_scheme color_schemes[]; + +// x.c globals +extern struct window_buffer* focused_window; + +// se.c globals +Term term; +static Glyph global_attr; +static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; + +// callbacks +extern void(*cursor_movement_callback)(struct window_buffer*, enum cursor_reason); // buffer, current_pos, reason +extern void(*buffer_contents_updated)(struct file_buffer*, int, enum buffer_content_reason); // modified buffer, current_pos +extern void(*buffer_written_to_screen_callback)(struct window_buffer*, int, int, int, int, int, int); // drawn buffer, offset start & end, min x & y, max x & y +// TODO: planned callbacks: +// buffer written + +///////////////////////////////////////////// +// Internal functions +// + +// path must be freed +static void recursive_mkdir(char* path); +static void draw_dir(const char* path, const char* search, int* sel, int minx, int miny, int maxx, int maxy, int focused); +static void color_selection(Glyph* letter); +static void do_color_scheme(struct file_buffer* fb, struct color_scheme cs, int offset); +static int write_string(const char* string, int y, int minx, int maxx); +static int str_contains_char(const char* string, char check); +static int t_decode_utf8_buffer(const char* buffer, const int buflen, Rune* u); +static size_t utf8decode(const char *, Rune *, size_t); +static Rune utf8decodebyte(char, size_t *); +static char utf8encodebyte(Rune, size_t); +static size_t utf8validate(Rune *, size_t); +static int is_correct_mode(enum window_split_mode mode, enum move_directons move); + +//////////////////////////////////////////// +// function implementations +// + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + die("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == NULL) + die("realloc: %s\n", strerror(errno)); + + return p; +} + +size_t +utf8decode(const char *c, Rune *u, size_t clen) +{ + size_t i, j, len, type; + Rune udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Rune +utf8decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uchar)c & utfmask[*i]) == utfbyte[*i]) + return (uchar)c & ~utfmask[*i]; + return 0; +} + +size_t +utf8encode(Rune u, char *c) +{ + size_t len, i; + + len = utf8validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8encodebyte(u, len); + + return len; +} + +char +utf8encodebyte(Rune u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8validate(Rune *u, size_t i) +{ + const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; + const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = UTF_INVALID; + for (i = 1; *u > utfmax[i]; ++i) + ; + + return i; +} + +void +die(const char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +int +tattrset(int attr) +{ + for (int i = 0; i < term.row-1; i++) + for (int j = 0; j < term.col-1; j++) + if (term.line[i][j].mode & attr) + return 1; + return 0; +} + +void +tnew(int col, int row) +{ + global_attr = default_attributes; + + term = (Term){0}; + tresize(col, row); + tsetregion(0, 0, term.col-1, term.row-1, ' '); +} + + +struct window_buffer +window_buffer_new(int buffer_index) +{ + struct window_buffer wb = {0}; + wb.buffer_index = buffer_index; + if (path_is_folder(get_file_buffer(&wb)->file_path)) + wb.mode = WINDOW_BUFFER_FILE_BROWSER; + + return wb; +} + +int +buffer_seek_char(const struct file_buffer* buf, int offset, char byte) +{ + LIMIT(offset, 0, buf->len-1); + char* new_buf = memchr(buf->contents + offset, byte, buf->len - offset); + if (!new_buf) return -1; + return new_buf - buf->contents; +} + +int +buffer_seek_char_backwards(const struct file_buffer* buf, int offset, char byte) +{ + LIMIT(offset, 0, buf->len-1); + for (int n = offset-1; n >= 0; n--) { + if (buf->contents[n] == byte) { + return n+1; + } + } + return -1; +} + +int +buffer_seek_string(const struct file_buffer* buf, int offset, const char* string) +{ + LIMIT(offset, 0, buf->len-1); + int str_len = strlen(string); + + for (int n = offset; n < buf->len - str_len; n++) + if (!memcmp(buf->contents + n, string, str_len)) + return n; + return -1; +} + +int +buffer_seek_string_backwards(const struct file_buffer* buf, int offset, const char* string) +{ + int str_len = strlen(string); + offset += str_len; + LIMIT(offset, 0, buf->len-1); + + for (int n = offset - str_len; n >= 0; n--) + if (!memcmp(buf->contents + n, string, str_len)) + return n; + return -1; +} + +void +buffer_move_on_line(struct window_buffer* buf, int amount, enum cursor_reason callback_reason) +{ + const struct file_buffer* fb = get_file_buffer((buf)); + if (fb->len <= 0) + return; + + if (amount < 0) { + /* + ** we cant get the size of a utf8 char backwards + ** therefore we move all the way to the start of the line, + ** then a seeker will try to find the cursor pos + ** the follower will then be *amount* steps behind, + ** when the seeker reaches the cursor + ** the follower will be the new cursor position + */ + + int line_start = buffer_seek_char_backwards(fb, buf->cursor_offset, '\n'); + amount = abs(MAX(line_start - buf->cursor_offset, amount)); + assert(amount < 2048); + + char moves[amount]; + int seek_pos = line_start, follower_pos = line_start; + int n = 0; + while (seek_pos < buf->cursor_offset) { + Rune u; + int charsize = t_decode_utf8_buffer(fb->contents + seek_pos, fb->len - seek_pos, &u); + seek_pos += charsize; + if (n < amount) { + moves[n++] = charsize; + } else { + follower_pos += moves[0]; + memmove(moves, moves + 1, amount - 1); + moves[amount - 1] = charsize; + } + } + buf->cursor_offset = follower_pos; + + LIMIT(buf->cursor_offset, 0, fb->len); + } else if (amount > 0) { + for (int charsize = 0; + buf->cursor_offset < fb->len && amount > 0 && fb->contents[buf->cursor_offset + charsize] != '\n'; + buf->cursor_offset += charsize, amount--) { + Rune u; + charsize = t_decode_utf8_buffer(fb->contents + buf->cursor_offset, fb->len - buf->cursor_offset, &u); + if (u != '\n' && u != '\t') + if (wcwidth(u) <= 0) + amount++; + if (buf->cursor_offset + charsize > fb->len) + break; + } + } + + if (callback_reason && cursor_movement_callback) + cursor_movement_callback(buf, callback_reason); +} + +void +buffer_move_offset_relative(struct window_buffer* buf, int amount, enum cursor_reason callback_reason) +{ + //NOTE: this does not check if the character on this offset is the start of a valid utf8 char + const struct file_buffer* fb = get_file_buffer((buf)); + if (fb->len <= 0) + return; + buf->cursor_offset += amount; + LIMIT(buf->cursor_offset, 0, fb->len); + + if (callback_reason && cursor_movement_callback) + cursor_movement_callback(buf, callback_reason); +} + +void +buffer_move_lines(struct window_buffer* buf, int amount, enum cursor_reason callback_reason) +{ + const struct file_buffer* fb = get_file_buffer((buf)); + if (fb->len <= 0) + return; + int offset = buf->cursor_offset; + if (amount > 0) { + while (amount-- && offset >= 0) { + int new_offset = buffer_seek_char(fb, offset, '\n'); + if (new_offset < 0) { + offset = fb->len; + break; + } + offset = new_offset+1; + } + } else if (amount < 0) { + while (amount++ && offset >= 0) + offset = buffer_seek_char_backwards(fb, offset, '\n')-1; + } + buffer_move_to_offset(buf, offset, callback_reason); +} + +void +buffer_move_to_offset(struct window_buffer* buf, int offset, enum cursor_reason callback_reason) +{ + //NOTE: this does not check if the character on this offset is the start of a valid utf8 char + const struct file_buffer* fb = get_file_buffer((buf)); + if (fb->len <= 0) + return; + LIMIT(offset, 0, fb->len); + buf->cursor_offset = offset; + + if (callback_reason && cursor_movement_callback) + cursor_movement_callback(buf, callback_reason); +} + +void +buffer_move_to_x(struct window_buffer* buf, int x, enum cursor_reason callback_reason) +{ + assert(buf); + struct file_buffer* fb = get_file_buffer(buf); + + int offset = buffer_seek_char_backwards(fb, buf->cursor_offset, '\n'); + if (offset < 0) + offset = 0; + buffer_move_to_offset(buf, offset, 0); + + int x_counter = 0; + + while (offset < fb->len) { + if (fb->contents[offset] == '\t') { + offset++; + if (x_counter <= 0) x_counter += 1; + while (x_counter % tabspaces != 0) x_counter += 1; + x_counter += 1; + continue; + } else if (fb->contents[offset] == '\n') { + break; + } + Rune u = 0; + int charsize = t_decode_utf8_buffer(fb->contents + offset, fb->len - offset, &u); + x_counter += wcwidth(u); + if (x_counter <= x) { + offset += charsize; + if (x_counter == x) + break; + } else { + break; + } + } + buffer_move_to_offset(buf, offset, callback_reason); +} + +void +window_node_split(struct window_split_node* parent, float ratio, enum window_split_mode mode) +{ + assert(parent); + assert(parent->mode == WINDOW_SINGULAR); + assert(mode != WINDOW_SINGULAR); + + parent->node1 = xmalloc(sizeof(struct window_split_node)); + parent->node2 = xmalloc(sizeof(struct window_split_node)); + + *parent->node1 = *parent; + *parent->node2 = *parent; + parent->node1->parent = parent; + parent->node2->parent = parent; + + parent->node1->node1 = NULL; + parent->node1->node2 = NULL; + parent->node2->node1 = NULL; + parent->node2->node2 = NULL; + + parent->mode = mode; + parent->ratio = ratio; + parent->window = (struct window_buffer){0}; +} + +struct window_split_node* +window_node_delete(struct window_split_node* node) +{ + if (!node->parent) + return node; + struct window_split_node* old = node; + node = node->parent; + struct window_split_node* other = (node->node1 == old) ? node->node2 : node->node1; + free(old); + + struct window_split_node* parent = node->parent; + *node = *other; + if (other->mode != WINDOW_SINGULAR) { + other->node1->parent = node; + other->node2->parent = node; + } + free(other); + node->parent = parent; + + return node; +} + +void +window_draw_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy) +{ + assert(root); + + if (root->mode == WINDOW_SINGULAR) { + buffer_draw_to_screen(&root->window, minx, miny, maxx, maxy); + } else if (root->mode == WINDOW_HORISONTAL) { + int middlex = ((float)(maxx - minx) * root->ratio) + minx; + + // print seperator + tsetregion(middlex+1, miny, middlex+1, maxy, L'│'); + + window_draw_tree_to_screen(root->node1, minx, miny, middlex, maxy); + window_draw_tree_to_screen(root->node2, middlex+2, miny, maxx, maxy); + + // print connecting borders + if (middlex+1 >= term.col) + return; + if (miny-1 >= 0 && miny-1 < term.row) { + if (term.line[miny-1][middlex+1].u == L'┬' || + term.line[miny-1][middlex+1].u == L'┴' || + term.line[miny-1][middlex+1].u == L'├' || + term.line[miny-1][middlex+1].u == L'┤') + tsetchar(L'┼', middlex+1, miny-1); + if (term.line[miny-1][middlex+1].u == L'─') + tsetchar(L'┬', middlex+1, miny-1); + } + if (maxy+1 >= 0 && maxy+1 < term.row) { + if (term.line[maxy+1][middlex+1].u == L'┬' || + term.line[maxy+1][middlex+1].u == L'┴' || + term.line[maxy+1][middlex+1].u == L'├' || + term.line[maxy+1][middlex+1].u == L'┤') + tsetchar(L'┼', middlex+1, maxy+1); + if (term.line[maxy+1][middlex+1].u == L'─') + tsetchar(L'┴', middlex+1, maxy+1); + } + for (int y = miny; y < maxy+1; y++) + xdrawline(term.line[y], middlex+1, y, middlex+2); + } else if (root->mode == WINDOW_VERTICAL) { + int middley = ((float)(maxy - miny) * root->ratio) + miny; + + // print seperator + tsetregion(minx, middley+1, maxx, middley+1, L'─'); + //write_string(get_file_buffer(&root->window)->file_path, middley+1, minx, maxx); + + window_draw_tree_to_screen(root->node1, minx, miny, maxx, middley); + window_draw_tree_to_screen(root->node2, minx, middley+2, maxx, maxy); + + // print connecting borders + if (middley+1 >= term.row) + return; + if (minx-1 >= 0 && minx-1 < term.col) { + if (term.line[middley+1][minx-1].u == L'┬' || + term.line[middley+1][minx-1].u == L'┴' || + term.line[middley+1][minx-1].u == L'├' || + term.line[middley+1][minx-1].u == L'┤') + tsetchar(L'┼', minx-1, middley+1); + if (term.line[middley+1][minx-1].u == L'│') + tsetchar(L'├', minx-1, middley+1); + } + if (maxx+1 >= 0 && maxx+1 < term.col) { + if (term.line[middley+1][maxx+1].u == L'┤') + if (term.line[middley+1][maxx+1].u == L'┬' || + term.line[middley+1][maxx+1].u == L'┴' || + term.line[middley+1][maxx+1].u == L'├' || + term.line[middley+1][maxx+1].u == L'┤') + tsetchar(L'┼', maxx+1, middley+1); + if (term.line[middley+1][maxx+1].u == L'│') + tsetchar(L'┤', maxx+1, middley+1); + } + for (int y = middley+1; y < middley+2; y++) + xdrawline(term.line[y], minx, y, maxx+1); + } +} + +void +window_move_all_cursors_on_same_buf(struct window_split_node* root, struct window_split_node* excluded, int buf_index, int offset, + void(movement)(struct window_buffer*, int, enum cursor_reason), + int move, enum cursor_reason reason) +{ + if (root->mode == WINDOW_SINGULAR) { + if (root->window.buffer_index == buf_index && root->window.cursor_offset >= offset && root != excluded) + movement(&root->window, move, reason); + } else { + window_move_all_cursors_on_same_buf(root->node1, excluded, buf_index, offset, movement, move, reason); + window_move_all_cursors_on_same_buf(root->node2, excluded, buf_index, offset, movement, move, reason); + } +} + +void +window_move_all_yscrolls(struct window_split_node* root, struct window_split_node* excluded, int buf_index, int offset, int move) +{ + if (root->mode == WINDOW_SINGULAR) { + if (root->window.buffer_index == buf_index && root->window.cursor_offset >= offset && root != excluded) + root->window.y_scroll += move; + } else { + window_move_all_yscrolls(root->node1, excluded, buf_index, offset, move); + window_move_all_yscrolls(root->node2, excluded, buf_index, offset, move); + } +} + +int +window_other_nodes_contain_file_buffer(struct window_split_node* node, struct window_split_node* root) +{ + if (root->mode == WINDOW_SINGULAR) + return (root->window.buffer_index == node->window.buffer_index && root != node); + + return (window_other_nodes_contain_file_buffer(node, root->node1) || + window_other_nodes_contain_file_buffer(node, root->node2)); +} + +int +is_correct_mode(enum window_split_mode mode, enum move_directons move) +{ + if (move == MOVE_RIGHT || move == MOVE_LEFT) + return (mode == WINDOW_HORISONTAL); + if (move == MOVE_UP || move == MOVE_DOWN) + return (mode == WINDOW_VERTICAL); + return 0; +} + +struct window_split_node* +window_switch_to_window(struct window_split_node* node, enum move_directons move) +{ + assert(node); + if (!node->parent) return node; + assert(node->mode == WINDOW_SINGULAR); + struct window_split_node* old_node = node; + + if (move == MOVE_RIGHT || move == MOVE_DOWN) { + // traverse up the tree to the right + for (; node->parent; node = node->parent) { + if (is_correct_mode(node->parent->mode, move) && node->parent->node1 == node) { + // traverse down until a screen is found + node = node->parent->node2; + while(node->mode != WINDOW_SINGULAR) + node = node->node1; + + return node; + } + } + } else if (move == MOVE_LEFT || move == MOVE_UP) { + // traverse up the tree to the left + for (; node->parent; node = node->parent) { + if (is_correct_mode(node->parent->mode, move) && node->parent->node2 == node) { + // traverse down until a screen is found + node = node->parent->node1; + while(node->mode != WINDOW_SINGULAR) + node = node->node2; + + return node; + } + } + } + + return old_node; +} + +void +window_node_resize(struct window_split_node* node, enum move_directons move) +{ + for (; node; node = node->parent) { + if (is_correct_mode(node->mode, move)) { + float amount = (move == MOVE_RIGHT || move == MOVE_LEFT) ? 0.1f : 0.05f; + if (move == MOVE_RIGHT || move == MOVE_DOWN) amount = -amount; + node->ratio -= amount; + LIMIT(node->ratio, 0.001f, 0.95f); + return; + } + } +} + +int +path_is_folder(const char* path) { + struct stat statbuf; + if (stat(path, &statbuf) != 0) + return 0; + return S_ISDIR(statbuf.st_mode); +} + +// folder and file must be freed +char* +file_path_get_path(const char* path) +{ + assert(path); + + const char* folder_start = strrchr(path, '/'); + if (!folder_start) + folder_start = path; + else + folder_start++; + int folder_len = folder_start - path; + char* folder = xmalloc(folder_len + 1); + + memcpy(folder, path, folder_len); + folder[folder_len] = '\0'; + + return folder; +} + +int +file_browser_next_item(DIR* dir, const char* path, const char* search, char* full_path, char* filename) +{ + assert(path); + assert(dir); + assert(strlen(path) < PATH_MAX+1); + int len = strlen(search); + + struct dirent *folder; + while((folder = readdir(dir))) { + strcpy(filename, folder->d_name); + strcpy(full_path, path); + strcat(full_path, filename); + if (path_is_folder(full_path)) + strcat(filename, "/"); + + if (memcmp(filename, search, len) == 0) { + if (search[0] != '.' && folder->d_name[0] == '.') + continue; + if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) + continue; + return 1; + } + } + *filename = *full_path = 0; + return 0; +} + +void +draw_dir(const char* path, const char* search, int* sel, int minx, int miny, int maxx, int maxy, int focused) +{ + static char full_path[PATH_MAX]; + static char filename[PATH_MAX]; + assert(path); + assert(sel); + + global_attr.bg = alternate_bg_dark; + tsetregion(minx, miny+1, maxx, maxy, ' '); + global_attr = default_attributes; + + int len = strlen(search); + DIR *dir = opendir(path); + + int folder_lines = maxy - miny - 1; + int folders = 0; + while(file_browser_next_item(dir, path, search, full_path, filename)) + folders++; + + rewinddir(dir); + *sel = MIN(*sel, folders-1); + int sel_local = *sel; + char count[256]; + if (sel_local > folder_lines) + snprintf(count, 256, "^[%2d] ", folders); + else if (folders > folder_lines) + snprintf(count, 256, "ˇ[%2d] ", folders); + else + snprintf(count, 256, " [%2d] ", folders); + + global_attr.fg = path_color; + int new_x = write_string(count, miny, minx, maxx+1); + new_x = write_string(path, miny, new_x, maxx+1); + + global_attr = default_attributes; + new_x = write_string(search, miny, new_x, maxx+1); + + global_attr = default_attributes; + global_attr.bg = alternate_bg_dark; + + int start_miny = miny; + folders--; + miny++; + while(miny < maxy && file_browser_next_item(dir, path, search, full_path, filename)) { + if (path_is_folder(full_path)) + global_attr.fg = path_color; + else + global_attr.fg = default_attributes.fg; + + if (folders > folder_lines && sel_local > folder_lines) { + folders--; + sel_local--; + continue; + } + + write_string(filename, miny, minx, maxx+1); + for (int i = minx; i < minx + len; i++) + term.line[miny][i].fg = highlight_color; + if (miny - start_miny - 1 == sel_local) + for (int i = minx; i < maxx+1; i++) + term.line[miny][i].bg = selection_bg; + miny++; + } + miny = MIN(miny, maxy); + closedir(dir); + + + if (folders < 0) { + global_attr = default_attributes; + if (search[strlen(search)-1] == '/') + global_attr.fg = error_color; + else + global_attr.fg = warning_color; + write_string(" [Create New File]", start_miny, new_x, maxx+1); + } + + for (int y = start_miny; y < maxy+1; y++) + xdrawline(term.line[y], minx, y, maxx+1); + + xdrawcursor(new_x, start_miny, focused); + + global_attr = default_attributes; +} + +void recursive_mkdir(char *path) { + if (!path || !strlen(path)) + return; + char *sep = strrchr(path, '/'); + if(sep) { + *sep = '\0'; + recursive_mkdir(path); + *sep = '/'; + } + if(mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) && errno != EEXIST) + fprintf(stderr, "error while trying to create '%s'\n%s\n", path, strerror(errno)); +} + +struct file_buffer +buffer_new(const char* file_path) +{ + struct file_buffer buffer = {0}; + buffer.file_path = xmalloc(PATH_MAX); + + char* res = realpath(file_path, buffer.file_path); + if (!res) { + char* path = file_path_get_path(file_path); + recursive_mkdir(path); + free(path); + + FILE *new_file = fopen(file_path, "wb"); + fclose(new_file); + + realpath(file_path, buffer.file_path); + } + + if (path_is_folder(buffer.file_path)) { + int len = strlen(buffer.file_path); + if (buffer.file_path[len-1] != '/' && len < PATH_MAX-1) { + buffer.file_path[len] = '/'; + buffer.file_path[len+1] = '\0'; + } + buffer.len = 0; + buffer.capacity = 100; + buffer.contents = xmalloc(buffer.capacity); + } else { + FILE *file = fopen(buffer.file_path, "rb"); + fseek(file, 0L, SEEK_END); + long readsize = ftell(file); + rewind(file); + + if (readsize > (long)1.048576e+7) { + fclose(file); + die("you are opening a huge file(>10MiB), not allowed"); + } + + buffer.len = readsize; + buffer.capacity = readsize + 100; + + buffer.contents = xmalloc(buffer.capacity); + buffer.contents[0] = 0; + + char bom[4] = {0}; + fread(bom, 1, 3, file); + if (strcmp(bom, "\xEF\xBB\xBF")) + rewind(file); + else + buffer.mode |= BUFFER_UTF8_SIGNED; + fread(buffer.contents, 1, readsize, file); + fclose(file); + } + + buffer.ub = xmalloc(sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); + memset(buffer.ub, 0, sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); + + if (buffer_contents_updated) + buffer_contents_updated(&buffer, 0, BUFFER_CONTENT_INIT); + + return buffer; +} + +void +buffer_destroy(struct file_buffer* fb) +{ + free(fb->ub); + free(fb->contents); + free(fb->file_path); + *fb = (struct file_buffer){0}; +} + +void +buffer_insert(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback) +{ + assert(buf->contents); + if (offset > buf->len || offset < 0) { + fprintf(stderr, "writing past buf %s\n", buf->file_path); + return; + } + + if (buf->len + len >= buf->capacity) { + buf->capacity = buf->len + len + 256; + buf->contents = xrealloc(buf->contents, buf->capacity); + } + if (offset < buf->len) + memmove(buf->contents+offset+len, buf->contents+offset, buf->len-offset); + buf->len += len; + + memcpy(buf->contents+offset, new_content, len); + if (buffer_contents_updated && !do_not_callback) + buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); +} + +void +buffer_change(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback) +{ + assert(buf->contents); + if (offset > buf->len) + die("writing past buf %s\n", buf->file_path); + + if (offset + len > buf->len) { + buf->len = offset + len; + if (buf->len >= buf->capacity) { + buf->capacity = buf->len + len + 256; + buf->contents = xrealloc(buf->contents, buf->capacity); + } + } + + memcpy(buf->contents+offset, new_content, len); + if (buffer_contents_updated && !do_not_callback) + buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); +} + +int +buffer_remove(struct file_buffer* buf, const int offset, int len, int do_not_calculate_charsize, int do_not_callback) +{ + assert(buf->contents); + if (offset > buf->len) + die("deleting past buffer (offset is %d len is %d)\n", offset, buf->len); + + int removed_len = 0; + if (do_not_calculate_charsize) { + removed_len = len; + } else { + while (len--) { + int charsize = t_decode_utf8_buffer(buf->contents + offset, buf->len - offset, NULL); + if (buf->len - charsize < 0) + return 0; + removed_len += charsize; + } + } + buf->len -= removed_len; + memmove(buf->contents+offset, buf->contents+offset+removed_len, buf->len-offset); + if (buffer_contents_updated && !do_not_callback) + buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); + return removed_len; +} + +int +buffer_get_charsize(Rune u, int cur_x_pos) +{ + if (u == '\t') + return 8 - (cur_x_pos % tabspaces); + if (u == '\n') + return 0; + return wcwidth(u); + +} + +void +buffer_offset_to_xy(struct window_buffer* buf, int offset, int maxx, int* cx, int* cy) +{ + assert(buf); + struct file_buffer* fb = get_file_buffer(buf); + + *cx = *cy = 0; + if (fb->len <= 0) + return; + LIMIT(offset, 0, fb->len); + + char* repl = fb->contents; + char* last = repl + offset; + + char* new_repl; + if (wrap_buffer && maxx > 0) { + int yscroll = 0; + while ((new_repl = memchr(repl, '\n', last - repl))) { + if (++yscroll >= buf->y_scroll) + break; + repl = new_repl+1; + } + *cy = yscroll - buf->y_scroll; + } else { + while ((new_repl = memchr(repl, '\n', last - repl))) { + repl = new_repl+1; + *cy += 1; + } + *cy -= buf->y_scroll; + } + + while (repl < last) { + if (wrap_buffer && maxx > 0 && (*repl == '\n' || *cx >= maxx)) { + *cy += 1; + *cx = 0; + repl++; + continue; + } + if (*repl == '\t') { + repl++; + if (*cx <= 0) *cx += 1; + while (*cx % tabspaces != 0) *cx += 1; + *cx += 1; + continue; + } + Rune u; + repl += t_decode_utf8_buffer(repl, last - repl, &u); + *cx += wcwidth(u); + } +} + +void +buffer_draw_to_screen(struct window_buffer* buf, int minx, int miny, int maxx, int maxy) +{ + assert(buf); + struct file_buffer* fb = get_file_buffer(buf); + + LIMIT(maxx, 0, term.col-1); + LIMIT(maxy, 0, term.row-1); + LIMIT(minx, 0, maxx-1); + LIMIT(miny, 0, maxy-1); + LIMIT(buf->cursor_offset, 0, fb->len); + tsetregion(minx, miny, maxx, maxy, ' '); + + if (buf->mode == WINDOW_BUFFER_FILE_BROWSER) { + char* folder = file_path_get_path(fb->file_path); + + buffer_change(fb, "\0", 1, fb->len, 1); + if (fb->len > 0) fb->len--; + + draw_dir(folder, fb->contents, &buf->y_scroll, minx, miny, maxx, maxy, buf == focused_window); + + free(folder); + return; + } + + int color_scheme_available = -1; + for (int i = 0; color_schemes[i].file_ending; i++) { + if (is_file_type(fb->file_path, color_schemes[i].file_ending)) { + color_scheme_available = i; + break; + } + } + + int x = minx, y = miny; + global_attr = default_attributes; + do_color_scheme(NULL, (struct color_scheme){0}, 0); + + // force the screen in a place where the cursor is visable + int ox, oy; + buffer_offset_to_xy(buf, buf->cursor_offset, maxx - minx, &ox, &oy); + ox += minx - (maxx-3); + int xscroll = 0; + if (ox > 0) + xscroll = ox; + if (oy < 0) { + buf->y_scroll += oy; + } else { + oy += miny - maxy; + if (oy > 0) + buf->y_scroll += oy; + } + if (buf->y_scroll < 0) + buf->y_scroll = 0; + + if (wrap_buffer) + xscroll = 0; + + // move to y_scroll + char* repl = fb->contents; + char* last = repl + fb->len; + char* new_repl; + int line = buf->y_scroll; + while ((new_repl = memchr(repl, '\n', last - repl))) { + if (--line < 0) + break; + else if (new_repl+1 < last) + repl = new_repl+1; + else + return; + } + int offset_start = repl - fb->contents; + int cursor_x = 0, cursor_y = 0; + + // search backwards to find multi-line syntax highlighting + if (color_scheme_available >= 0) { + for (int i = 0; i < color_schemes[color_scheme_available].entry_count; i++) { + const struct color_scheme_entry cs = color_schemes[color_scheme_available].entries[i]; + if (cs.mode == COLOR_AROUND || cs.mode == COLOR_INSIDE) { + int offset = 0; + int count = 0; + int start_len = strlen(cs.arg.start); + while((offset = buffer_seek_string(fb, offset, cs.arg.start)) >= 0) { + offset += start_len; + if (offset >= offset_start) + break; + count++; + } + + if (strcmp(cs.arg.start, cs.arg.end) != 0) { + int end_len = strlen(cs.arg.end); + offset = 0; + while((offset = buffer_seek_string(fb, offset, cs.arg.end)) >= 0) { + offset += end_len; + if (offset >= offset_start) + break; + count--; + } + } + if (count > 0) { + offset = buffer_seek_string_backwards(fb, offset_start, cs.arg.start); + do_color_scheme(fb, color_schemes[color_scheme_available], offset); + break; + } + } + } + } + + // actually write to the screen + int once = 0; + for (int charsize = 1; repl < last && charsize; repl += charsize) { + if (!once && repl - fb->contents >= buf->cursor_offset) { + // if the buffer being drawn is focused, set the cursor position global + once = 1; + cursor_x = x - xscroll; + cursor_y = y; + LIMIT(cursor_x, minx, maxx); + LIMIT(cursor_y, miny, maxy); + } + + if (color_scheme_available >= 0) + do_color_scheme(fb, color_schemes[color_scheme_available], repl - fb->contents); + + if (!wrap_buffer && x - xscroll > maxx && *repl != '\n') { + charsize = 1; + x++; + continue; + } + + if (*repl == '\n' || (wrap_buffer && x >= maxx)) { + x = minx; + if (++y > maxy) + break; + if (wrap_buffer && *repl != '\n') + continue; + charsize = 1; + continue; + } else if (*repl == '\t') { + charsize = 1; + if (x <= 0) { + x += tsetchar(' ', x - xscroll, y); + if (x >= maxx) + continue; + } + while (x % tabspaces != 0 && x - xscroll <= maxx) + x += tsetchar(' ', x - xscroll, y); + + if (x - xscroll <= maxx) + x += tsetchar(' ', x, y); + continue; + } + + Rune u; + charsize = t_decode_utf8_buffer(repl, last - repl, &u); + + int width; + if (x - xscroll >= minx) + width = tsetchar(u, x - xscroll, y); + else + width = wcwidth(u); + + x += width; + } + int offset_end = repl - fb->contents; + + if (buf->cursor_offset >= fb->len) { + cursor_x = x - xscroll; + cursor_y = y; + } + + if(buffer_written_to_screen_callback) + buffer_written_to_screen_callback(buf, offset_start, offset_end, minx, miny, maxx, maxy); + + if (buf == focused_window) + for (int i = minx; i < maxx+1; i++) + term.line[cursor_y][i].bg = mouse_line_bg; + + buffer_write_selection(buf, minx, miny, maxx, maxy); + do_color_scheme(NULL, (struct color_scheme){0}, 0); + + for (int i = miny; i < maxy+1; i++) + xdrawline(term.line[i], minx, i, maxx+1); + + xdrawcursor(cursor_x, cursor_y, buf == focused_window); +} + +void +do_color_scheme(struct file_buffer* fb, struct color_scheme cs, int offset) +{ + static int end_at_whitespace = 0; + static const char* end_condition; + static int end_condition_len; + static Glyph next_word_attr; + static int color_next_word = 0; + static int around = 0; + + if (!fb) { + // reset + end_at_whitespace = 0; + end_condition_len = 0; + around = 0; + color_next_word = 0; + end_condition = NULL; + global_attr = default_attributes; + return; + } + + char* buf = fb->contents; + int buflen = fb->len; + + if (end_condition && !color_next_word) { + if (buflen - offset <= end_condition_len + || (offset-1 >= 0 && buf[offset-1] == '\\')) + return; + if (end_at_whitespace && buf[offset] == '\n') { + // *_TO_LINE reached end of line + end_condition_len = 0; + end_condition = NULL; + end_at_whitespace = 0; + global_attr = default_attributes; + } else if (memcmp(buf + offset, end_condition, end_condition_len) == 0) { + // end word mathces + if (isspace(end_condition[end_condition_len-1])) { + end_condition_len--; + if (end_condition_len <= 0) + global_attr = default_attributes; + } + // if it's around not inside, don't reset color + if (around) + around = 0; + else + global_attr = default_attributes; + + end_condition = NULL; + end_at_whitespace = 0; + } + return; + } else if (end_at_whitespace) { + if (str_contains_char(cs.word_seperators, buf[offset])) { + end_at_whitespace = 0; + global_attr = default_attributes; + } else { + return; + } + } else if (color_next_word) { + // check if new word encountered + if (str_contains_char(cs.word_seperators, buf[offset])) + return; + global_attr = next_word_attr; + color_next_word = 0; + end_at_whitespace = 1; + return; + } else if (end_condition_len > 0) { + // wait for the word/sequence to finish + // NOTE: does not work with utf8 chars + if (--end_condition_len <= 0) + global_attr = default_attributes; + else + return; + } + + for (int i = 0; i < cs.entry_count; i++) { + struct color_scheme_entry entry = cs.entries[i]; + enum color_scheme_mode mode = entry.mode; + + if (mode == COLOR_UPPER_CASE_WORD) { + // check if this is a new word + if (str_contains_char(cs.word_seperators, buf[offset])) continue; + + // check if it's upper case + int end_len = 0; + while (offset + end_len < fb->len && !str_contains_char(cs.word_seperators, buf[offset + end_len])) { + if (!isupper(buf[offset + end_len]) && buf[offset + end_len] != '_' + && (!end_len || (buf[offset + end_len] < '0' || buf[offset + end_len] > '9'))) + goto not_upper_case; + end_len++; + } + // upper case words must be longer than x chars + if (end_len < 3) continue; + + global_attr = entry.attr; + end_condition_len = end_len; + return; + + not_upper_case: + continue; + } + + int len = strlen(entry.arg.start); + + if (mode == COLOR_WORD_BEFORE_STR || mode == COLOR_WORD_BEFORE_STR_STR || mode == COLOR_WORD_ENDING_WITH_STR) { + // check if this is a new word + if (str_contains_char(cs.word_seperators, buf[offset])) continue; + + int offset_tmp = offset; + // find new word twice if it's BEFORE_STR_STR + int times = mode == COLOR_WORD_BEFORE_STR_STR ? 2 : 1; + int first_word_len = 0; + int first_time = 1; + while (times--) { + // seek end of word + int chars = 0; + while (offset_tmp < fb->len && !str_contains_char(cs.word_seperators, buf[offset_tmp])) { + if (buf[offset_tmp] != '*') + chars++; + offset_tmp++; + } + if (!chars && mode == COLOR_WORD_BEFORE_STR_STR) + goto exit_word_before_str_str; + if (first_time) + first_word_len = chars; + + // seek start of word + if (mode != COLOR_WORD_ENDING_WITH_STR) { + int whiespaces = 0; + while (offset_tmp < fb->len && (isspace(buf[offset_tmp]) || buf[offset_tmp] == '*')) { + offset_tmp++; + whiespaces++; + } + } + first_time = 0; + } + if (mode == COLOR_WORD_ENDING_WITH_STR) { + offset_tmp -= len; + if (offset_tmp < 0) + continue; + } + // check if string matches + if (memcmp(buf + offset_tmp, entry.arg.start, len) == 0) { + global_attr = entry.attr; + end_condition_len = first_word_len; + return; + } + exit_word_before_str_str: + continue; + } + + if (mode == COLOR_INSIDE || mode == COLOR_INSIDE_TO_LINE || mode == COLOR_WORD_INSIDE) { + if (offset - len < 0) + continue; + // check the if what's behind the cursor is the first string + if (memcmp(buf + offset - len, entry.arg.start, len) == 0) { + assert(entry.arg.end); + int end_len = strlen(entry.arg.end); + if (offset < fb->len && memcmp(buf + offset, entry.arg.end, end_len) == 0) + continue; + + if (mode == COLOR_WORD_INSIDE) { + // verify that only one word exists inside + int offset_tmp = offset; + while (offset_tmp < fb->len && isspace(buf[offset_tmp])) offset_tmp++; + while (offset_tmp < fb->len && !str_contains_char(cs.word_seperators, buf[offset_tmp])) offset_tmp++; + while (offset_tmp < fb->len && isspace(buf[offset_tmp])) offset_tmp++; + if (memcmp(buf + offset_tmp, entry.arg.end, end_len) != 0 + || offset_tmp - offset <= 1) + continue; + } + + end_condition = entry.arg.end; + end_condition_len = end_len; + global_attr = entry.attr; + around = 0; + if (entry.mode == COLOR_INSIDE_TO_LINE) + end_at_whitespace = 1; + return; + } + continue; + } + + // the rest of the conditions all check if the first string matches + if (buflen - offset <= len) + continue; + if (memcmp(buf + offset, entry.arg.start, len) == 0) { + if (mode == COLOR_AROUND || mode == COLOR_AROUND_TO_LINE) { + assert(entry.arg.end); + end_condition = entry.arg.end; + end_condition_len = strlen(entry.arg.end); + around = 1; + if (entry.mode == COLOR_AROUND_TO_LINE) + end_at_whitespace = 1; + } else if (mode == COLOR_WORD || mode == COLOR_STR_AFTER_WORD || + mode == COLOR_WORD_STR || mode == COLOR_WORD_STARTING_WITH_STR) { + + // check if this is the start of a new word that matches word exactly(except for WORD_STARTING_WITH_STR) + if ((offset > 0 && !str_contains_char(cs.word_seperators, buf[offset-1])) || + (buflen - (offset+len) > len && !str_contains_char(cs.word_seperators, buf[offset+len]) + && mode != COLOR_WORD_STARTING_WITH_STR)) + continue; + + if (mode == COLOR_WORD_STR) { + assert(entry.arg.end); + int offset_tmp = offset + len; + // move to next string + while (offset_tmp < fb->len && isspace(fb->contents[offset_tmp])) + offset_tmp++; + + int end_len = strlen(entry.arg.end); + if (offset_tmp + end_len >= fb->len || + memcmp(buf + offset_tmp, entry.arg.end, end_len) != 0) + continue; + end_condition_len = offset_tmp - offset; + } else { + end_at_whitespace = 1; + } + if (mode == COLOR_STR_AFTER_WORD) { + next_word_attr = entry.attr; + color_next_word = 1; + continue; + } + } else if (mode == COLOR_STR) { + end_condition_len = len; + } + + global_attr= entry.attr; + return; + } + } +} + + +int +write_string(const char* string, int y, int minx, int maxx) +{ + LIMIT(maxx, 0, term.col); + LIMIT(minx, 0, maxx-1); + + int offset = 0; + int len = strlen(string); + while(minx < maxx && offset < len) { + Rune u; + int charsize = t_decode_utf8_buffer(string + offset, len - offset, &u); + offset += charsize; + minx += tsetchar(u, minx, y); + } + return minx; +} + +int +str_contains_char(const char* string, char check) +{ + int len = strlen(string); + for (int i = 0; i < len; i++) + if (string[i] == check) + return 1; + return 0; +} + +void +color_selection(Glyph* letter) +{ + letter->bg = selection_bg; +} + + +int +buffer_is_selection_start_top_left(const struct file_buffer* buf) +{ + return (buf->s1o <= buf->s2o) ? 1 : 0; +} + +void +buffer_move_cursor_to_selection_start(struct window_buffer* buf) +{ + const struct file_buffer* fb = get_file_buffer(buf); + if (buffer_is_selection_start_top_left(fb)) + buffer_move_to_offset(buf, fb->s1o, CURSOR_SNAPPED); + else + buffer_move_to_offset(buf, fb->s2o, CURSOR_SNAPPED); +} + +void +buffer_write_selection(struct window_buffer* buf, int minx, int miny, int maxx, int maxy) +{ + assert(buf); + struct file_buffer* fb = get_file_buffer(buf); + + LIMIT(maxx, 0, term.col-1); + LIMIT(maxy, 0, term.row-1); + LIMIT(minx, 0, maxx-1); + LIMIT(miny, 0, maxy-1); + + //TODO: implement alternative selection modes + if (!(fb->mode & BUFFER_SELECTION_ON)) + return; + + int x, y, x2, y2; + if (buffer_is_selection_start_top_left(fb)) { + buffer_offset_to_xy(buf, fb->s1o, maxx - minx, &x, &y); + buffer_offset_to_xy(buf, fb->s2o, maxx - minx, &x2, &y2); + } else { + buffer_offset_to_xy(buf, fb->s2o, maxx - minx, &x, &y); + buffer_offset_to_xy(buf, fb->s1o, maxx - minx, &x2, &y2); + } + x += minx, x2 += minx + 1; + y += miny, y2 += miny; + + + for(; y < y2; y++) { + for(; x < maxx; x++) + color_selection(&term.line[y][x]); + x = 0; + } + for(; x < x2; x++) + color_selection(&term.line[y][x]); +} + +char* buffer_get_selection(struct file_buffer* buffer, int* selection_len) +{ + if (!(buffer->mode & BUFFER_SELECTION_ON)) + return NULL; + + int start, end, len; + if (buffer_is_selection_start_top_left(buffer)) { + start = buffer->s1o; + end = buffer->s2o+1; + } else { + start = buffer->s2o; + end = buffer->s1o+1; + } + len = end - start; + if (selection_len) + *selection_len = len; + + char* returned_selction = xmalloc(len + 1); + memcpy(returned_selction, buffer->contents+start, len); + returned_selction[len] = 0; + return returned_selction; +} + +void +buffer_remove_selection(struct file_buffer* buffer) +{ + if (!(buffer->mode & BUFFER_SELECTION_ON)) + return; + + int start, end, len; + if (buffer_is_selection_start_top_left(buffer)) { + start = buffer->s1o; + end = buffer->s2o+1; + } else { + start = buffer->s2o; + end = buffer->s1o+1; + } + len = end - start; + buffer_remove(buffer, start, len, 1, 1); + if (buffer_contents_updated) + buffer_contents_updated(buffer, start, BUFFER_CONTENT_BIG_CHANGE); +} + +void +buffer_write_to_filepath(const struct file_buffer* buffer) +{ + if (!buffer->file_path) + return; + assert(buffer->contents); + FILE* file = fopen(buffer->file_path, "w"); + assert(file); + + if (buffer->mode & BUFFER_UTF8_SIGNED) + fwrite("\xEF\xBB\xBF", 1, 3, file); + fwrite(buffer->contents, sizeof(char), buffer->len, file); + + fclose(file); +} + +int +t_decode_utf8_buffer(const char* buffer, const int buflen, Rune* u) +{ + if (!buflen) return 0; + + Rune u_tmp; + int charsize; + if (!u) + u = &u_tmp; + + /* process a complete utf8 char */ + charsize = utf8decode(buffer, u, buflen); + + return charsize; +} + +int +tsetchar(Rune u, int x, int y) +{ + Glyph attr = global_attr; + if (y >= term.row || x >= term.col || + y < 0 || x < 0) + return 1; + + int width = wcwidth(u); + if (width == -1) + width = 1; + else if (width > 1) + attr.mode |= ATTR_WIDE; + + if (term.line[y][x].mode & ATTR_WIDE || attr.mode & ATTR_WIDE) { + if (x+1 < term.col) { + term.line[y][x+1].u = 0; + term.line[y][x+1].mode |= ATTR_WDUMMY; + } + } else if (term.line[y][x].mode & ATTR_WDUMMY) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~ATTR_WIDE; + } + + term.line[y][x] = attr; + term.line[y][x].u = u; + + return width; +} + +void +tsetregion(int x1, int y1, int x2, int y2, Rune u) +{ + int x, y, temp; + + if (x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if (y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + assert(term.line); + assert(term.line[0]); + + for (y = y1; y <= y2; y++) { + for (x = x1; x <= x2; x++) { + term.line[y][x] = global_attr; + term.line[y][x].u = u; + } + } +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* resize to new height */ + if (row < term.row) + for (i = row; i < term.row; i++) + free(term.line[i]); + + term.line = xrealloc(term.line, row * sizeof(Line)); + + if (row > term.row) + for (i = term.row; i < row; i++) + term.line[i] = NULL; + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < row; i++) + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); + + /* update terminal size */ + term.col = col; + term.row = row; + + /* Clear screen */ + if (mincol < col && 0 < minrow) + tsetregion(mincol, 0, col - 1, minrow - 1, ' '); + if (0 < col && minrow < row) + tsetregion(0, minrow, col - 1, row - 1, ' '); +} + +int +is_file_type(const char* file_path, const char* file_type) +{ + int ftlen = strlen(file_type); + int offset = strlen(file_path) - ftlen; + if(offset > 0 && memcmp(file_path + offset, file_type, ftlen) == 0) + return 1; + return 0; +} diff --git a/se.h b/se.h @@ -0,0 +1,278 @@ +/* See LICENSE for license details. */ +#ifndef _ST_H +#define _ST_H + +#include <stdint.h> +#include <sys/types.h> +#include <wchar.h> +#include <limits.h> +#include <dirent.h> + +// Arbitrary sizes +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 +#define UNDO_BUFFERS_COUNT 32 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) + +//////////////////////////////////////////////// +// Glyphs +// + +enum glyph_attribute { + ATTR_NULL = 0, + ATTR_BOLD = 1 << 0, + ATTR_FAINT = 1 << 1, + ATTR_ITALIC = 1 << 2, + ATTR_UNDERLINE = 1 << 3, + ATTR_REVERSE = 1 << 5, + ATTR_INVISIBLE = 1 << 6, + ATTR_STRUCK = 1 << 7, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, +}; + +typedef unsigned char uchar; +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; + +typedef uint_least32_t Rune; + +typedef struct { + Rune u; // character code + ushort mode; // attribute flags + uint32_t fg; // foreground + uint32_t bg; // background +} Glyph_; +#define Glyph Glyph_ + +typedef Glyph *Line; + +//////////////////////////////////////////////// +// Window buffer +// + +enum window_buffer_mode { + WINDOW_BUFFER_NORMAL = 0, + WINDOW_BUFFER_FILE_BROWSER, +}; + +struct window_buffer { + int y_scroll; + int cursor_offset; + int cursor_col; + + int buffer_index; // index into an array storing file buffers + enum window_buffer_mode mode; +}; + +struct window_buffer window_buffer_new(int buffer_index); + +void buffer_write_selection(struct window_buffer* buf, int minx, int miny, int maxx, int maxy); +void buffer_move_cursor_to_selection_start(struct window_buffer* buffer); + +void buffer_offset_to_xy(struct window_buffer* buf, int offset, int maxx, int* cx, int* cy); +void buffer_draw_to_screen(struct window_buffer* buf, int minx, int miny, int maxx, int maxy); + +enum cursor_reason { + CURSOR_DO_NOT_CALLBACK = 0, + CURSOR_COMMAND_MOVEMENT = 1, + CURSOR_UP_DOWN_MOVEMENT, + CURSOR_RIGHT_LEFT_MOVEMENT, + CURSOR_SNAPPED, +}; + +void buffer_move_on_line(struct window_buffer* buf, int amount, enum cursor_reason callback_reason); +void buffer_move_lines(struct window_buffer* buf, int amount, enum cursor_reason callback_reason); +void buffer_move_to_offset(struct window_buffer* buf, int offset, enum cursor_reason callback_reason); +void buffer_move_offset_relative(struct window_buffer* buf, int amount, enum cursor_reason callback_reason); +void buffer_move_to_x(struct window_buffer* buf, int x, enum cursor_reason callback_reason); + +enum window_split_mode { + WINDOW_SINGULAR, + WINDOW_HORISONTAL, + WINDOW_VERTICAL, + WINDOW_FILE_BROWSER, +}; + +struct window_split_node { + struct window_buffer window; + enum window_split_mode mode; + float ratio; + struct window_split_node *node1, *node2, *parent; +}; + +void window_node_split(struct window_split_node* parent, float ratio, enum window_split_mode mode); +struct window_split_node* window_node_delete(struct window_split_node* node); +// uses focused_window to draw the cursor +void window_draw_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy); +void window_move_all_cursors_on_same_buf(struct window_split_node* root, struct window_split_node* excluded, int buf_index, int offset, void(movement)(struct window_buffer*, int, enum cursor_reason), int move, enum cursor_reason reason); +void window_move_all_yscrolls(struct window_split_node* root, struct window_split_node* excluded, int buf_index, int offset, int move); +int window_other_nodes_contain_file_buffer(struct window_split_node* node, struct window_split_node* root); + +enum move_directons { + MOVE_RIGHT, + MOVE_LEFT, + MOVE_UP, + MOVE_DOWN, +}; + +struct window_split_node* window_switch_to_window(struct window_split_node* node, enum move_directons move); +// NOTE: if you have two splits both having two splits of the same type, you can't resize the upper split +void window_node_resize(struct window_split_node* node, enum move_directons move); + +//////////////////////////////////////////////// +// Color Scheme +// + +struct color_scheme_arg { + const char* start; + const char* end; +}; + +enum color_scheme_mode { + // needs two strings + COLOR_AROUND, + // needs two strings + COLOR_AROUND_TO_LINE, + // needs two strings + COLOR_INSIDE, + // needs two strings + COLOR_INSIDE_TO_LINE, + // needs two strings + COLOR_WORD_INSIDE, + // needs one string + COLOR_WORD, + // needs one string + COLOR_WORD_ENDING_WITH_STR, + // needs one string + COLOR_WORD_STARTING_WITH_STR, + // needs one string + COLOR_STR, + // needs two strings + // colors word if string is found after it + COLOR_WORD_STR, + // needs one string + // can be combined with others if this is first + COLOR_STR_AFTER_WORD, + // needs one string + // "(" would color like this "not_colored colored(" + // "[" would color like this "not_colored colored [" + COLOR_WORD_BEFORE_STR, + // needs one string + // "(" would color like this "colored not_colored(" + // "=" would color like this "colored not_colored =" + COLOR_WORD_BEFORE_STR_STR, + // no arguments needed + COLOR_UPPER_CASE_WORD, +}; + +struct color_scheme_entry { + const enum color_scheme_mode mode; + const struct color_scheme_arg arg; + const Glyph attr; +}; + +struct color_scheme { + const char* file_ending; + const char* word_seperators; + const struct color_scheme_entry* entries; + const int entry_count; +}; + +//////////////////////////////////////////////// +// File buffer +// + +struct undo_buffer { + char* contents; // not null terminated + int len, capacity; + int cursor_offset; + int y_scroll; +}; + +// Contents of a file buffer +struct file_buffer { + char* file_path; + char* contents; // !! NOT NULL TERMINATED !! + int len; + int capacity; + int mode; // flags + struct undo_buffer* ub; + int current_undo_buffer; + int available_redo_buffers; + int s1o, s2o; // selection start offset and end offset +}; + +struct file_buffer buffer_new(const char* file_path); +void buffer_destroy(struct file_buffer* fb); +void buffer_insert(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback); +void buffer_change(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback); +int buffer_remove(struct file_buffer* buf, const int offset, int len, int do_not_calculate_charsize, int do_not_callback); +void buffer_write_to_filepath(const struct file_buffer* buffer); +int buffer_seek_char(const struct file_buffer* buf, int offset, char byte); +int buffer_seek_char_backwards(const struct file_buffer* buf, int offset, char byte); +int buffer_seek_string(const struct file_buffer* buf, int offset, const char* string); +int buffer_seek_string_backwards(const struct file_buffer* buf, int offset, const char* string); + +/////////////////////////////////// +// returns a null terminated string containing the selection +// the returned value must be freed by the reciever +// for conveniance the length of the string may be taken with the pointer +// a selection_len of NULL wil be ignored +char* buffer_get_selection(struct file_buffer* buf, int* selection_len); +int buffer_is_selection_start_top_left(const struct file_buffer* buffer); +void buffer_remove_selection(struct file_buffer* buffer); + +//////////////////////////////////////////////// +// Other +// + +void die(const char *, ...); +int is_file_type(const char* file_path, const char* file_type); +char* file_path_get_path(const char* path); +int path_is_folder(const char* path); +int file_browser_next_item(DIR* dir, const char* path, const char* search, char* full_path, char* filename); + +// Internal representation of the screen +typedef struct { + int row; /* nb row */ + int col; /* nb col */ + Line *line; /* screen */ + int ocx, ocy; // old cursor + Rune lastc; /* last printed char outside of sequence, 0 if control */ +} Term; + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetregion(int x1, int y1, int x2, int y2, Rune u); +int tsetchar(Rune u, int x, int y); + +size_t utf8encode(Rune, char *); +void *xmalloc(size_t); +void *xrealloc(void *, size_t); + +enum buffer_content_reason { + BUFFER_CONTENT_DO_NOT_CALLBACK, + BUFFER_CONTENT_OPERATION_ENDED, + BUFFER_CONTENT_NORMAL_EDIT, + BUFFER_CONTENT_BIG_CHANGE, + BUFFER_CONTENT_INIT, +}; + +enum buffer_flags { + BUFFER_SELECTION_ON = 1 << 0, + BUFFER_BLOCK_SELECT = 1 << 1, + BUFFER_LINE_SELECT = 1 << 2, + BUFFER_READ_ONLY = 1 << 3, + BUFFER_UTF8_SIGNED = 1 << 4, +}; + +#endif // _ST_H diff --git a/st.c b/st.c @@ -1,1001 +0,0 @@ -/* See LICENSE for license details. */ -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <stdarg.h> -#include <string.h> -#include <assert.h> -#include <stddef.h> - -#include "st.h" -#include "win.h" - -/* Arbitrary sizes */ -#define UTF_INVALID 0xFFFD -#define UTF_SIZ 4 - -Term term; -extern struct window_buffer* focused_window; -extern int cursor_x, cursor_y; - -static void colour_selection(Glyph* letter); - -static int t_decode_utf8_buffer(const char* buffer, const int buflen, Rune* u); - -static const uchar utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; -static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; -static size_t utf8decode(const char *, Rune *, size_t); -static Rune utf8decodebyte(char, size_t *); -static char utf8encodebyte(Rune, size_t); -static size_t utf8validate(Rune *, size_t); - -void * -xmalloc(size_t len) -{ - void *p; - - if (!(p = malloc(len))) - die("malloc: %s\n", strerror(errno)); - - return p; -} - -void * -xrealloc(void *p, size_t len) -{ - if ((p = realloc(p, len)) == NULL) - die("realloc: %s\n", strerror(errno)); - - return p; -} - -size_t -utf8decode(const char *c, Rune *u, size_t clen) -{ - size_t i, j, len, type; - Rune udecoded; - - *u = UTF_INVALID; - if (!clen) - return 0; - udecoded = utf8decodebyte(c[0], &len); - if (!BETWEEN(len, 1, UTF_SIZ)) - return 1; - for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { - udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); - if (type != 0) - return j; - } - if (j < len) - return 0; - *u = udecoded; - utf8validate(u, len); - - return len; -} - -Rune -utf8decodebyte(char c, size_t *i) -{ - for (*i = 0; *i < LEN(utfmask); ++(*i)) - if (((uchar)c & utfmask[*i]) == utfbyte[*i]) - return (uchar)c & ~utfmask[*i]; - return 0; -} - -size_t -utf8encode(Rune u, char *c) -{ - size_t len, i; - - len = utf8validate(&u, 0); - if (len > UTF_SIZ) - return 0; - - for (i = len - 1; i != 0; --i) { - c[i] = utf8encodebyte(u, 0); - u >>= 6; - } - c[0] = utf8encodebyte(u, len); - - return len; -} - -char -utf8encodebyte(Rune u, size_t i) -{ - return utfbyte[i] | (u & ~utfmask[i]); -} - -size_t -utf8validate(Rune *u, size_t i) -{ - const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; - const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; - - if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) - *u = UTF_INVALID; - for (i = 1; *u > utfmax[i]; ++i) - ; - - return i; -} - -void -die(const char *errstr, ...) -{ - va_list ap; - - va_start(ap, errstr); - vfprintf(stderr, errstr, ap); - va_end(ap); - exit(1); -} - -int -tattrset(int attr) -{ - for (int i = 0; i < term.row-1; i++) - for (int j = 0; j < term.col-1; j++) - if (term.line[i][j].mode & attr) - return 1; - return 0; -} - -void -tnew(int col, int row) -{ - term = (Term){0}; - tresize(col, row); - tsetregion(0, 0, term.col-1, term.row-1, ' '); -} - -int -buffer_seek_char(const struct file_buffer* buf, int offset, char byte) -{ - LIMIT(offset, 0, buf->len-1); - char* new_buf = memchr(buf->contents + offset, byte, buf->len - offset); - if (!new_buf) return -1; - return new_buf - buf->contents; -} -int -buffer_seek_char_backwards(const struct file_buffer* buf, int offset, char byte) -{ - LIMIT(offset, 0, buf->len-1); - for (int n = offset-1; n >= 0; n--) { - if (buf->contents[n] == byte) { - return n+1; - } - } - return -1; -} - -int -buffer_seek_string(const struct file_buffer* buf, int offset, const char* string) -{ - LIMIT(offset, 0, buf->len-1); - int str_len = strlen(string); - - for (int n = offset; n < buf->len - str_len; n++) { - if (!memcmp(buf->contents + n, string, str_len)) { - printf("n = %d\n", n); - return n; - } - } - return -1; -} -int -buffer_seek_string_backwards(const struct file_buffer* buf, int offset, const char* string) -{ - int str_len = strlen(string); - offset += str_len; - LIMIT(offset, 0, buf->len-1); - - for (int n = offset - str_len; n >= 0; n--) { - if (!memcmp(buf->contents + n, string, str_len)) { - printf("n = %d\n", n); - return n; - } - } - return -1; -} - -void -buffer_move_on_line(struct window_buffer* buf, int amount, enum cursor_reason callback_reason) -{ - const struct file_buffer* fb = get_file_buffer((buf)); - - if (amount < 0) { - /* - * we cant get the size of a utf8 char backwards - * therefore we move all the way to the start of the line, - * then a seeker will try to find the cursor pos - * the follower will then be *amount* steps behind, - * when the seeker reaches the cursor - * the follower will be the new cursor position - */ - - int line_start = buffer_seek_char_backwards(fb, buf->cursor_offset, '\n'); - amount = abs(MAX(line_start - buf->cursor_offset, amount)); - assert(amount < 2048); - - char moves[amount]; - int seek_pos = line_start, follower_pos = line_start; - int n = 0; - while (seek_pos < buf->cursor_offset) { - Rune u; - int charsize = t_decode_utf8_buffer(fb->contents + seek_pos, fb->len - seek_pos, &u); - seek_pos += charsize; - if (n < amount) { - moves[n++] = charsize; - } else { - follower_pos += moves[0]; - memmove(moves, moves + 1, amount - 1); - moves[amount - 1] = charsize; - } - } - buf->cursor_offset = follower_pos; - - LIMIT(buf->cursor_offset, 0, fb->len-1); - } else if (amount > 0) { - for (int charsize = 0; - buf->cursor_offset < fb->len && amount > 0 && fb->contents[buf->cursor_offset + charsize] != '\n'; - buf->cursor_offset += charsize, amount--) { - Rune u; - charsize = t_decode_utf8_buffer(fb->contents + buf->cursor_offset, fb->len - buf->cursor_offset, &u); - if (u != '\n' && u != '\t') - if (wcwidth(u) <= 0) - amount++; - if (buf->cursor_offset + charsize >= fb->len) - break; - } - } - - if (callback_reason && cursor_movement_callback) - cursor_movement_callback(buf, callback_reason); -} - -void -buffer_move_lines(struct window_buffer* buf, int amount, enum cursor_reason callback_reason) -{ - const struct file_buffer* fb = get_file_buffer((buf)); - int offset = buf->cursor_offset; - if (amount > 0) { - while (amount-- && offset >= 0) - offset = buffer_seek_char(fb, offset, '\n')+1; - if (offset < 0) - offset = fb->len-1; - } else if (amount < 0) { - while (amount++ && offset >= 0) - offset = buffer_seek_char_backwards(fb, offset, '\n')-1; - } - buffer_move_to_offset(buf, offset, callback_reason); -} - -void -buffer_move_to_offset(struct window_buffer* buf, int offset, enum cursor_reason callback_reason) -{ - const struct file_buffer* fb = get_file_buffer((buf)); - LIMIT(offset, 0, fb->len-1); - buf->cursor_offset = offset; - - if (callback_reason && cursor_movement_callback) - cursor_movement_callback(buf, callback_reason); -} - -void -buffer_move_to_x(struct window_buffer* buf, int x, enum cursor_reason callback_reason) -{ - assert(buf); - struct file_buffer* fb = get_file_buffer(buf); - - int offset = buffer_seek_char_backwards(fb, buf->cursor_offset, '\n'); - if (offset < 0) - offset = 0; - buffer_move_to_offset(buf, offset, 0); - - int x_counter = 0; - - while (offset < fb->len) { - if (fb->contents[offset] == '\t') { - offset++; - if (x_counter <= 0) x_counter += 1; - while (x_counter % tabspaces != 0) x_counter += 1; - x_counter += 1; - continue; - } else if (fb->contents[offset] == '\n') { - break; - } - Rune u = 0; - int charsize = t_decode_utf8_buffer(fb->contents + offset, fb->len - offset, &u); - x_counter += wcwidth(u); - if (x_counter <= x) { - offset += charsize; - if (x_counter == x) - break; - } else { - break; - } - } - buffer_move_to_offset(buf, offset, callback_reason); -} - -void -window_buffer_split(struct window_split_node* parent, float ratio, enum window_split_mode mode) -{ - assert(parent); - assert(parent->mode == WINDOW_SINGULAR); - assert(mode != WINDOW_SINGULAR); - - parent->node1 = xmalloc(sizeof(struct window_split_node)); - parent->node2 = xmalloc(sizeof(struct window_split_node)); - - *parent->node1 = *parent; - *parent->node2 = *parent; - parent->node1->parent = parent; - parent->node2->parent = parent; - - // set the new childrens children to NULL for good measure - parent->node1->node1 = NULL; - parent->node1->node2 = NULL; - parent->node2->node1 = NULL; - parent->node2->node2 = NULL; - - parent->mode = mode; - parent->ratio = ratio; - parent->window = (struct window_buffer){0}; -} - -void -window_write_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy) -{ - assert(root); - - if (root->mode == WINDOW_SINGULAR) { - buffer_write_to_screen(&root->window, minx, miny, maxx, maxy); - } else if (IS_HORISONTAL(root->mode)) { - int middlex = ((float)(maxx - minx) * root->ratio) + minx; - - // print seperator - tsetregion(middlex+1, miny, middlex+1, maxy, L'│'); - - window_write_tree_to_screen(root->node1, minx, miny, middlex, maxy); - window_write_tree_to_screen(root->node2, middlex+2, miny, maxx, maxy); - - // print connecting borders - if (middlex+1 >= term.col) - return; - if (miny-1 >= 0 && miny-1 < term.row) { - if (term.line[miny-1][middlex+1].u == L'┬' || - term.line[miny-1][middlex+1].u == L'┴' || - term.line[miny-1][middlex+1].u == L'├' || - term.line[miny-1][middlex+1].u == L'┤') - tsetchar(L'┼', middlex+1, miny-1); - if (term.line[miny-1][middlex+1].u == L'─') - tsetchar(L'┬', middlex+1, miny-1); - } - if (maxy+1 >= 0 && maxy+1 < term.row) { - if (term.line[maxy+1][middlex+1].u == L'┬' || - term.line[maxy+1][middlex+1].u == L'┴' || - term.line[maxy+1][middlex+1].u == L'├' || - term.line[maxy+1][middlex+1].u == L'┤') - tsetchar(L'┼', middlex+1, maxy+1); - if (term.line[maxy+1][middlex+1].u == L'─') - tsetchar(L'┴', middlex+1, maxy+1); - } - } else if (root->mode == WINDOW_VERTICAL) { - int middley = ((float)(maxy - miny) * root->ratio) + miny; - - // print seperator - tsetregion(minx, middley+1, maxx, middley+1, L'─'); - - window_write_tree_to_screen(root->node1, minx, miny, maxx, middley); - window_write_tree_to_screen(root->node2, minx, middley+2, maxx, maxy); - - // print connecting borders - if (middley+1 >= term.row) - return; - if (minx-1 >= 0 && minx-1 < term.col) { - if (term.line[middley+1][minx-1].u == L'┬' || - term.line[middley+1][minx-1].u == L'┴' || - term.line[middley+1][minx-1].u == L'├' || - term.line[middley+1][minx-1].u == L'┤') - tsetchar(L'┼', minx-1, middley+1); - if (term.line[middley+1][minx-1].u == L'│') - tsetchar(L'├', minx-1, middley+1); - } - if (maxx+1 >= 0 && maxx+1 < term.col) { - if (term.line[middley+1][maxx+1].u == L'┤') - if (term.line[middley+1][maxx+1].u == L'┬' || - term.line[middley+1][maxx+1].u == L'┴' || - term.line[middley+1][maxx+1].u == L'├' || - term.line[middley+1][maxx+1].u == L'┤') - tsetchar(L'┼', maxx+1, middley+1); - if (term.line[middley+1][maxx+1].u == L'│') - tsetchar(L'┤', maxx+1, middley+1); - } - } -} - -int -is_correct_mode(enum window_split_mode mode, enum move_directons move) -{ - if (move == MOVE_RIGHT || move == MOVE_LEFT) - return IS_HORISONTAL(mode); - if (move == MOVE_UP || move == MOVE_DOWN) - return (mode == WINDOW_VERTICAL); - return 0; -} - -struct window_split_node* -window_switch_to_window(struct window_split_node* current, enum move_directons move) -{ - assert(current); - if (!current->parent) return current; - assert(current->mode == WINDOW_SINGULAR); - struct window_split_node* old_current = current; - - if (move == MOVE_RIGHT || move == MOVE_DOWN) { - // traverse up the tree to the right - for (; current->parent; current = current->parent) { - if (is_correct_mode(current->parent->mode, move) && current->parent->node1 == current) { - // traverse down until a screen is found - current = current->parent->node2; - while(current->mode != WINDOW_SINGULAR) - current = current->node1; - - return current; - } - } - } else if (move == MOVE_LEFT || move == MOVE_UP) { - // traverse up the tree to the left - for (; current->parent; current = current->parent) { - if (is_correct_mode(current->parent->mode, move) && current->parent->node2 == current) { - // traverse down until a screen is found - current = current->parent->node1; - while(current->mode != WINDOW_SINGULAR) - current = current->node2; - - return current; - } - } - } - - return old_current; -} - -struct file_buffer -buffer_new(char* file_path) -{ - if (!file_path) { - die("creating new buffers not implemented\n"); - } - struct file_buffer buffer = {0}; - buffer.file_path = file_path; - - FILE *file = fopen(file_path, "rb"); - if (!file) { - fprintf(stderr, "---error reading file \"%s\"---\n", file_path); - die(""); - return buffer; - } - - fseek(file, 0L, SEEK_END); - long readsize = ftell(file); - rewind(file); - - assert(readsize); - - if (readsize > (long)1.048576e+7) { - fclose(file); - die("you are opening a huge file(>10MiB), not allowed"); - } - - buffer.len = readsize; - buffer.capacity = readsize + 100; - - buffer.contents = xmalloc(buffer.capacity); - - char bom[4] = {0}; - fread(bom, 1, 3, file); - if (strcmp(bom, "\xEF\xBB\xBF")) - rewind(file); - else - buffer.mode |= BUFFER_UTF8_SIGNED; - fread(buffer.contents, 1, readsize, file); - fclose(file); - - buffer.ub = xmalloc(sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); - memset(buffer.ub, 0, sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); - - if (buffer_contents_updated) - buffer_contents_updated(&buffer, 0, BUFFER_CONTENT_INIT); - - return buffer; -} - -void -buffer_insert(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback) -{ - assert(buf->contents); - if (offset > buf->len) - die("writing past buf %s\n", buf->file_path); - - if (buf->len + len >= buf->capacity) { - buf->capacity = buf->len + len + 256; - buf->contents = xrealloc(buf->contents, buf->capacity); - } - if (offset < buf->len) - memmove(buf->contents+offset+len, buf->contents+offset, buf->len-offset); - buf->len += len; - - memcpy(buf->contents+offset, new_content, len); - if (buffer_contents_updated && !do_not_callback) - buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); -} - -void -buffer_change(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback) -{ - assert(buf->contents); - if (offset > buf->len) - die("writing past buf %s\n", buf->file_path); - - if (offset + len > buf->len) { - buf->len = offset + len; - if (buf->len >= buf->capacity) { - buf->capacity = buf->len + len + 256; - buf->contents = xrealloc(buf->contents, buf->capacity); - } - } - - memcpy(buf->contents+offset, new_content, len); - if (buffer_contents_updated && !do_not_callback) - buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); -} - -void -buffer_remove(struct file_buffer* buf, const int offset, int len, int do_not_calculate_charsize, int do_not_callback) -{ - assert(buf->contents); - if (offset > buf->len) - die("deleting past buffer (offset is %d len is %d)\n", offset, buf->len); - - int removed_len = 0; - if (do_not_calculate_charsize) { - removed_len = len; - } else { - while (len--) { - int charsize = t_decode_utf8_buffer(buf->contents + offset, buf->len - offset, NULL); - if (buf->len - charsize < 0) - return; - removed_len += charsize; - } - } - buf->len -= removed_len; - memmove(buf->contents+offset, buf->contents+offset+removed_len, buf->len-offset); - if (buffer_contents_updated && !do_not_callback) - buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); -} - -int -buffer_get_charsize(Rune u, int cur_x_pos) -{ - if (u == '\t') - return 8 - (cur_x_pos % tabspaces); - if (u == '\n') - return 0; - return wcwidth(u); - -} - -void -buffer_offset_to_xy(struct window_buffer* buf, int offset, int maxx, int* cx, int* cy) -{ - assert(buf); - struct file_buffer* fb = get_file_buffer(buf); - - LIMIT(offset, 0, fb->len-1); - *cx = *cy = 0; - - char* repl = fb->contents; - char* last = repl + offset; - - char* new_repl; - if (WRAP_BUFFER && maxx > 0) { - int yscroll = 0; - while ((new_repl = memchr(repl, '\n', last - repl))) { - if (++yscroll >= buf->y_scroll) - break; - repl = new_repl+1; - } - *cy = yscroll - buf->y_scroll; - } else { - while ((new_repl = memchr(repl, '\n', last - repl))) { - repl = new_repl+1; - *cy += 1; - } - *cy -= buf->y_scroll; - } - - while (repl < last) { -#if WRAP_BUFFER - if (maxx > 0 && (*repl == '\n' || *cx >= maxx)) { - *cy += 1; - *cx = 0; - repl++; - continue; - } -#endif // WRAP_BUFFER - if (*repl == '\t') { - repl++; - if (*cx <= 0) *cx += 1; - while (*cx % tabspaces != 0) *cx += 1; - *cx += 1; - continue; - } - Rune u; - repl += t_decode_utf8_buffer(repl, last - repl, &u); - *cx += wcwidth(u); - } -} - -void -buffer_write_to_screen(struct window_buffer* buf, int minx, int miny, int maxx, int maxy) -{ - assert(buf); - struct file_buffer* fb = get_file_buffer(buf); - - LIMIT(maxx, 0, term.col-1); - LIMIT(maxy, 0, term.row-1); - LIMIT(minx, 0, maxx-1); - LIMIT(miny, 0, maxy-1); - - int x = minx, y = miny; - tsetregion(minx, miny, maxx, maxy, ' '); - - // force the screen in a place where the cursor is visable - int ox, oy; - buffer_offset_to_xy(buf, buf->cursor_offset, maxx - minx, &ox, &oy); - ox += minx - maxx; - int xscroll = 0; - if (ox > 0) - xscroll = ox; - if (oy < 0) { - buf->y_scroll += oy; - } else { - oy += miny - maxy; - if (oy > 0) - buf->y_scroll += oy; - } - if (buf->y_scroll < 0) - buf->y_scroll = 0; - - // move to y_scroll - char* repl = fb->contents; - char* last = repl + fb->len; - char* new_repl; - int line = buf->y_scroll; - while ((new_repl = memchr(repl, '\n', last - repl))) { - if (--line < 0) - break; - else if (new_repl+1 < last) - repl = new_repl+1; - else - return; - } - - // actually write to the screen - int once = 0; - for (int charsize = 1; repl < last && charsize; repl += charsize) { - // if the buffer being drawn is focused, set the cursor position global - if (!once && buf == focused_window && repl - fb->contents >= buf->cursor_offset) { - once = 1; - cursor_x = x; - cursor_y = y; - LIMIT(cursor_x, minx, maxx); - LIMIT(cursor_y, miny, maxy); - } - - -#if WRAP_BUFFER - xscroll = 0; - if (*repl == '\n' || x >= maxx) { -#else - if (x - xscroll > maxx) - repl = memchr(repl, '\n', last - repl); - - if (*repl == '\n') { -#endif // WRAP_BUFFER - - x = minx; - if (++y > maxy) - break; - -#if WRAP_BUFFER - if (*repl == '\n') { - charsize = 1; - continue; - } -#else - charsize = 1; - continue; -#endif // WRAP_BUFFER - - } else if (*repl == '\t') { - charsize = 1; - if (x <= 0) { - x += tsetchar(' ', x - xscroll, y); - if (x >= maxx) - continue; - } - while (x % tabspaces != 0 && x - xscroll <= maxx) - x += tsetchar(' ', x - xscroll, y); - - if (x - xscroll <= maxx) - x += tsetchar(' ', x, y); - continue; - } - - Rune u; - charsize = t_decode_utf8_buffer(repl, last - repl, &u); - int width; - if (x - xscroll >= minx) - width = tsetchar(u, x - xscroll, y); - else - width = wcwidth(u); - x += width; - } - - buffer_write_selection(buf, minx, miny, maxx, maxy); -} - -void -colour_selection(Glyph* letter) -{ - int fg = letter->fg; - letter->fg = letter->bg; - letter->bg = fg; -} - - -int -buffer_is_selection_start_top_left(const struct file_buffer* buf) -{ - return (buf->s1o <= buf->s2o) ? 1 : 0; -} - -void -buffer_move_cursor_to_selection_start(struct window_buffer* buf) -{ - const struct file_buffer* fb = get_file_buffer(buf); - if (buffer_is_selection_start_top_left(fb)) - buffer_move_to_offset(buf, fb->s1o, CURSOR_SNAPPED); - else - buffer_move_to_offset(buf, fb->s2o, CURSOR_SNAPPED); -} - -void -buffer_write_selection(struct window_buffer* buf, int minx, int miny, int maxx, int maxy) -{ - assert(buf); - struct file_buffer* fb = get_file_buffer(buf); - - LIMIT(maxx, 0, term.col-1); - LIMIT(maxy, 0, term.row-1); - LIMIT(minx, 0, maxx-1); - LIMIT(miny, 0, maxy-1); - - //TODO: implement alternative selection modes - if (!(fb->mode & BUFFER_SELECTION_ON)) - return; - - int x, y, x2, y2; - if (buffer_is_selection_start_top_left(fb)) { - buffer_offset_to_xy(buf, fb->s1o, maxx - minx, &x, &y); - buffer_offset_to_xy(buf, fb->s2o, maxx - minx, &x2, &y2); - } else { - buffer_offset_to_xy(buf, fb->s2o, maxx - minx, &x, &y); - buffer_offset_to_xy(buf, fb->s1o, maxx - minx, &x2, &y2); - } - x += minx, x2 += minx + 1; - y += miny, y2 += miny; - - - for(; y < y2; y++) { - for(; x < maxx; x++) - colour_selection(&term.line[y][x]); - x = 0; - } - for(; x < x2; x++) - colour_selection(&term.line[y][x]); -} - -char* buffer_get_selection(struct file_buffer* buffer, int* selection_len) -{ - if (!(buffer->mode & BUFFER_SELECTION_ON)) - return NULL; - - int start, end, len; - if (buffer_is_selection_start_top_left(buffer)) { - start = buffer->s1o; - end = buffer->s2o+1; - } else { - start = buffer->s2o; - end = buffer->s1o+1; - } - len = end - start; - if (selection_len) - *selection_len = len; - - char* returned_selction = xmalloc(len + 1); - memcpy(returned_selction, buffer->contents+start, len); - returned_selction[len] = 0; - return returned_selction; -} - -void -buffer_remove_selection(struct file_buffer* buffer) -{ - if (!(buffer->mode & BUFFER_SELECTION_ON)) - return; - - int start, end, len; - if (buffer_is_selection_start_top_left(buffer)) { - start = buffer->s1o; - end = buffer->s2o+1; - } else { - start = buffer->s2o; - end = buffer->s1o+1; - } - len = end - start; - buffer_remove(buffer, start, len, 1, 1); - if (buffer_contents_updated) - buffer_contents_updated(buffer, start, BUFFER_CONTENT_BIG_CHANGE); -} - -void -buffer_write_to_filepath(const struct file_buffer* buffer) -{ - if (!buffer->file_path) - return; - assert(buffer->contents); - FILE* file = fopen(buffer->file_path, "w"); - assert(file); - - if (buffer->mode & BUFFER_UTF8_SIGNED) - fwrite("\xEF\xBB\xBF", 1, 3, file); - fwrite(buffer->contents, sizeof(char), buffer->len, file); - - fclose(file); -} - -int -t_decode_utf8_buffer(const char* buffer, const int buflen, Rune* u) -{ - if (!buflen) return 0; - - Rune u_tmp; - int charsize; - if (!u) - u = &u_tmp; - - /* process a complete utf8 char */ - charsize = utf8decode(buffer, u, buflen); - - return charsize; -} - -int -tsetchar(Rune u, int x, int y) -{ - Glyph attr = default_attributes; - if (y >= term.row || x >= term.col || - y < 0 || x < 0) - return 1; - - int width = wcwidth(u); - if (width == -1) - width = 1; - else if (width > 1) - attr.mode |= ATTR_WIDE; - - if (term.line[y][x].mode & ATTR_WIDE || attr.mode & ATTR_WIDE) { - if (x+1 < term.col) { - term.line[y][x+1].u = 0; - term.line[y][x+1].mode |= ATTR_WDUMMY; - } - } else if (term.line[y][x].mode & ATTR_WDUMMY) { - term.line[y][x-1].u = ' '; - term.line[y][x-1].mode &= ~ATTR_WIDE; - } - - term.line[y][x] = attr; - term.line[y][x].u = u; - - return width; -} - -void -tsetregion(int x1, int y1, int x2, int y2, Rune u) -{ - int x, y, temp; - - if (x1 > x2) - temp = x1, x1 = x2, x2 = temp; - if (y1 > y2) - temp = y1, y1 = y2, y2 = temp; - - LIMIT(x1, 0, term.col-1); - LIMIT(x2, 0, term.col-1); - LIMIT(y1, 0, term.row-1); - LIMIT(y2, 0, term.row-1); - - for (y = y1; y <= y2; y++) { - for (x = x1; x <= x2; x++) { - term.line[y][x].fg = default_attributes.fg; - term.line[y][x].bg = default_attributes.bg; - term.line[y][x].mode = 0; - term.line[y][x].u = u; - } - } -} - -void -tresize(int col, int row) -{ - int i; - int minrow = MIN(row, term.row); - int mincol = MIN(col, term.col); - - if (col < 1 || row < 1) { - fprintf(stderr, - "tresize: error resizing to %dx%d\n", col, row); - return; - } - - /* resize to new height */ - if (row < term.row) - for (i = row; i < term.row; i++) - free(term.line[i]); - - term.line = xrealloc(term.line, row * sizeof(Line)); - - if (row > term.row) - for (i = term.row; i < row; i++) - term.line[i] = NULL; - - /* resize each row to new width, zero-pad if needed */ - for (i = 0; i < row; i++) - term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); - - /* update terminal size */ - term.col = col; - term.row = row; - - /* Clear screen */ - if (mincol < col && 0 < minrow) - tsetregion(mincol, 0, col - 1, minrow - 1, ' '); - if (0 < col && minrow < row) - tsetregion(0, minrow, col - 1, row - 1, ' '); -} - - -void -draw(int cursor_x, int cursor_y) -{ - LIMIT(cursor_x, 0, term.col-1); - LIMIT(cursor_y, 0, term.row-1); - - if (!xstartdraw()) - return; - if (term.line[cursor_y][cursor_x].mode & ATTR_WDUMMY) - cursor_x--; - - for (int y = 0; y < term.row; y++) - xdrawline(term.line[y], 0, y, term.col); - - xdrawcursor(cursor_x, cursor_y, term.line[cursor_y][cursor_x]); - - xfinishdraw(); -} diff --git a/st.h b/st.h @@ -1,213 +0,0 @@ -/* See LICENSE for license details. */ -#ifndef _ST_H -#define _ST_H - -#include <stdint.h> -#include <sys/types.h> -#include <wchar.h> - -/* macros */ -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) < (b) ? (b) : (a)) -#define LEN(a) (sizeof(a) / sizeof(a)[0]) -#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) -#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) -#define DEFAULT(a, b) (a) = (a) ? (a) : (b) -#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) -#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ - (a).bg != (b).bg) -#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) - -#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) -#define IS_TRUECOL(x) (1 << 24 & (x)) - -enum glyph_attribute { - ATTR_NULL = 0, - ATTR_BOLD = 1 << 0, - ATTR_FAINT = 1 << 1, - ATTR_ITALIC = 1 << 2, - ATTR_UNDERLINE = 1 << 3, - ATTR_REVERSE = 1 << 5, - ATTR_INVISIBLE = 1 << 6, - ATTR_STRUCK = 1 << 7, - ATTR_WRAP = 1 << 8, - ATTR_WIDE = 1 << 9, - ATTR_WDUMMY = 1 << 10, - ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, -}; - -typedef unsigned char uchar; -typedef unsigned int uint; -typedef unsigned long ulong; -typedef unsigned short ushort; - -typedef uint_least32_t Rune; - -typedef struct { - Rune u; /* character code */ - ushort mode; /* attribute flags */ - uint32_t fg; /* foreground */ - uint32_t bg; /* background */ -} Glyph_; -#define Glyph Glyph_ - -typedef Glyph *Line; - -typedef union { - int i; - uint ui; - float f; - int vec2i[2]; - const void *v; - const char *s; -} Arg; - -struct undo_buffer { - char* contents; // not null terminated - int len, capacity; - int cursor_offset; - int y_scroll; -}; - -struct window_buffer { - int y_scroll; - int cursor_offset; - int cursor_col; // last right_left movement for snapping the cursor - - int buffer_index; // index into an array storing buffers -}; - -enum window_split_mode { - WINDOW_SINGULAR, - WINDOW_HORISONTAL, - WINDOW_VERTICAL, - WINDOW_HORISONTAL_NOT_RESISABLE, -}; -#define IS_HORISONTAL(mode) ((mode) == WINDOW_HORISONTAL || (mode) == WINDOW_HORISONTAL_NOT_RESISABLE) - -struct window_split_node { - struct window_buffer window; - enum window_split_mode mode; - float ratio; - struct window_split_node *node1, *node2, *parent; -}; - -void window_buffer_split(struct window_split_node* parent, float ratio, enum window_split_mode mode); -// uses focused_window to draw the cursor -void window_write_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy); - -enum move_directons { - MOVE_RIGHT, - MOVE_LEFT, - MOVE_UP, - MOVE_DOWN, -}; -struct window_split_node* window_switch_to_window(struct window_split_node* current, enum move_directons move); - - -enum buffer_flags { - BUFFER_SELECTION_ON = 1 << 0, - BUFFER_BLOCK_SELECT = 1 << 1, - BUFFER_READ_ONLY = 1 << 2, - BUFFER_UTF8_SIGNED = 1 << 3, -}; - -/* Contents of a file buffer */ -struct file_buffer { - char* file_path; - char* contents; // !! NOT NULL TERMINATED !! - int len; - int capacity; - int mode; // flags - struct undo_buffer* ub; - int current_undo_buffer; - int available_redo_buffers; - int s1o, s2o; // selection start offset and end offset - //TODO: colour instructions -}; - -struct file_buffer buffer_new(char* file_path); -void buffer_insert(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback); -void buffer_change(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback); -void buffer_remove(struct file_buffer* buf, const int offset, int len, int do_not_calculate_charsize, int do_not_callback); -void buffer_offset_to_xy(struct window_buffer* buf, int offset, int maxx, int* cx, int* cy); -void buffer_write_to_screen(struct window_buffer* buf, int minx, int miny, int maxx, int maxy); -void buffer_write_to_filepath(const struct file_buffer* buffer); - -void buffer_write_selection(struct window_buffer* buf, int minx, int miny, int maxx, int maxy); -/////////////////////////////////// -// returns a null terminated string containing the selection -// the returned value must be freed by the reciever -// for conveniance the length of the string may be taken with the pointer -// a selection_len of NULL wil be ignored -char* buffer_get_selection(struct file_buffer* buf, int* selection_len); -int buffer_is_selection_start_top_left(const struct file_buffer* buffer); -void buffer_move_cursor_to_selection_start(struct window_buffer* buffer); -void buffer_remove_selection(struct file_buffer* buffer); - -void die(const char *, ...); - -void draw(int cursor_x, int cursor_y); - -/* Internal representation of the screen */ -typedef struct { - int row; /* nb row */ - int col; /* nb col */ - Line *line; /* screen */ - int ocx, ocy; // old cursor - Rune lastc; /* last printed char outside of sequence, 0 if control */ -} Term; -extern Term term; - -int tattrset(int); -void tnew(int, int); -void tresize(int, int); -void tsetregion(int x1, int y1, int x2, int y2, Rune u); -int tsetchar(Rune u, int x, int y); - -size_t utf8encode(Rune, char *); -void *xmalloc(size_t); -void *xrealloc(void *, size_t); - -enum cursor_reason { - CURSOR_DO_NOT_CALLBACK = 0, - CURSOR_COMMAND_MOVEMENT = 1, - CURSOR_UP_DOWN_MOVEMENT, - CURSOR_RIGHT_LEFT_MOVEMENT, - CURSOR_SNAPPED, - CURSOR_WINDOW_RESIZED, - CURSOR_SCROLL_ONLY, -}; - -enum buffer_content_reason { - BUFFER_CONTENT_DO_NOT_CALLBACK, - BUFFER_CONTENT_OPERATION_ENDED, - BUFFER_CONTENT_NORMAL_EDIT, - BUFFER_CONTENT_BIG_CHANGE, - BUFFER_CONTENT_INIT, -}; - -void buffer_move_on_line(struct window_buffer* buf, int amount, enum cursor_reason callback_reason); -void buffer_move_lines(struct window_buffer* buf, int amount, enum cursor_reason callback_reason); -void buffer_move_to_offset(struct window_buffer* buf, int offset, enum cursor_reason callback_reason); -void buffer_move_to_x(struct window_buffer* buf, int x, enum cursor_reason callback_reason); -int buffer_seek_char(const struct file_buffer* buf, int offset, char byte); -int buffer_seek_char_backwards(const struct file_buffer* buf, int offset, char byte); -int buffer_seek_string(const struct file_buffer* buf, int offset, const char* string); -int buffer_seek_string_backwards(const struct file_buffer* buf, int offset, const char* string); - -/* callbacks */ -extern void(*cursor_movement_callback)(struct window_buffer*, enum cursor_reason); // buffer, current_pos, reason -extern void(*buffer_contents_updated)(struct file_buffer*, int, enum buffer_content_reason); // modified buffer, current_pos -// TODO: planned callbacks: -// buffer written - -/* config.h globals */ -extern Glyph default_attributes; -extern char *termname; -extern unsigned int tabspaces; - -#define UNDO_BUFFERS_COUNT 32 -#define WRAP_BUFFER 0 - -#endif // _ST_H diff --git a/win.h b/win.h @@ -1,35 +0,0 @@ -/* See LICENSE for license details. */ -#ifndef _WIN_H -#define _WIN_H - -#include "st.h" - -enum win_mode { - MODE_VISIBLE = 1 << 0, - MODE_FOCUSED = 1 << 1, - MODE_APPKEYPAD = 1 << 2, - MODE_KBDLOCK = 1 << 6, - MODE_HIDE = 1 << 7, - MODE_APPCURSOR = 1 << 8, - MODE_MOUSESGR = 1 << 9, - MODE_BLINK = 1 << 11, - MODE_FBLINK = 1 << 12, - MODE_BRCKTPASTE = 1 << 16, - MODE_NUMLOCK = 1 << 17, -}; - -void xclipcopy(void); -void xdrawcursor(int, int, Glyph); -void xdrawline(Line, int, int, int); -void xfinishdraw(void); -void xloadcols(void); -int xsetcolorname(int, const char *); -void xseticontitle(char *); -int xsetcursor(int); -void xsetpointermotion(int); -int xstartdraw(void); - -struct file_buffer* get_file_buffer(struct window_buffer* buf); -int new_file_buffer(struct file_buffer buf); - -#endif // _WIN_H diff --git a/x.c b/x.c @@ -1,103 +1,81 @@ /* See LICENSE for license details. */ + +/* +** This file mainly contains X11 stuff (drawing to the screen, window hints, etc) +** Most of that part is unchanged from ST (https://st.suckless.org/) +** the main() function and the main loop are found at the very bottom of this file +** there are a very few functions here that are interresting for configuratinos. +** I would suggest looking into x.h and seeing if any of the callbacks fit +** your configuration needs. +*/ + #include <assert.h> #include <errno.h> #include <math.h> #include <locale.h> #include <stdio.h> -#include <stdlib.h> #include <time.h> - #include <unistd.h> -#include <X11/Xatom.h> -#include <X11/Xlib.h> -#include <X11/cursorfont.h> -#include <X11/keysym.h> -#include <X11/Xft/Xft.h> -#include <X11/XKBlib.h> - -#include "win.h" - -/* config.h for applying patches and the configuration. */ -#include "config.h" - -#define Glyph Glyph_ - -static void keep_cursor_col(struct window_buffer* buf, enum cursor_reason callback_reason); -static void move_selection(struct window_buffer* buf, enum cursor_reason callback_reason); - -static void add_to_undo_buffer(struct file_buffer* buffer, int offset, enum buffer_content_reason reason); - -/* XEMBED messages */ +#include <dirent.h> + +#include "x.h" + +///////////////////////////////////////////////////// +// config.c variables that must be defined +// + +extern int border_px; +extern float cw_scale; +extern float ch_scale; +extern char* fontconfig; +extern const char* const colors[]; +extern Glyph default_attributes; +extern unsigned int cursor_fg; +extern unsigned int cursor_bg; +extern unsigned int cursor_thickness; +extern unsigned int cursor_shape; +extern unsigned int default_cols; +extern unsigned int default_rows; + +// callbacks +extern void(*draw_callback)(void); +extern void(*buffer_contents_updated)(struct file_buffer* modified_fb, int current_pos, enum buffer_content_reason reason); +// non-zero return value means the kpress function will not proceed further +extern int(*keypress_callback)(KeySym keycode, int modkey); +extern void(*string_input_callback)(const char* buf, int len); +// TODO: planned callbacks: +// buffer focused +// window focused + +////////////////////////////////// +// macros +// + +// X modifiers +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +// XEMBED messages #define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_OUT 5 -/* macros */ #define IS_SET(flag) ((win.mode & (flag)) != 0) #define TRUERED(x) (((x) & 0xff0000) >> 8) #define TRUEGREEN(x) (((x) & 0xff00)) #define TRUEBLUE(x) (((x) & 0xff) << 8) - -typedef XftDraw *Draw; -typedef XftColor Color; -typedef XftGlyphFontSpec GlyphFontSpec; - -/* Purely graphic info */ -typedef struct { - int tw, th; /* tty width and height */ - int w, h; /* window width and height */ - int ch; /* char height */ - int cw; /* char width */ - int mode; /* window state/mode flags */ - int cursor; /* cursor style */ -} TermWindow; - -typedef struct { - Display *dpy; - Colormap cmap; - Window win; - Drawable buf; - GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ - Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; - struct { - XIM xim; - XIC xic; - XPoint spot; - XVaNestedList spotlist; - } ime; - Draw draw; - Visual *vis; - XSetWindowAttributes attrs; - int scr; - int isfixed; /* is fixed geometry? */ - int l, t; /* left and top offset */ - int gm; /* geometry mask */ -} XWindow; - -/* Font structure */ -#define Font Font_ -typedef struct { - int height; - int width; - int ascent; - int descent; - int badslant; - int badweight; - short lbearing; - short rbearing; - XftFont *match; - FcFontSet *set; - FcPattern *pattern; -} Font; - -/* Drawing Context */ -typedef struct { - Color *col; - size_t collen; - Font font, bfont, ifont, ibfont; - GC gc; -} DC; - -static inline ushort sixd_to_16bit(int); +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) +#define IS_TRUECOL(x) (1 << 24 & (x)) +#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + +//////////////////////////////////////// +// Internal Functions +// + +static int file_browser_actions(KeySym keysym, int modkey); +static void file_browser_string_insert(const char* buf, int buflen); static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyph(Glyph, int, int); @@ -108,17 +86,17 @@ static void ximinstantiate(Display *, XPointer, XPointer); static void ximdestroy(XIM, XPointer, XPointer); static int xicdestroy(XIC, XPointer, XPointer); static void xinit(int, int); -static void cresize(int, int); static void xresize(int, int); -static void xhints(void); static int xloadcolor(int, const char *, Color *); static int xloadfont(Font *, FcPattern *); -static void xloadfonts(const char *, double); -static void xunloadfont(Font *); -static void xunloadfonts(void); static void xsetenv(void); static void xseturgency(int); static void xsettitle(char *); +static void run(void); + +/////////////////////////////////////////////////// +// X11 events +// static void expose(XEvent *); static void visibility(XEvent *); @@ -130,10 +108,6 @@ static void kpress(XEvent *); static void cmessage(XEvent *); static void resize(XEvent *); static void focus(XEvent *); -static int match(uint, uint); -static void buffer_copy_ub_to_current(struct window_buffer* buffer); - -static void run(void); static void (*handler[LASTEvent])(XEvent *) = { [KeyPress] = kpress, @@ -149,216 +123,132 @@ static void (*handler[LASTEvent])(XEvent *) = { [SelectionRequest] = selrequest, }; -/* Globals */ -static struct window_split_node root_node = {.mode = WINDOW_SINGULAR}; -static struct window_split_node* focused_node = &root_node; - -// cursor is set when calling buffer_write_to_screen and the buffer is focused_window -struct window_buffer* focused_window = {0}; -int cursor_x, cursor_y; +//////////////////////////////////////////////// +// Globals +// +extern Term term; static struct file_buffer* file_buffers; -static int available_buffer_slots; -static DC dc; -static XWindow xw; -static Atom xtarget; -static char* copy_buffer; -static int copy_len; -static TermWindow win; - -/* Font Ring Cache */ -enum { - FRC_NORMAL, - FRC_ITALIC, - FRC_BOLD, - FRC_ITALICBOLD -}; - -typedef struct { - XftFont *font; - int flags; - Rune unicodep; -} Fontcache; - -/* Fontcache is an array now. A new font will be appended to the array. */ -static Fontcache *frc = NULL; -static int frclen = 0; -static int frccap = 0; -static char *usedfont = NULL; -static double usedfontsize = 0; -static double defaultfontsize = 0; - -static char *opt_class = NULL; -static char **opt_cmd = NULL; -static char *opt_embed = NULL; -static char *opt_font = NULL; -static char *opt_line = NULL; -static char *opt_name = NULL; -static char *opt_title = NULL; +static int available_buffer_slots = 0; +struct window_split_node root_node = {.mode = WINDOW_SINGULAR}; -void -numlock(const Arg *dummy) -{ - win.mode ^= MODE_NUMLOCK; -} - -void window_split(const Arg *arg) -{ - window_buffer_split(focused_node, 0.5, arg->i); - focused_node = focused_node->node2; - focused_window = &focused_node->window; -} - -void window_move(const Arg *arg) -{ - struct window_split_node* new_node = window_switch_to_window(focused_node, arg->i); - if (new_node->mode == WINDOW_SINGULAR) { - focused_node = new_node; - focused_window = &focused_node->window; - } -} - -void -zoom(const Arg *arg) -{ - Arg larg; - - larg.f = usedfontsize + arg->f; - zoomabs(&larg); -} +// cursor is set when calling buffer_write_to_screen and the buffer is focused_window +struct window_split_node* focused_node = &root_node; +struct window_buffer* focused_window = &root_node.window; + +Atom xtarget; +char* copy_buffer; +int copy_len; +TermWindow win; +DC dc; +XWindow xw; + +// Fontcache is an array. A new font will be appended to the array. +int frccap = 0; +Fontcache *frc = NULL; +int frclen = 0; +double defaultfontsize = 0; +double usedfontsize = 0; + +///////////////////////////////////////////////// +// function implementations +// -void -zoomabs(const Arg *arg) +struct file_buffer* +get_file_buffer(struct window_buffer* buf) { - xunloadfonts(); - xloadfonts(usedfont, arg->f); - cresize(0, 0); - xhints(); -} + assert(buf); + assert(file_buffers); -void -zoomreset(const Arg *arg) -{ - Arg larg; + if (buf->buffer_index < 0) + buf->buffer_index = available_buffer_slots-1; + else if (buf->buffer_index >= available_buffer_slots) + buf->buffer_index = 0; - if (defaultfontsize > 0) { - larg.f = defaultfontsize; - zoomabs(&larg); + if (!file_buffers[buf->buffer_index].contents) { + for(int n = 0; n < available_buffer_slots; n++) { + if (file_buffers[n].contents) { + buf->buffer_index = n; + assert(file_buffers[n].contents); + return &file_buffers[n]; + } + } + } else { + assert(file_buffers[buf->buffer_index].contents); + return &file_buffers[buf->buffer_index]; } -} - -void -cursor_move_x_relative(const Arg* arg) -{ - buffer_move_on_line(focused_window, arg->i, CURSOR_RIGHT_LEFT_MOVEMENT); -} -void -cursor_move_y_relative(const Arg* arg) -{ - buffer_move_lines(focused_window, arg->i, 0); - buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + buf->buffer_index = new_file_buffer_entry(NULL); + return get_file_buffer(buf); } -void -save_buffer(const Arg* arg) -{ - buffer_write_to_filepath(get_file_buffer(focused_window)); -} +int +new_file_buffer_entry(const char* file_path) +{ + static char full_path[PATH_MAX]; + if (!file_path) + file_path = "./"; + assert(strlen(file_path) < PATH_MAX); + + char* res = realpath(file_path, full_path); + if (available_buffer_slots) { + if (res) { + for(int n = 0; n < available_buffer_slots; n++) + if (file_buffers[n].contents) + if (strcmp(file_buffers[n].file_path, full_path) == 0) + return n; + } else { + strcpy(full_path, file_path); + } -void -toggle_selection(const Arg* arg) -{ - struct file_buffer* fb = get_file_buffer(focused_window); - if (fb->mode & BUFFER_SELECTION_ON) { - fb->mode &= ~(BUFFER_SELECTION_ON); - } else { - fb->mode |= BUFFER_SELECTION_ON; - fb->s1o = fb->s2o = focused_window->cursor_offset; + for(int n = 0; n < available_buffer_slots; n++) { + if (!file_buffers[n].contents) { + file_buffers[n] = buffer_new(full_path); + return n; + } + } } -} - -void -move_cursor_to_offset(const Arg* arg) -{ - focused_window->cursor_offset = arg->i; -} -void -move_cursor_to_end_of_buffer(const Arg* arg) -{ - focused_window->cursor_offset = get_file_buffer(focused_window)->len-1; + available_buffer_slots++; + file_buffers = xrealloc(file_buffers, sizeof(struct file_buffer) * available_buffer_slots); + file_buffers[available_buffer_slots-1] = buffer_new(full_path); + return available_buffer_slots-1; } void -clipboard_copy(const Arg* arg) +destroy_file_buffer_entry(struct window_split_node* node, struct window_split_node* root) { - struct file_buffer* fb = get_file_buffer(focused_window); - int len; - char* temp = buffer_get_selection(fb, &len); - if (!temp) + // do not allow deletion of the lst file buffer + int n = 0; + for(; n < available_buffer_slots; n++) + if (file_buffers[n].contents && n != node->window.buffer_index) + break; + if (n >= available_buffer_slots) return; - if (copy_buffer) - free(copy_buffer); - copy_buffer = temp; - copy_len = len; - - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~BUFFER_SELECTION_ON; - - Atom clipboard; - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); -} -void -clipboard_paste(const Arg* arg) -{ - Atom clipboard; - - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XConvertSelection(xw.dpy, clipboard, xtarget, clipboard, - xw.win, CurrentTime); -} - -void -buffer_copy_ub_to_current(struct window_buffer* buffer) -{ - struct file_buffer* fb = get_file_buffer(buffer); - struct undo_buffer* cub = &fb->ub[fb->current_undo_buffer]; - assert(cub->contents); - - fb->contents = xrealloc(fb->contents, cub->capacity); - memcpy(fb->contents, cub->contents, cub->capacity); - fb->len = cub->len; - fb->capacity = cub->capacity; - - buffer_move_to_offset(buffer, cub->cursor_offset, CURSOR_SNAPPED); - buffer->y_scroll = cub->y_scroll; -} - -void -undo(const Arg* arg) -{ - struct file_buffer* fb = get_file_buffer(focused_window); - if (fb->current_undo_buffer == 0) + if (window_other_nodes_contain_file_buffer(node, root)) { + node->window.buffer_index++; + node->window = window_buffer_new(node->window.buffer_index); return; - fb->current_undo_buffer--; - fb->available_redo_buffers++; + } + buffer_destroy(get_file_buffer(&node->window)); - buffer_copy_ub_to_current(focused_window); + if (node->window.mode == WINDOW_BUFFER_FILE_BROWSER) + node->window = window_buffer_new(node->window.cursor_col); + else + node->window = window_buffer_new(node->window.buffer_index); } -void -redo(const Arg* arg) +int +delete_selection(struct file_buffer* buf) { - struct file_buffer* fb = get_file_buffer(focused_window); - if (fb->available_redo_buffers == 0) - return; - fb->available_redo_buffers--; - fb->current_undo_buffer++; - - buffer_copy_ub_to_current(focused_window); + if (buf->mode & BUFFER_SELECTION_ON) { + buffer_remove_selection(buf); + buffer_move_cursor_to_selection_start(focused_window); + buf->mode &= ~(BUFFER_SELECTION_ON); + return 1; + } + return 0; } void @@ -425,14 +315,12 @@ selnotify(XEvent *e) XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); - /* - * Deleting the property is the transfer start signal. - */ + // Deleting the property is the transfer start signal. XDeleteProperty(xw.dpy, xw.win, (int)property); continue; } - // replace all '\r' with '\n'. + // replace all '\r' with '\n'. repl = data; last = data + nitems * format / 8; while ((repl = memchr(repl, '\r', last - repl))) { @@ -480,12 +368,12 @@ selrequest(XEvent *e) if (xsre->property == None) xsre->property = xsre->target; - /* reject */ + // reject xev.property = None; xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); if (xsre->target == xa_targets) { - /* respond with the supported type */ + // respond with the supported type string = xtarget; XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, @@ -520,130 +408,12 @@ selrequest(XEvent *e) } } - /* all done, send a notification to the listener */ + // all done, send a notification to the listener if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) fprintf(stderr, "Error sending SelectionNotify event\n"); } void -keep_cursor_col(struct window_buffer* buf, enum cursor_reason callback_reason) -{ - static int last_cursor_col; - - if (callback_reason == CURSOR_COMMAND_MOVEMENT || callback_reason == CURSOR_RIGHT_LEFT_MOVEMENT) { - int y; - buffer_offset_to_xy(buf, buf->cursor_offset, -1, &buf->cursor_col, &y); - last_cursor_col = buf->cursor_col; - } else if (callback_reason == CURSOR_WINDOW_RESIZED) { - int x, y; - buffer_offset_to_xy(buf, buf->cursor_offset, -1, &x, &y); - if (last_cursor_col != x) - buf->cursor_col = x; - } -} - -void move_selection(struct window_buffer* buf, enum cursor_reason callback_reason) -{ - struct file_buffer* fb = get_file_buffer(buf); - if (fb->mode & BUFFER_SELECTION_ON) { - fb->s2o = buf->cursor_offset; - } -} - -void -cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason) -{ - keep_cursor_col(buf, callback_reason); - move_selection(buf, callback_reason); - - printf("mved to: %d | reason: %d\n", buf->cursor_offset, callback_reason); -} - -void -add_to_undo_buffer(struct file_buffer* buffer, int offset, enum buffer_content_reason reason) -{ - static time_t last_normal_edit; - static int edits; - - if (reason == BUFFER_CONTENT_NORMAL_EDIT) { - time_t previous_time = last_normal_edit; - last_normal_edit = time(NULL); - - if (last_normal_edit - previous_time < 2 && edits < 30) { - edits++; - goto copy_undo_buffer; - } else { - edits = 0; - } - } else if (reason == BUFFER_CONTENT_INIT) { - goto copy_undo_buffer; - } - - if (buffer->available_redo_buffers > 0) { - buffer->available_redo_buffers = 0; - buffer->current_undo_buffer++; - goto copy_undo_buffer; - } - - if (buffer->current_undo_buffer == UNDO_BUFFERS_COUNT-1) { - char* begin_buffer = buffer->ub[0].contents; - memmove(buffer->ub, &(buffer->ub[1]), (UNDO_BUFFERS_COUNT-1) * sizeof(struct undo_buffer)); - buffer->ub[buffer->current_undo_buffer].contents = begin_buffer; - } else { - buffer->current_undo_buffer++; - } - -copy_undo_buffer: ; - struct undo_buffer* cub = &buffer->ub[buffer->current_undo_buffer]; - - cub->contents = xrealloc(cub->contents, buffer->capacity); - memcpy(cub->contents, buffer->contents, buffer->capacity); - cub->len = buffer->len; - cub->capacity = buffer->capacity; - cub->cursor_offset = offset; - if (focused_window) - cub->y_scroll = focused_window->y_scroll; - else - cub->y_scroll = 0; -} - -void -buffer_content_callback(struct file_buffer* buffer, int offset, enum buffer_content_reason reason) -{ - add_to_undo_buffer(buffer, offset, reason); -} - -struct file_buffer* -get_file_buffer(struct window_buffer* buf) -{ - assert(file_buffers); - - if (buf->buffer_index < 0) - buf->buffer_index = available_buffer_slots-1; - else if (buf->buffer_index >= available_buffer_slots) - buf->buffer_index = 0; - - return (file_buffers[buf->buffer_index].contents) ? &(file_buffers[buf->buffer_index]) : NULL; -} - -int -new_file_buffer(struct file_buffer buf) -{ - assert(buf.contents); - for(int n = 0; n < available_buffer_slots; n++) { - if (!file_buffers[n].contents) { - file_buffers[n] = buf; - return n; - } - } - - available_buffer_slots++; - file_buffers = xrealloc(file_buffers, sizeof(struct file_buffer) * available_buffer_slots); - file_buffers[available_buffer_slots-1] = buf; - return available_buffer_slots-1; -} - -void cresize(int width, int height) { int col, row; @@ -653,8 +423,8 @@ cresize(int width, int height) if (height != 0) win.h = height; - col = (win.w - 2 * borderpx) / win.cw; - row = (win.h - 2 * borderpx) / win.ch; + col = (win.w - 2 * border_px) / win.cw; + row = (win.h - 2 * border_px) / win.ch; col = MAX(1, col); row = MAX(1, row); @@ -674,35 +444,16 @@ xresize(int col, int row) XftDrawChange(xw.draw, xw.buf); xclear(0, 0, win.w, win.h); - /* resize to new width */ + // resize to new width xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); } -ushort -sixd_to_16bit(int x) -{ - return x == 0 ? 0 : 0x3737 + 0x2828 * x; -} - int xloadcolor(int i, const char *name, Color *ncolor) { - XRenderColor color = { .alpha = 0xffff }; - if (!name) { - if (BETWEEN(i, 16, 255)) { /* 256 color */ - if (i < 6*6*6+16) { /* same colors as xterm */ - color.red = sixd_to_16bit( ((i-16)/36)%6 ); - color.green = sixd_to_16bit( ((i-16)/6) %6 ); - color.blue = sixd_to_16bit( ((i-16)/1) %6 ); - } else { /* greyscale */ - color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); - color.green = color.blue = color.red; - } - return XftColorAllocValue(xw.dpy, xw.vis, - xw.cmap, &color, ncolor); - } else - name = colorname[i]; + if (!(name = colors[i])) + return 0; } return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); @@ -719,18 +470,20 @@ xloadcols(void) for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); } else { - dc.collen = MAX(LEN(colorname), 256); + i = 0; + while (colors[i++]) + ; + dc.collen = i; dc.col = xmalloc(dc.collen * sizeof(Color)); + loaded = 1; } - for (i = 0; i < dc.collen; i++) + for (i = 0; i < dc.collen; i++) { if (!xloadcolor(i, NULL, &dc.col[i])) { - if (colorname[i]) - die("could not allocate color '%s'\n", colorname[i]); - else - die("could not allocate color %d\n", i); + if (colors[i]) + die("could not allocate color '%s'\n", colors[i]); } - loaded = 1; + } } int @@ -750,9 +503,8 @@ xsetcolorname(int x, const char *name) return 0; } -/* - * Absolute coordinates. - */ + +// Absolute coordinates. void xclear(int x1, int y1, int x2, int y2) { @@ -763,8 +515,7 @@ xclear(int x1, int y1, int x2, int y2) void xhints(void) { - XClassHint class = {opt_name ? opt_name : termname, - opt_class ? opt_class : termname}; + XClassHint class = {"se", "se"}; XWMHints wm = {.flags = InputHint, .input = 1}; XSizeHints *sizeh; @@ -775,10 +526,10 @@ xhints(void) sizeh->width = win.w; sizeh->height_inc = win.ch; sizeh->width_inc = win.cw; - sizeh->base_height = 2 * borderpx; - sizeh->base_width = 2 * borderpx; - sizeh->min_height = win.ch + 2 * borderpx; - sizeh->min_width = win.cw + 2 * borderpx; + sizeh->base_height = 2 * border_px; + sizeh->base_width = 2 * border_px; + sizeh->min_height = win.ch + 2 * border_px; + sizeh->min_width = win.cw + 2 * border_px; if (xw.isfixed) { sizeh->flags |= PMaxSize; sizeh->min_width = sizeh->max_width = win.w; @@ -866,6 +617,12 @@ xloadfont(Font *f, FcPattern *pattern) } } + // Printable characters in ASCII, used to estimate the advance width of single wide characters. + const char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + XftTextExtentsUtf8(xw.dpy, f->match, (const FcChar8 *) ascii_printable, strlen(ascii_printable), &extents); @@ -933,8 +690,8 @@ xloadfonts(const char *fontstr, double fontsize) } /* Setting character width and height. */ - win.cw = ceilf(dc.font.width * cwscale); - win.ch = ceilf(dc.font.height * chscale); + win.cw = ceilf(dc.font.width * cw_scale); + win.ch = ceilf(dc.font.height * ch_scale); FcPatternDel(pattern, FC_SLANT); FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); @@ -954,28 +711,6 @@ xloadfonts(const char *fontstr, double fontsize) FcPatternDestroy(pattern); } -void -xunloadfont(Font *f) -{ - XftFontClose(xw.dpy, f->match); - FcPatternDestroy(f->pattern); - if (f->set) - FcFontSetDestroy(f->set); -} - -void -xunloadfonts(void) -{ - /* Free the loaded fonts in the font cache. */ - while (frclen > 0) - XftFontClose(xw.dpy, frc[--frclen].font); - - xunloadfont(&dc.font); - xunloadfont(&dc.bfont); - xunloadfont(&dc.ifont); - xunloadfont(&dc.ibfont); -} - int ximopen(Display *dpy) { @@ -1048,16 +783,15 @@ xinit(int cols, int rows) if (!FcInit()) die("could not init fontconfig.\n"); - usedfont = (opt_font == NULL)? font : opt_font; - xloadfonts(usedfont, 0); + xloadfonts(fontconfig, 0); /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xloadcols(); /* adjust fixed window geometry */ - win.w = 2 * borderpx + cols * win.cw; - win.h = 2 * borderpx + rows * win.ch; + win.w = 2 * border_px + cols * win.cw; + win.h = 2 * border_px + rows * win.ch; if (xw.gm & XNegative) xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; if (xw.gm & YNegative) @@ -1072,8 +806,7 @@ xinit(int cols, int rows) | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; xw.attrs.colormap = xw.cmap; - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) - parent = XRootWindow(xw.dpy, xw.scr); + parent = XRootWindow(xw.dpy, xw.scr); xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity @@ -1101,16 +834,16 @@ xinit(int cols, int rows) } /* white cursor, black outline */ - cursor = XCreateFontCursor(xw.dpy, mouseshape); + cursor = XCreateFontCursor(xw.dpy, XC_xterm); XDefineCursor(xw.dpy, xw.win, cursor); - if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + if (XParseColor(xw.dpy, xw.cmap, colors[cursor_fg], &xmousefg) == 0) { xmousefg.red = 0xffff; xmousefg.green = 0xffff; xmousefg.blue = 0xffff; } - if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + if (XParseColor(xw.dpy, xw.cmap, colors[cursor_bg], &xmousebg) == 0) { xmousebg.red = 0x0000; xmousebg.green = 0x0000; xmousebg.blue = 0x0000; @@ -1142,7 +875,7 @@ xinit(int cols, int rows) int xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) { - float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + float winx = border_px + x * win.cw, winy = border_px + y * win.ch, xp, yp; ushort mode, prevmode = USHRT_MAX; Font *font = &dc.font; int frcflags = FRC_NORMAL; @@ -1275,7 +1008,7 @@ void xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) { int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); - int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + int winx = border_px + x * win.cw, winy = border_px + y * win.ch, width = charlen * win.cw; Color *fg, *bg, *temp, revfg, truefg, truebg; XRenderColor colfg, colbg; @@ -1284,10 +1017,10 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i /* Fallback on color display for attributes not supported by the font */ if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { if (dc.ibfont.badslant || dc.ibfont.badweight) - base.fg = defaultattr; + base.fg = default_attributes.fg; } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || (base.mode & ATTR_BOLD && dc.bfont.badweight)) { - base.fg = defaultattr; + base.fg = default_attributes.fg; } if (IS_TRUECOL(base.fg)) { @@ -1336,17 +1069,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i /* Intelligent cleaning up of the borders. */ if (x == 0) { - xclear(0, (y == 0)? 0 : winy, borderpx, + xclear(0, (y == 0)? 0 : winy, border_px, winy + win.ch + - ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + ((winy + win.ch >= border_px + win.th)? win.h : 0)); } - if (winx + width >= borderpx + win.tw) { + if (winx + width >= border_px + win.tw) { xclear(winx + width, (y == 0)? 0 : winy, win.w, - ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + ((winy + win.ch >= border_px + win.th)? win.h : (winy + win.ch))); } if (y == 0) - xclear(winx, 0, winx + width, borderpx); - if (winy + win.ch >= borderpx + win.th) + xclear(winx, 0, winx + width, border_px); + if (winy + win.ch >= border_px + win.th) xclear(winx, winy + win.ch, winx + width, win.h); /* Clean up the region we want to draw to. */ @@ -1388,83 +1121,67 @@ xdrawglyph(Glyph g, int x, int y) } void -xdrawcursor(int cx, int cy, Glyph g) +xdrawcursor(int cx, int cy, int focused) { - Color drawcol; + LIMIT(cx, 0, term.col-1); + LIMIT(cy, 0, term.row-1); + Glyph g = term.line[cy][cx]; + if (IS_SET(MODE_HIDE)) return; - if (IS_SET(MODE_HIDE)) - return; - - /* - * Select the right color for the right mode. - */ - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; - - g.fg = default_attributes.bg; - g.bg = defaultcs; - drawcol = dc.col[g.bg]; + g.mode &= ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + g.fg = cursor_bg; + g.bg = cursor_fg; + Color drawcol = dc.col[g.bg]; /* draw the new one */ - if (IS_SET(MODE_FOCUSED)) { + if (IS_SET(MODE_FOCUSED) && !(get_file_buffer(focused_window)->mode & BUFFER_SELECTION_ON) && focused) { switch (win.cursor) { - case 7: /* st extension */ - g.u = 0x2603; /* snowman (U+2603) */ - /* FALLTHROUGH */ - case 0: /* Blinking Block */ - case 1: /* Blinking Block (Default) */ - case 2: /* Steady Block */ + case 0: // Blinking Block + case 1: // Blinking Block (Default) + case 2: // Steady Block xdrawglyph(g, cx, cy); break; - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ + case 3: // Blinking Underline + case 4: // Steady Underline XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + (cy + 1) * win.ch - \ - cursorthickness, - win.cw, cursorthickness); + border_px + cx * win.cw, + border_px + (cy + 1) * win.ch - \ + cursor_thickness, + win.cw, cursor_thickness); break; - case 5: /* Blinking bar */ - case 6: /* Steady bar */ + case 5: // Blinking bar + case 6: // Steady bar XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + cy * win.ch, - cursorthickness, win.ch); + border_px + cx * win.cw, + border_px + cy * win.ch, + cursor_thickness, win.ch); break; } } else { XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + cy * win.ch, + border_px + cx * win.cw, + border_px + cy * win.ch, win.cw - 1, 1); XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + cy * win.ch, + border_px + cx * win.cw, + border_px + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, - borderpx + (cx + 1) * win.cw - 1, - borderpx + cy * win.ch, + border_px + (cx + 1) * win.cw - 1, + border_px + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + (cy + 1) * win.ch - 1, + border_px + cx * win.cw, + border_px + (cy + 1) * win.ch - 1, win.cw, 1); } } void -xsetenv(void) -{ - char buf[sizeof(long) * 8 + 1]; - - snprintf(buf, sizeof(buf), "%lu", xw.win); - setenv("WINDOWID", buf, 1); -} - -void xseticontitle(char *p) { XTextProperty prop; - DEFAULT(p, opt_title); + DEFAULT(p, "se"); if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop) != Success) @@ -1478,7 +1195,7 @@ void xsettitle(char *p) { XTextProperty prop; - DEFAULT(p, opt_title); + DEFAULT(p, "se"); if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop) != Success) @@ -1488,12 +1205,6 @@ xsettitle(char *p) XFree(prop.value); } -int -xstartdraw(void) -{ - return IS_SET(MODE_VISIBLE); -} - void xdrawline(Line line, int x1, int y1, int x2) { @@ -1523,29 +1234,27 @@ xdrawline(Line line, int x1, int y1, int x2) xdrawglyphfontspecs(specs, base, i, ox, y1); } -void -xfinishdraw(void) -{ - XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, - win.h, 0, 0); +void xsetenv(void) { + char buf[sizeof(long) * 8 + 1]; + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +int xstartdraw(void) {return IS_SET(MODE_VISIBLE);} + +void xfinishdraw(void) { + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0); XSetForeground(xw.dpy, dc.gc, dc.col[default_attributes.bg].pixel); } void expose(XEvent *ev) {} // do nothing -void -visibility(XEvent *ev) -{ +void visibility(XEvent *ev) { XVisibilityEvent *e = &ev->xvisibility; - MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); } -void -unmap(XEvent *ev) -{ - win.mode &= ~MODE_VISIBLE; -} +void unmap(XEvent *ev) {win.mode &= ~MODE_VISIBLE;} void xsetpointermotion(int set) @@ -1554,26 +1263,46 @@ xsetpointermotion(int set) XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); } -int -xsetcursor(int cursor) -{ +int xsetcursor(int cursor) { if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ return 1; win.cursor = cursor; return 0; } -void -xseturgency(int add) -{ +void xseturgency(int add) { XWMHints *h = XGetWMHints(xw.dpy, xw.win); - MODBIT(h->flags, add, XUrgencyHint); XSetWMHints(xw.dpy, xw.win, h); XFree(h); } void +xunloadfonts(void) +{ + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +void +xunloadfont(Font *f) +{ + assert(f); + assert(f->match); + assert(f->pattern); + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void focus(XEvent *ev) { XFocusChangeEvent *e = &ev->xfocus; @@ -1594,8 +1323,141 @@ focus(XEvent *ev) } int -match(uint mask, uint state) +file_browser_actions(KeySym keysym, int modkey) +{ + static char full_path[PATH_MAX]; + static char filename[PATH_MAX]; + struct file_buffer* fb = get_file_buffer(focused_window); + int offset = fb->len; + + switch (keysym) { + float new_font_size; + int new_fb; + case XK_BackSpace: + if (offset <= 0) { + char* dest = strrchr(fb->file_path, '/'); + printf("%ld\n", dest - fb->file_path); + if (dest && dest != fb->file_path) *dest = 0; + return 1; + } + + focused_window->cursor_offset = offset; + buffer_move_on_line(focused_window, -1, 0); + offset = focused_window->cursor_offset; + + buffer_remove(fb, offset, 1, 0, 0); + focused_window->y_scroll = 0; + return 1; + case XK_Tab: + case XK_Return: + { + char* path = file_path_get_path(fb->file_path); + buffer_change(fb, "\0", 1, fb->len, 1); + if (fb->len > 0) fb->len--; + + DIR *dir = opendir(path); + for (int y = 0; file_browser_next_item(dir, path, fb->contents, full_path, filename); y++) { + if (y == focused_window->y_scroll) { + if (path_is_folder(full_path)) { + strcat(full_path, "/"); + strcpy(fb->file_path, full_path); + + fb->len = 0; + fb->contents[0] = '\0'; + focused_window->y_scroll = 0; + focused_window->y_scroll = 0; + + free(path); + closedir(dir); + return 1; + } + goto open_file; + } + } + + if (fb->contents[fb->len-1] == '/') { + free(path); + closedir(dir); + return 1; + } + + strcpy(full_path, path); + strcat(full_path, fb->contents); +open_file: + new_fb = new_file_buffer_entry(full_path); + destroy_file_buffer_entry(focused_node, &root_node); + focused_node->window = window_buffer_new(new_fb); + free(path); + closedir(dir); + return 1; + } + case XK_Down: + focused_window->y_scroll++; + if (focused_window->y_scroll < 0) + focused_window->y_scroll = 0; + return 1; + case XK_Up: + focused_window->y_scroll--; + if (focused_window->y_scroll < 0) + focused_window->y_scroll = 0; + return 1; + case XK_Escape: + destroy_file_buffer_entry(focused_node, &root_node); + return 1; + + case XK_Page_Down: + new_font_size = usedfontsize-1.0; + goto set_fontsize; + case XK_Page_Up: + new_font_size = usedfontsize+1.0; + goto set_fontsize; + case XK_Home: + new_font_size = defaultfontsize; + set_fontsize: + xunloadfonts(); + xloadfonts(fontconfig, new_font_size); + cresize(0, 0); + xhints(); + return 1; + } + return 0; +} + +void +file_browser_string_insert(const char* buf, int buflen) { + static char full_path[PATH_MAX]; + struct file_buffer* fb = get_file_buffer(focused_window); + + if (fb->len + buflen + strlen(fb->file_path) > PATH_MAX) + return; + + if (buf[0] >= 32 || buflen > 1) { + buffer_insert(fb, buf, buflen, fb->len, 0); + buffer_move_offset_relative(focused_window, buflen, 0); + focused_window->y_scroll = 0; + + if (*buf == '/') { + buffer_change(fb, "\0", 1, fb->len, 0); + if (fb->len > 0) fb->len--; + char* path = file_path_get_path(fb->file_path); + strcpy(full_path, path); + strcat(full_path, fb->contents); + + free(path); + + if (path_is_folder(full_path)) { + file_browser_actions(XK_Return, 0); + return; + } + } + } else { + printf("unhandled control character %x\n", buf[0]); + } +} + +int match(uint mask, uint state) { + const uint ignoremod = Mod2Mask|XK_SWITCH_MOD; return mask == XK_ANY_MOD || mask == (state & ~ignoremod); } @@ -1608,7 +1470,6 @@ kpress(XEvent *ev) int len; Rune c; Status status; - Shortcut *bp; if (IS_SET(MODE_KBDLOCK)) return; @@ -1617,120 +1478,38 @@ kpress(XEvent *ev) len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); else len = XLookupString(e, buf, sizeof buf, &ksym, NULL); - /* 1. shortcuts */ - for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { - if (ksym == bp->keysym && match(bp->mod, e->state)) { - bp->func(&(bp->arg)); - return; - } - } - /* 2. defaults for actions like backspace, del, enter etc*/ - int offset = focused_window->cursor_offset; - struct file_buffer* fb = get_file_buffer(focused_window); - - if (offset == -1) - return; - - switch (ksym) { - case XK_BackSpace: - if (fb->mode & BUFFER_SELECTION_ON) { - buffer_remove_selection(fb); - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~(BUFFER_SELECTION_ON); + // keysym callback + if (focused_window->mode == WINDOW_BUFFER_FILE_BROWSER) { + if(file_browser_actions(ksym, e->state)) return; - } - if (offset <= 0) return; - - if (fb->contents[offset-1] == '\n') { - buffer_move_lines(focused_window, -1, CURSOR_COMMAND_MOVEMENT); - } else { - buffer_move_on_line(focused_window, -1, CURSOR_COMMAND_MOVEMENT); - } - offset = focused_window->cursor_offset; - - /* FALLTHROUGH */ - case XK_Delete: - if (fb->mode & BUFFER_SELECTION_ON) { - buffer_remove_selection(fb); - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~(BUFFER_SELECTION_ON); + } else if (keypress_callback) { + if (keypress_callback(ksym, e->state)) return; - } - buffer_remove(fb, offset, 1, 0, 0); - return; - case XK_Return: - if (fb->mode & BUFFER_SELECTION_ON) { - buffer_remove_selection(fb); - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~(BUFFER_SELECTION_ON); - offset = focused_window->cursor_offset; - } - buffer_insert(fb, "\n", 1, offset, 0); - buffer_move_to_offset(focused_window, offset+1, CURSOR_COMMAND_MOVEMENT); - return; - case XK_Home: { - int new_offset = buffer_seek_char_backwards(fb, offset, '\n'); - if (new_offset < 0) - new_offset = 0; - buffer_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT); - return; - } - case XK_End: { - int new_offset = buffer_seek_char(fb, offset, '\n'); - if (new_offset < 0) - new_offset = fb->len-1; - buffer_move_to_offset(focused_window, new_offset, CURSOR_COMMAND_MOVEMENT); - return; - } - case XK_Page_Down: - buffer_move_lines(focused_window, (term.row-1) / 2, CURSOR_COMMAND_MOVEMENT); - focused_window->y_scroll += (term.row-1) / 2; - //TODO: make cursor follow - return; - case XK_Page_Up: - buffer_move_lines(focused_window, -((term.row-1) / 2), CURSOR_COMMAND_MOVEMENT); - focused_window->y_scroll -= (term.row-1) / 2; - //TODO: make cursor follow - return; - case XK_Tab: - buffer_insert(fb, "\t", 1, offset, 0); - buffer_move_on_line(focused_window, 1, CURSOR_COMMAND_MOVEMENT); - return; } - //TODO: keybinds for escape and tab - /* 3. composed string from input method */ - if (len == 0) - return; - if (len == 1 && e->state & Mod1Mask) { - if (*buf < 0177) { - c = *buf | 0x80; - len = utf8encode(c, buf); - } - } - - // TODO: allow blocking of the bufferwrite, redirecting to keybinds with multiple characther length - if (buf[0] >= 32) { - if (fb->mode & BUFFER_SELECTION_ON) { - buffer_remove_selection(fb); - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~(BUFFER_SELECTION_ON); + // composed string from input method and send to callback + if (string_input_callback) { + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } } - buffer_insert(fb, buf, len, offset, 0); - buffer_move_on_line(focused_window, 1, CURSOR_COMMAND_MOVEMENT); - } else { - printf("unhandled control character %x\n", buf[0]); + if (focused_window->mode == WINDOW_BUFFER_FILE_BROWSER) + file_browser_string_insert(buf, len); + else + string_input_callback(buf, len); } } void cmessage(XEvent *e) { - /* - * See xembed specs - * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html - */ + // See xembed specs + // http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { win.mode |= MODE_FOCUSED; @@ -1758,7 +1537,7 @@ run(void) XEvent ev; int w = win.w, h = win.h; - /* Waiting for window mapping */ + // Waiting for window mapping do { XNextEvent(xw.dpy, &ev); /* @@ -1794,11 +1573,13 @@ run(void) continue; } - tsetregion(0, 0, term.col-1, term.row-1, ' '); - window_write_tree_to_screen(&root_node, 0, 0, term.col-1, term.row-1); + window_draw_tree_to_screen(&root_node, 0, 0, term.col-1, term.row-1); + + if (draw_callback) + draw_callback(); - draw(cursor_x,cursor_y); + xfinishdraw(); XFlush(xw.dpy); } } @@ -1808,32 +1589,45 @@ main(int argc, char *argv[]) { xw.l = xw.t = 0; xw.isfixed = False; - xsetcursor(cursorshape); - - if (argc > 0) /* eat all remaining arguments */ - opt_cmd = argv; - - if (!opt_title) - opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + xsetcursor(cursor_shape); setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); - cols = MAX(cols, 1); - rows = MAX(rows, 1); + int cols = MAX(default_cols, 1); + int rows = MAX(default_rows, 1); tnew(cols, rows); xinit(cols, rows); xsetenv(); - //buffer_new("/home/halvard/gunslinger/gs.h"); - //buffer_new("/home/halvard/test.c"); + if (argc <= 1) { + *focused_window = window_buffer_new(new_file_buffer_entry(NULL)); + } else { + int master_stack = 1; + for (int i = 1; i < argc; i++) { + if (*argv[i] == '-') { + i++; + } else { + if (master_stack < 0) { + window_node_split(focused_node, 0.5, WINDOW_HORISONTAL); + master_stack = 0; + } else if (master_stack) { + *focused_window = window_buffer_new(new_file_buffer_entry(argv[i])); + master_stack = -1; + continue; + } else { + window_node_split(focused_node, 0.5, WINDOW_VERTICAL); + } + if (focused_node->node2) { + focused_node = focused_node->node2; + focused_window = &focused_node->window; + if (!master_stack) + *focused_window = window_buffer_new(new_file_buffer_entry(argv[i])); + } + master_stack = 0; + } + } + } - root_node.mode = WINDOW_SINGULAR; - root_node.window.buffer_index = - new_file_buffer(buffer_new("/home/halvard/gunslinger/gs.h")); - //new_file_buffer(buffer_new("/home/halvard/Code/C/se/st/st.c")); - //new_file_buffer(buffer_new("/home/halvard/test.c")); - focused_node = &root_node; - focused_window = &focused_node->window; run(); return 0; diff --git a/x.h b/x.h @@ -0,0 +1,127 @@ +/* See LICENSE for license details. */ +#ifndef _X_H +#define _X_H + +#include "se.h" +#undef Glyph + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/cursorfont.h> +#include <X11/keysym.h> +#include <X11/Xft/Xft.h> +#include <X11/XKBlib.h> + +#define Glyph Glyph_ + +enum win_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, +}; + +// Purely graphic info +typedef struct { + int tw, th; // tty width and height + int w, h; // window width and height + int ch; // char height + int cw; // char width + int mode; // window state/mode flags + int cursor; // cursor style +} TermWindow; + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; // font spec buffer used for rendering + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; // is fixed geometry? + int l, t; // left and top offset + int gm; // geometry mask +} XWindow; + +// Font structure +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +// Font Ring Cache +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +// Drawing Context +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +void xclipcopy(void); +void xdrawcursor(int, int, int focused); +void xdrawline(Line, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +void xloadfonts(const char *, double); +int xsetcolorname(int, const char *); +void xseticontitle(char *); +int xsetcursor(int); +void xsetpointermotion(int); +int xstartdraw(void); +void xunloadfonts(void); +void xunloadfont(Font *); +void cresize(int, int); +void xhints(void); +int match(uint, uint); + +struct file_buffer* get_file_buffer(struct window_buffer* buf); +int new_file_buffer_entry(const char* file_path); +void destroy_file_buffer_entry(struct window_split_node* node, struct window_split_node* root); +int delete_selection(struct file_buffer* buf); + +#endif // _X_H