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 | +-- |
M | Makefile | | | 62 | +++++++++++++++++++++++++++++++------------------------------- |
A | config.def.c | | | 716 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | config.def.h | | | 199 | ------------------------------------------------------------------------------- |
M | config.mk | | | 12 | ++++++------ |
A | se.c | | | 1666 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | se.h | | | 278 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | st.c | | | 1001 | ------------------------------------------------------------------------------- |
D | st.h | | | 213 | ------------------------------------------------------------------------------- |
D | win.h | | | 35 | ----------------------------------- |
M | x.c | | | 1174 | +++++++++++++++++++++++++++++++++---------------------------------------------- |
A | x.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