se

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

commit 9d120485062d6cf0ce9075543f46000b984f7f23
parent 0f1494bc0a8b07c352df0b20586b91f93efa2b71
Author: Samdal <samdal@protonmail.com>
Date:   Wed, 14 Sep 2022 23:06:48 +0200

vim keybinds, so much more it's insane

Diffstat:
M.seproj | 1-
MMakefile | 7+++++--
Abuffer.c | 998+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abuffer.h | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.def.c | 388+++++++++----------------------------------------------------------------------
Aconfig.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.mk | 4++--
Aextension.h | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/README.txt | 3+++
Aextensions/default_shortcuts.h | 262+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/default_status_bar.h | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/keep_cursor_col.h | 18++++++++++++++++++
Aextensions/line_count.h | 21+++++++++++++++++++++
Aextensions/line_count_relative.h | 28++++++++++++++++++++++++++++
Aextensions/move_selection_with_cursor.h | 32++++++++++++++++++++++++++++++++
Aextensions/shortcuts.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/startup_message.h | 21+++++++++++++++++++++
Aextensions/syntax/c.h | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/syntax/gd.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/syntax/handy_defines.h | 32++++++++++++++++++++++++++++++++
Aextensions/syntax/schemes/gruvbox.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/syntax/syntax.h | 646+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aextensions/undo.h | 24++++++++++++++++++++++++
Aextensions/window_modes/choose_one_of_selection.enums | 2++
Aextensions/window_modes/choose_one_of_selection.h | 594+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anormal_config.def.c | 245+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dplugins/color_schemes/gruvbox.h | 66------------------------------------------------------------------
Dplugins/syntax/c.h | 101-------------------------------------------------------------------------------
Dplugins/syntax/handy_defines.h | 20--------------------
Mse.c | 2187+++++++------------------------------------------------------------------------
Mse.h | 335+++----------------------------------------------------------------------------
Aseek.c | 484+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aseek.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autf8.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autf8.h | 19+++++++++++++++++++
Mx.c | 929+++++++++++++++++++++++++++++++------------------------------------------------
Mx.h | 181++++++++++++++++++++++++++++++++++++++-----------------------------------------
37 files changed, 5028 insertions(+), 3539 deletions(-)

diff --git a/.seproj b/.seproj @@ -3,4 +3,3 @@ se.h x.c x.h config.c -config.mk diff --git a/Makefile b/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = se.c x.c config.c +SRC = se.c x.c config.c buffer.c seek.c utf8.c OBJ = $(SRC:.c=.o) all: options se @@ -54,4 +54,7 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/se # rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 -.PHONY: all options clean dist install uninstall +run: all + ./se + +.PHONY: all options clean dist install uninstall run diff --git a/buffer.c b/buffer.c @@ -0,0 +1,998 @@ +#include "buffer.h" + +#include "config.h" +#include "se.h" +#include "extension.h" + +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> +#include <time.h> + +// TODO: mark buffers as dirty and only redraw windows that have been changed + +//////////////////////////////////////////////// +// Globals +// + +static char root_node_search[SEARCH_TERM_MAX_LEN]; +struct window_split_node root_node = {.mode = WINDOW_SINGULAR, .search = root_node_search}; +struct window_split_node* focused_node = &root_node; +struct window_buffer* focused_window = &root_node.wb; + +static struct file_buffer* file_buffers; +static int available_buffer_slots = 0; + +///////////////////////////////////////////////// +// Function implementations +// + +//////////////////////////////////////////////// +// File buffer +// + +static 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)); +} + + +// TODO: file open callback, implement as plugin +static int open_seproj(struct file_buffer fb); +int +open_seproj(struct file_buffer fb) +{ + int first = -1; + + char* path = file_path_get_path(fb.file_path); + chdir(path); + int offset = -1; + + while((offset = fb_seek_char(&fb, offset+1, ' ')) >= 0) + fb_change(&fb, "\n", 1, offset, 0); + + offset = -1; + while((offset = fb_seek_char(&fb, offset+1, '\n')) >= 0) { + char* line = fb_get_line_at_offset(&fb, offset); + if (strlen(line) && !is_file_type(line, ".seproj")) { + if (first < 0) + first = fb_new_entry(line); + else + fb_new_entry(line); + } + free(line); + } + + if (first < 0) + first = fb_new_entry(NULL); + writef_to_status_bar("opened project %s", path); + free(path); + + fb_destroy(&fb); + return first; +} + +void +fb_write_to_filepath(struct file_buffer* fb) +{ + if (!fb->file_path) + return; + soft_assert(fb->contents, return;); + FILE* file = fopen(fb->file_path, "w"); + soft_assert(file, return;); + + if (fb->mode & FB_UTF8_SIGNED) + fwrite("\xEF\xBB\xBF", 1, 3, file); + fwrite(fb->contents, sizeof(char), fb->len, file); + writef_to_status_bar("saved buffer to %s", fb->file_path); + + fclose(file); + call_extension(fb_written_to_file, fb); +} + + +int +destroy_fb_entry(struct window_split_node* node, struct window_split_node* root) +{ + // 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->wb.fb_index) + break; + if (n >= available_buffer_slots) { + writef_to_status_bar("can't delete last buffer"); + return 0; + } + + if (window_other_nodes_contain_fb(node, root)) { + node->wb.fb_index++; + node->wb = wb_new(node->wb.fb_index); + writef_to_status_bar("swapped buffer"); + return 0; + } + fb_destroy(get_fb(&node->wb)); + + node->wb = wb_new(node->wb.fb_index); + + return 1; +} + + + +struct file_buffer* +get_fb(struct window_buffer* wb) +{ + soft_assert(wb, wb = focused_window;); + soft_assert(file_buffers, fb_new_entry(NULL);); + + if (wb->fb_index < 0) + wb->fb_index = available_buffer_slots-1; + else if (wb->fb_index >= available_buffer_slots) + wb->fb_index = 0; + + if (!file_buffers[wb->fb_index].contents) { + for(int n = wb->fb_index; n < available_buffer_slots; n++) { + if (file_buffers[n].contents) { + wb->fb_index = n; + return &file_buffers[n]; + } + } + for(int n = 0; n < available_buffer_slots; n++) { + if (file_buffers[n].contents) { + wb->fb_index = n; + return &file_buffers[n]; + } + } + } else { + soft_assert(file_buffers[wb->fb_index].contents, ); + return &file_buffers[wb->fb_index]; + } + + wb->fb_index = fb_new_entry(NULL); + writef_to_status_bar("all buffers were somehow deleted, creating new one"); + status_bar_bg = warning_color; + return get_fb(wb); +} + +int +fb_delete_selection(struct file_buffer* fb) +{ + if (fb->mode & FB_SELECTION_ON) { + fb_remove_selection(fb); + wb_move_cursor_to_selection_start(focused_window); + fb->mode &= ~(FB_SELECTION_ON); + return 1; + } + return 0; +} + +struct file_buffer +fb_new(const char* file_path) +{ + struct file_buffer fb = {0}; + fb.file_path = xmalloc(PATH_MAX); + + char* res = realpath(file_path, fb.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, fb.file_path); + remove(file_path); + + writef_to_status_bar("opened new file %s", fb.file_path); + } else if (path_is_folder(fb.file_path)) { + int len = strlen(fb.file_path); + if (fb.file_path[len-1] != '/' && len < PATH_MAX-1) { + fb.file_path[len] = '/'; + fb.file_path[len+1] = '\0'; + } + } else { + FILE *file = fopen(fb.file_path, "rb"); + if (file) { + 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"); + return fb; + // TODO: don't crash + } + + fb.len = readsize; + fb.capacity = readsize + 100; + + fb.contents = xmalloc(fb.capacity); + fb.contents[0] = 0; + + char bom[4] = {0}; + fread(bom, 1, 3, file); + if (strcmp(bom, "\xEF\xBB\xBF")) + rewind(file); + else + fb.mode |= FB_UTF8_SIGNED; + fread(fb.contents, 1, readsize, file); + fclose(file); + + fb.syntax_index = -1; + } + } + + if (!fb.capacity) + fb.capacity = 100; + if (!fb.contents) { + fb.contents = xmalloc(fb.capacity); + memset(fb.contents, 0, fb.capacity); + } + fb.ub = xmalloc(sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); + fb.search_term = xmalloc(SEARCH_TERM_MAX_LEN); + fb.non_blocking_search_term = xmalloc(SEARCH_TERM_MAX_LEN); + memset(fb.ub, 0, sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); + memset(fb.search_term, 0, SEARCH_TERM_MAX_LEN); + memset(fb.non_blocking_search_term, 0, SEARCH_TERM_MAX_LEN); + fb.indent_len = default_indent_len; + + // change line endings + int offset = 0; + while((offset = fb_seek_string(&fb, offset, "\r\n")) >= 0) + fb_remove(&fb, offset, 1, 1, 1); + offset = 0; + while((offset = fb_seek_char(&fb, offset, '\r')) >= 0) + fb_change(&fb, "\n", 1, offset, 1); + + call_extension(fb_new_file_opened, &fb); + + call_extension(fb_contents_updated, &fb, 0, FB_CONTENT_INIT); + + if (res) + writef_to_status_bar("new fb %s", fb.file_path); + return fb; +} + +int +fb_new_entry(const char* file_path) +{ + static char full_path[PATH_MAX]; + if (!file_path) + file_path = "./"; + soft_assert(strlen(file_path) < PATH_MAX, file_path = "./";); + + 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) { + writef_to_status_bar("buffer exits"); + return n; + } + } + } + } else { + strcpy(full_path, file_path); + } + + for(int n = 0; n < available_buffer_slots; n++) { + if (!file_buffers[n].contents) { + if (is_file_type(full_path, ".seproj")) + return open_seproj(fb_new(full_path)); + file_buffers[n] = fb_new(full_path); + return n; + } + } + } + + if (is_file_type(full_path, ".seproj")) + return open_seproj(fb_new(full_path)); + + available_buffer_slots++; + file_buffers = xrealloc(file_buffers, sizeof(struct file_buffer) * available_buffer_slots); + file_buffers[available_buffer_slots-1] = fb_new(full_path); + + return available_buffer_slots-1; +} + +void +fb_destroy(struct file_buffer* fb) +{ + free(fb->ub); + free(fb->contents); + free(fb->file_path); + free(fb->search_term); + free(fb->non_blocking_search_term); + *fb = (struct file_buffer){0}; +} + +void +fb_insert(struct file_buffer* fb, const char* new_content, const int len, const int offset, int do_not_callback) +{ + soft_assert(fb, return;); + soft_assert(fb->contents, fb->capacity = 0;); + soft_assert(offset <= fb->len && offset >= 0, + fprintf(stderr, "writing past fb%s\n", fb->file_path); + return; + ); + + if (fb->len + len >= fb->capacity) { + fb->capacity = fb->len + len + 256; + fb->contents = xrealloc(fb->contents, fb->capacity); + } + if (offset < fb->len) + memmove(fb->contents+offset+len, fb->contents+offset, fb->len-offset); + fb->len += len; + + memcpy(fb->contents+offset, new_content, len); + if (!do_not_callback) + call_extension(fb_contents_updated, fb, offset, FB_CONTENT_NORMAL_EDIT); +} + +void +fb_change(struct file_buffer* fb, const char* new_content, const int len, const int offset, int do_not_callback) +{ + soft_assert(offset <= fb->len && offset >= 0, return;); + + if (offset + len > fb->len) { + fb->len = offset + len; + if (fb->len >= fb->capacity) { + fb->capacity = fb->len + len + 256; + fb->contents = xrealloc(fb->contents, fb->capacity); + } + } + + memcpy(fb->contents+offset, new_content, len); + if (!do_not_callback) + call_extension(fb_contents_updated, fb, offset, FB_CONTENT_NORMAL_EDIT); +} + +int +fb_remove(struct file_buffer* fb, int offset, int len, int do_not_calculate_charsize, int do_not_callback) +{ + LIMIT(offset, 0, fb->len-1); + if (len == 0) return 0; + soft_assert(fb->contents, return 0;); + soft_assert(offset + len <= fb->len, return 0;); + + int removed_len = 0; + if (do_not_calculate_charsize) { + removed_len = len; + } else { + while (len--) { + int charsize = utf8_decode_buffer(fb->contents + offset, fb->len - offset, NULL); + if (fb->len - charsize < 0) + return 0; + removed_len += charsize; + } + } + fb->len -= removed_len; + memmove(fb->contents+offset, fb->contents+offset+removed_len, fb->len-offset); + if (!do_not_callback) + call_extension(fb_contents_updated, fb, offset, FB_CONTENT_NORMAL_EDIT); + return removed_len; +} + +void +wb_copy_ub_to_current(struct window_buffer* wb) +{ + struct file_buffer* fb = get_fb(wb); + struct undo_buffer* cub = &fb->ub[fb->current_undo_buffer]; + + fb->contents = xrealloc(fb->contents, cub->capacity); + memcpy(fb->contents, cub->contents, cub->capacity); + fb->len = cub->len; + fb->capacity = cub->capacity; + + wb_move_to_offset(wb, cub->cursor_offset, CURSOR_SNAPPED); + //TODO: remove y_scroll from undo buffer + wb->y_scroll = cub->y_scroll; +} + + +void +fb_undo(struct file_buffer* fb) +{ + if (fb->current_undo_buffer == 0) { + writef_to_status_bar("end of undo buffer"); + return; + } + fb->current_undo_buffer--; + fb->available_redo_buffers++; + + wb_copy_ub_to_current(focused_window); + writef_to_status_bar("undo"); +} + +void +fb_redo(struct file_buffer* fb) +{ + if (fb->available_redo_buffers == 0) { + writef_to_status_bar("end of redo buffer"); + return; + } + fb->available_redo_buffers--; + fb->current_undo_buffer++; + + wb_copy_ub_to_current(focused_window); + writef_to_status_bar("redo"); +} + +void +fb_add_to_undo(struct file_buffer* fb, int offset, enum buffer_content_reason reason) +{ + static time_t last_normal_edit; + static int edits; + + if (reason == FB_CONTENT_CURSOR_MOVE) { + struct undo_buffer* cub = &fb->ub[fb->current_undo_buffer]; + cub->cursor_offset = offset; + if (focused_window) + cub->y_scroll = focused_window->y_scroll; + else + cub->y_scroll = 0; + return; + } + + if (reason == FB_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 == FB_CONTENT_INIT) { + goto copy_undo_buffer; + } + + fb->available_redo_buffers = 0; + if (fb->current_undo_buffer == UNDO_BUFFERS_COUNT-1) { + char* begin_buffer = fb->ub[0].contents; + memmove(fb->ub, &(fb->ub[1]), (UNDO_BUFFERS_COUNT-1) * sizeof(struct undo_buffer)); + fb->ub[fb->current_undo_buffer].contents = begin_buffer; + } else { + fb->current_undo_buffer++; + } + +copy_undo_buffer: ; + struct undo_buffer* cub = fb->ub + fb->current_undo_buffer; + + cub->contents = xrealloc(cub->contents, fb->capacity); + memcpy(cub->contents, fb->contents, fb->capacity); + cub->len = fb->len; + cub->capacity = fb->capacity; + cub->cursor_offset = offset; + if (focused_window) + cub->y_scroll = focused_window->y_scroll; + else + cub->y_scroll = 0; +} + + +char* +fb_get_string_between_offsets(struct file_buffer* fb, int start, int end) +{ + int len = end - start; + + char* string = xmalloc(len + 1); + memcpy(string, fb->contents+start, len); + string[len] = 0; + return string; +} + +char* +fb_get_selection(struct file_buffer* fb, int* selection_len) +{ + if (!(fb->mode & FB_SELECTION_ON)) + return NULL; + + int start, end; + if (fb_is_selection_start_top_left(fb)) { + start = fb->s1o; + end = fb->s2o+1; + } else { + start = fb->s2o; + end = fb->s1o+1; + } + if (selection_len) + *selection_len = end - start; + return fb_get_string_between_offsets(fb, start, end); +} + + +int +fb_is_selection_start_top_left(const struct file_buffer* fb) +{ + return (fb->s1o <= fb->s2o) ? 1 : 0; +} + +void +fb_remove_selection(struct file_buffer* buffer) +{ + if (!(buffer->mode & FB_SELECTION_ON)) + return; + + int start, end, len; + if (fb_is_selection_start_top_left(buffer)) { + start = buffer->s1o; + end = buffer->s2o+1; + } else { + start = buffer->s2o; + end = buffer->s1o+1; + } + len = end - start; + fb_remove(buffer, start, len, 1, 1); + call_extension(fb_contents_updated, buffer, start, FB_CONTENT_BIG_CHANGE); +} + +char* +fb_get_line_at_offset(const struct file_buffer* fb, int offset) +{ + int start = fb_seek_char_backwards(fb, offset, '\n'); + if (start < 0) start = 0; + int end = fb_seek_char(fb, offset, '\n'); + if (end < 0) end = fb->len-1; + + int len = end - start; + + char* res = xmalloc(len + 1); + if (len > 0) + memcpy(res, fb->contents+start, len); + res[len] = 0; + return res; +} + +void +fb_offset_to_xy(struct file_buffer* fb, int offset, int maxx, int y_scroll, int* cx, int* cy, int* xscroll) +{ + *cx = *cy = *xscroll = 0; + soft_assert(fb, return;); + + 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 >= y_scroll) + break; + repl = new_repl+1; + } + *cy = yscroll - y_scroll; + } else { + while ((new_repl = memchr(repl, '\n', last - repl))) { + repl = new_repl+1; + *cy += 1; + } + *cy -= 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_t u; + repl += utf8_decode_buffer(repl, last - repl, &u); + *cx += wcwidth(u); + } + + // TODO: make customizable + // -1 = wrap, >= 0 is padding or something like that + const int padding = 3; + + if ((*cx - maxx) + padding > 0) + *xscroll = (*cx - maxx) + padding; +} + +//////////////////////////////////////////////// +// Window buffer +// + +struct window_buffer +wb_new(int fb_index) +{ + struct window_buffer wb = {0}; + wb.fb_index = fb_index; + if (path_is_folder(get_fb(&wb)->file_path)) { + wb.mode = WB_FILE_BROWSER; + writef_to_status_bar("opened file browser %s", get_fb(&wb)->file_path); + } + + return wb; +} + +void +wb_move_on_line(struct window_buffer* wb, int amount, enum cursor_reason callback_reason) +{ + const struct file_buffer* fb = get_fb(wb); + if (fb->len <= 0) + return; + + if (amount < 0) { + while (wb->cursor_offset > 0 && fb->contents[wb->cursor_offset - 1] != '\n' && amount < 0) { + wb->cursor_offset--; + if ((fb->contents[wb->cursor_offset] & 0xC0) == 0x80) // if byte starts with 0b10 + continue; // byte is UTF-8 extender + amount++; + } + LIMIT(wb->cursor_offset, 0, fb->len); + } else if (amount > 0) { + for (int charsize = 0; + wb->cursor_offset < fb->len && amount > 0 && fb->contents[wb->cursor_offset + charsize] != '\n'; + wb->cursor_offset += charsize, amount--) { + rune_t u; + charsize = utf8_decode_buffer(fb->contents + wb->cursor_offset, fb->len - wb->cursor_offset, &u); + if (u != '\n' && u != '\t') + if (wcwidth(u) <= 0) + amount++; + if (wb->cursor_offset + charsize > fb->len) + break; + } + } + + if (callback_reason) + call_extension(wb_cursor_movement, wb, callback_reason); +} + +void +wb_move_offset_relative(struct window_buffer* wb, 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_fb((wb)); + if (fb->len <= 0) + return; + wb->cursor_offset += amount; + LIMIT(wb->cursor_offset, 0, fb->len); + + if (callback_reason) + call_extension(wb_cursor_movement, wb, callback_reason); +} + +void +wb_move_lines(struct window_buffer* wb, int amount, enum cursor_reason callback_reason) +{ + const struct file_buffer* fb = get_fb((wb)); + if (fb->len <= 0) + return; + int offset = wb->cursor_offset; + if (amount > 0) { + while (amount-- && offset >= 0) { + int new_offset = fb_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 = fb_seek_char_backwards(fb, offset, '\n')-1; + } + wb_move_to_offset(wb, offset, callback_reason); +} + +void +wb_move_to_offset(struct window_buffer* wb, 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_fb((wb)); + if (fb->len <= 0) + return; + LIMIT(offset, 0, fb->len); + wb->cursor_offset = offset; + + if (callback_reason) + call_extension(wb_cursor_movement, wb, callback_reason); +} + +void +wb_move_to_x(struct window_buffer* wb, int x, enum cursor_reason callback_reason) +{ + soft_assert(wb, return;); + struct file_buffer* fb = get_fb(wb); + + int offset = fb_seek_char_backwards(fb, wb->cursor_offset, '\n'); + if (offset < 0) + offset = 0; + wb_move_to_offset(wb, 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_t u = 0; + int charsize = utf8_decode_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; + } + } + wb_move_to_offset(wb, offset, callback_reason); +} + +//////////////////////////////////////////////// +// Window split node +// + +static 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; +} + +void +window_node_split(struct window_split_node* parent, float ratio, enum window_split_mode mode) +{ + soft_assert(parent, return;); + soft_assert(parent->mode == WINDOW_SINGULAR, return;); + soft_assert(mode != WINDOW_SINGULAR, return;); + + if ((parent->maxx - parent->minx < MIN_WINDOW_SPLIT_SIZE_HORISONTAL && mode == WINDOW_HORISONTAL) + || (parent->maxy - parent->miny < MIN_WINDOW_SPLIT_SIZE_VERTICAL && mode == WINDOW_VERTICAL)) + return; + + parent->node1 = xmalloc(sizeof(struct window_split_node)); + *parent->node1 = *parent; + parent->node1->search = xmalloc(SEARCH_TERM_MAX_LEN); + parent->node1->parent = parent; + parent->node1->node1 = NULL; + parent->node1->node2 = NULL; + + + parent->node2 = xmalloc(sizeof(struct window_split_node)); + *parent->node2 = *parent; + parent->node2->search = xmalloc(SEARCH_TERM_MAX_LEN); + parent->node2->parent = parent; + parent->node2->node1 = NULL; + parent->node2->node2 = NULL; + + if (parent->mode == WINDOW_HORISONTAL) { + // NOTE: if the window resizing is changed, change in draw tree function as well + int middlex = ((float)(parent->maxx - parent->minx) * parent->ratio) + parent->minx; + parent->node1->minx = parent->minx; + parent->node1->miny = parent->miny; + parent->node1->maxx = middlex; + parent->node1->maxy = parent->maxy; + parent->node2->minx = middlex+2; + parent->node2->miny = parent->miny; + parent->node2->maxx = parent->maxx; + parent->node2->maxy = parent->maxy; + } else if (parent->mode == WINDOW_VERTICAL) { + // NOTE: if the window resizing is changed, change in draw tree function as well + int middley = ((float)(parent->maxy - parent->miny) * parent->ratio) + parent->miny; + parent->node1->minx = parent->minx; + parent->node1->miny = parent->miny; + parent->node1->maxx = parent->maxx; + parent->node1->maxy = middley; + parent->node2->minx = parent->miny; + parent->node2->miny = middley; + parent->node2->maxx = parent->maxx; + parent->node2->maxy = parent->maxy; + } + + parent->mode = mode; + parent->ratio = ratio; + parent->wb = (struct window_buffer){0}; +} + +struct window_split_node* +window_node_delete(struct window_split_node* node) +{ + if (!node->parent) { + writef_to_status_bar("can't close root winodw"); + 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->search); + 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_node_draw_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy) +{ + soft_assert(root, return;); + + if (root->mode == WINDOW_SINGULAR) { + LIMIT(maxx, 0, screen.col-1); + LIMIT(maxy, 0, screen.row-1); + LIMIT(minx, 0, maxx); + LIMIT(miny, 0, maxy); + root->minx = minx; + root->miny = miny; + root->maxx = maxx; + root->maxy = maxy; + if (root->wb.mode != 0) { + int wn_custom_window_draw_callback_exists = 0; + extension_callback_exists(wn_custom_window_draw, wn_custom_window_draw_callback_exists = 1;); + soft_assert(wn_custom_window_draw_callback_exists, return;); + + call_extension(wn_custom_window_draw, root); + + return; + } else { + window_node_draw_to_screen(root); + } + } else if (root->mode == WINDOW_HORISONTAL) { + // NOTE: if the window resizing is changed, change in split function as well + int middlex = ((float)(maxx - minx) * root->ratio) + minx; + + // print seperator + screen_set_region(middlex+1, miny, middlex+1, maxy, L'│'); + + window_node_draw_tree_to_screen(root->node1, minx, miny, middlex, maxy); + window_node_draw_tree_to_screen(root->node2, middlex+2, miny, maxx, maxy); + + for (int y = miny; y < maxy+1; y++) + xdrawline(middlex+1, y, middlex+2); + } else if (root->mode == WINDOW_VERTICAL) { + // NOTE: if the window resizing is changed, change in split function as well + int middley = ((float)(maxy - miny) * root->ratio) + miny; + + window_node_draw_tree_to_screen(root->node1, minx, miny, maxx, middley); + window_node_draw_tree_to_screen(root->node2, minx, middley, maxx, maxy); + } +} + +void +window_node_move_all_cursors_on_same_fb(struct window_split_node* root, struct window_split_node* excluded, int fb_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->wb.fb_index == fb_index && root->wb.cursor_offset >= offset && root != excluded) + movement(&root->wb, move, reason); + } else { + window_node_move_all_cursors_on_same_fb(root->node1, excluded, fb_index, offset, movement, move, reason); + window_node_move_all_cursors_on_same_fb(root->node2, excluded, fb_index, offset, movement, move, reason); + } +} + +void +window_node_move_all_yscrolls(struct window_split_node* root, struct window_split_node* excluded, int fb_index, int offset, int move) +{ + if (root->mode == WINDOW_SINGULAR) { + if (root->wb.fb_index == fb_index && root->wb.cursor_offset >= offset && root != excluded) + root->wb.y_scroll += move; + } else { + window_node_move_all_yscrolls(root->node1, excluded, fb_index, offset, move); + window_node_move_all_yscrolls(root->node2, excluded, fb_index, offset, move); + } +} + +int +window_other_nodes_contain_fb(struct window_split_node* node, struct window_split_node* root) +{ + if (root->mode == WINDOW_SINGULAR) + return (root->wb.fb_index == node->wb.fb_index && root != node); + + return (window_other_nodes_contain_fb(node, root->node1) || + window_other_nodes_contain_fb(node, root->node2)); +} + +// TODO: create a distance type function and use that instead (from the current cursor position)? +// struct window_split_node* wincdow_closest(root, node ignore(get maxx/minx etc from this and make sure the it's outside the ignored node), enum move_directions move, int cx, int cy) +struct window_split_node* +window_switch_to_window(struct window_split_node* node, enum move_directons move) +{ + soft_assert(node, return &root_node;); + if (!node->parent) return node; + soft_assert(node->mode == WINDOW_SINGULAR, + while(node->mode != WINDOW_SINGULAR) + node = node->node1; + return node; + ); + 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, float amount) +{ + 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; + } + } +} + +void +window_node_resize_absolute(struct window_split_node* node, enum move_directons move, float amount) +{ + for (; node; node = node->parent) { + if (is_correct_mode(node->mode, move)) { + node->ratio = amount; + LIMIT(node->ratio, 0.001f, 0.95f); + return; + } + } +} diff --git a/buffer.h b/buffer.h @@ -0,0 +1,204 @@ +#ifndef BUFFER_H_ +#define BUFFER_H_ + +/* fb: file_buffer +** wb: window_buffer +** wn: window_split_node +** +*/ + +// Arbitrary sizes +#define SEARCH_TERM_MAX_LEN PATH_MAX +#define LINE_MAX_LEN 2048 +#define MIN_WINDOW_SPLIT_SIZE_VERTICAL 10 +#define MIN_WINDOW_SPLIT_SIZE_HORISONTAL 20 + +// external globals +extern struct window_split_node root_node; +extern struct window_split_node* focused_node; +extern struct window_buffer* focused_window; + +//////////////////////////////////////////////// +// File buffer +// + +#define UNDO_BUFFERS_COUNT 128 +struct undo_buffer { + char* contents; // not null terminated + int len, capacity; + int cursor_offset; + int y_scroll; +}; + +enum buffer_flags { + FB_SELECTION_ON = 1 << 0, + FB_BLOCK_SELECT = 1 << 1, + FB_LINE_SELECT = 1 << 2, + FB_SELECT_MASK = (FB_SELECTION_ON | FB_BLOCK_SELECT | FB_LINE_SELECT), + FB_SELECT_MODE_MASK = (FB_BLOCK_SELECT | FB_LINE_SELECT), + FB_READ_ONLY = 1 << 3, + FB_UTF8_SIGNED = 1 << 4, + FB_SEARCH_BLOCKING = 1 << 5, + FB_SEARCH_BLOCKING_IDLE = 1 << 6, + FB_SEARCH_BLOCKING_MASK = (FB_SEARCH_BLOCKING | FB_SEARCH_BLOCKING_IDLE), + FB_SEARCH_NON_BLOCKING = 1 << 7, + FB_SEARCH_BLOCKING_BACKWARDS = 1 << 8, + FB_SEARCH_NON_BLOCKING_BACKWARDS = 1 << 9, +}; + +struct file_buffer { + char* file_path; + char* contents; // !! NOT NULL TERMINATED !! + int len; + int capacity; + int mode; // buffer_flags + struct undo_buffer* ub; + // TODO: int file_buffer_len; + int current_undo_buffer; + int available_redo_buffers; + int s1o, s2o; // selection start offset and end offset + char* search_term; + char* non_blocking_search_term; + int syntax_index; + unsigned int indent_len; // amount of spaces, if 0 tab is used +}; + +enum buffer_content_reason { + FB_CONTENT_DO_NOT_CALLBACK = 0, + FB_CONTENT_OPERATION_ENDED, + FB_CONTENT_NORMAL_EDIT, + FB_CONTENT_BIG_CHANGE, + FB_CONTENT_INIT, + FB_CONTENT_CURSOR_MOVE, +}; + +struct file_buffer* get_fb(struct window_buffer* wb); +int fb_new_entry(const char* file_path); +int destroy_fb_entry(struct window_split_node* node, struct window_split_node* root); +int fb_delete_selection(struct file_buffer* fb); + +struct file_buffer fb_new(const char* file_path); +void fb_write_to_filepath(struct file_buffer* fb); +void fb_destroy(struct file_buffer* fb); + +void fb_insert(struct file_buffer* fb, const char* new_content, const int len, const int offset, int do_not_callback); +void fb_change(struct file_buffer* fb, const char* new_content, const int len, const int offset, int do_not_callback); +int fb_remove(struct file_buffer* fb, const int offset, int len, int do_not_calculate_charsize, int do_not_callback); + +void fb_undo(struct file_buffer* fb); +void fb_redo(struct file_buffer* fb); +void fb_add_to_undo(struct file_buffer* fb, int offset, enum buffer_content_reason reason); + +/////////////////////////////////// +// 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* fb_get_string_between_offsets(struct file_buffer* fb, int start, int end); + +char* fb_get_selection(struct file_buffer* fb, int* selection_len); +int fb_is_selection_start_top_left(const struct file_buffer* fb); +void fb_remove_selection(struct file_buffer* fb); + +/////////////////////////////////// +// returns a null terminated string containing the current line +// the returned value must be freed by the reciever +// TODO: make this take any string/char instead of hardcoded \n +char* fb_get_line_at_offset(const struct file_buffer* fb, int offset); + +void fb_offset_to_xy(struct file_buffer* fb, int offset, int maxx, int y_scroll, int* cx, int* cy, int* xscroll); + + +//////////////////////////////////////////////// +// Window buffer +// + +#define WB_NORMAL 0 +#define WB_FILE_BROWSER 1 +#define WB_MODES_DEFAULT_END 1 + +struct window_buffer { + int y_scroll; + int cursor_offset; + int cursor_col; + + int fb_index; // index into an array storing file buffers + + /////////////////////////////////// + // you may implement your own "modes" + // it will run a callback where you can render your window + // a callback allowing you to override the default input callback + // is also provided + // TODO:↑ + // see extensions/window_modes for other modes + unsigned int mode; // WB_NORMAL = 0 +}; + +enum cursor_reason { + CURSOR_DO_NOT_CALLBACK = 0, + CURSOR_COMMAND_MOVEMENT = 1, + CURSOR_UP_DOWN_MOVEMENT, + CURSOR_RIGHT_LEFT_MOVEMENT, + CURSOR_SNAPPED, +}; + +struct window_buffer wb_new(int buffer_index); + +//////////////////////////////////////////////// +// Window split node +// + +enum window_split_mode { + WINDOW_SINGULAR, + WINDOW_HORISONTAL, + WINDOW_VERTICAL, + WINDOW_FILE_BROWSER, +}; + +struct window_split_node { + struct window_buffer wb; + enum window_split_mode mode; + float ratio; + struct window_split_node *node1, *node2, *parent; + int minx, miny, maxx, maxy; // position informatin from the last frame + char* search; + int selected; +}; + +enum move_directons { + MOVE_RIGHT, + MOVE_LEFT, + MOVE_UP, + MOVE_DOWN, +}; + +//////////////////////////////////////////////// +// Window buffer +// + +void wb_write_selection(struct window_buffer* wb, int minx, int miny, int maxx, int maxy); +void wb_move_cursor_to_selection_start(struct window_buffer* wb); + +void wb_move_on_line(struct window_buffer* wb, int amount, enum cursor_reason callback_reason); +void wb_move_lines(struct window_buffer* wb, int amount, enum cursor_reason callback_reason); +void wb_move_to_offset(struct window_buffer* wb, int offset, enum cursor_reason callback_reason); +void wb_move_offset_relative(struct window_buffer* wb, int amount, enum cursor_reason callback_reason); +void wb_move_to_x(struct window_buffer* wb, int x, enum cursor_reason callback_reason); + +// window split node + +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_node_draw_tree_to_screen(struct window_split_node* root, int minx, int miny, int maxx, int maxy); +void window_node_move_all_cursors_on_same_fb(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_node_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_fb(struct window_split_node* node, struct window_split_node* root); + +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 split same type, you can't resize the upper split +void window_node_resize(struct window_split_node* node, enum move_directons move, float amount); +void window_node_resize_absolute(struct window_split_node* node, enum move_directons move, float amount); + + +#endif // BUFFER_H_ diff --git a/config.def.c b/config.def.c @@ -1,6 +1,4 @@ -#include <assert.h> - -#include "x.h" +#include "config.h" //////////////////////////////////////// // apperance @@ -48,74 +46,23 @@ unsigned int cursor_thickness = 2; #include "plugins/syntax/c.h" -const struct color_scheme color_schemes[] = { - {".c", c_word_seperators, c_color_scheme, LEN(c_color_scheme)}, - {".h", c_word_seperators, c_color_scheme, LEN(c_color_scheme)}, +const struct syntax_scheme syntax_schemes[] = { + {".c", c_word_seperators, c_syntax, LEN(c_syntax)}, + {".h", c_word_seperators, c_syntax, LEN(c_syntax)}, {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 swap_to_next_file_buffer(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 search(const Arg* arg); -static void search_next(const Arg* arg); -static void search_previous(const Arg* arg); -static void search_for_buffer(const Arg* arg); -static void search_keyword_in_buffers(const Arg* arg); -static void open_file_browser(const Arg* arg); -static void buffer_kill(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 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 int keypress_actions(KeySym keysym, int modkey); -static void string_insert_callback(const char* buf, int buflen); - ///////////////////////////////////////// // Shortcuts // -typedef struct { - uint mod; - KeySym keysym; - void (*func)(const Arg* arg); - const Arg arg; -} Shortcut; +#include "plugins/shortcuts.h" +#include "plugins/default_shortcuts.h" -#define MODKEY Mod1Mask -#define TERMMOD (ControlMask|ShiftMask) +#define MODKEY Mod4Mask +#define CtrlShift (ControlMask|ShiftMask) -const Shortcut shortcuts[] = { +shortcuts = { // mask keysym function argument { 0, XK_Right, cursor_move_x_relative, {.i = +1} }, { 0, XK_Left, cursor_move_x_relative, {.i = -1} }, @@ -125,296 +72,56 @@ const Shortcut shortcuts[] = { { 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} }, + { CtrlShift, XK_Right, window_resize, {.i = MOVE_RIGHT} }, + { CtrlShift, XK_Left, window_resize, {.i = MOVE_LEFT} }, + { CtrlShift, XK_Down, window_resize, {.i = MOVE_DOWN} }, + { CtrlShift, XK_Up, window_resize, {.i = MOVE_UP} }, { ControlMask, XK_Tab, swap_to_next_file_buffer, {0} }, { ControlMask, XK_m, toggle_selection, {0} }, { ControlMask, XK_g, move_cursor_to_offset, {0} }, - { TERMMOD, XK_G, move_cursor_to_end_of_buffer, {0} }, + { CtrlShift, XK_G, move_cursor_to_end_of_buffer, {0} }, { ControlMask, XK_period, open_file_browser, {0} }, - { TERMMOD, XK_D, buffer_kill, {0} }, + { CtrlShift, XK_D, buffer_kill, {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} }, + { CtrlShift, XK_Z, redo, {0} }, { ControlMask, XK_s, save_buffer, {0} }, { ControlMask, XK_f, search, {0} }, - { TERMMOD, XK_F, search_keyword_in_buffers,{0},}, + { CtrlShift, XK_F, search_keyword_in_buffers,{0},}, { ControlMask, XK_space, search_for_buffer,{0}, }, { ControlMask, XK_n, search_next, {0} }, - { TERMMOD, XK_N, search_previous,{0} }, + { CtrlShift, XK_N, search_previous,{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} }, + { CtrlShift, XK_Prior, zoom, {.f = +1} }, + { CtrlShift, XK_Next, zoom, {.f = -1} }, + { CtrlShift, XK_Home, zoomreset, {.f = 0} }, + { CtrlShift, XK_Num_Lock, numlock, {.i = 0} }, }; ///////////////////////////////////////////////// // callbacks // +static void cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason); +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 int keypress_actions(KeySym keysym, int modkey); +static void string_insert_callback(const char* buf, int buflen); +#include "plugins/default_status_bar.h" + 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; +void(*buffer_contents_updated)(struct file_buffer*, int, enum buffer_content_reason) = buffer_add_to_undo; int(*keypress_callback)(KeySym, int) = keypress_actions; void(*string_input_callback)(const char*, int) = string_insert_callback; void(*draw_callback)(void) = NULL; void(*startup_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); - if (focused_node->node2) { - focused_node = focused_node->node2; - focused_window = &focused_node->window; - } -} - -void window_resize(const Arg *arg) -{ - float amount = (arg->i == MOVE_RIGHT || arg->i == MOVE_LEFT) ? 0.1f : 0.05f; - window_node_resize(focused_node, arg->i, amount); -} - -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(fontconfig, 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) -{ - if (focused_window->mode != WINDOW_BUFFER_FILE_BROWSER) - 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 -swap_to_next_file_buffer(const Arg* arg) -{ - focused_window->buffer_index++; -} - -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* buf = buffer_get_selection(fb, &len); - set_clipboard_copy(buf, len); - - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~BUFFER_SELECTION_ON; -} - -void -clipboard_paste(const Arg* arg) -{ - insert_clipboard_at_cursor(); -} - -void -undo(const Arg* arg) -{ - buffer_undo(get_file_buffer(focused_window)); -} - -void -redo(const Arg* arg) -{ - buffer_redo(get_file_buffer(focused_window)); -} - -void -search(const Arg* arg) -{ - get_file_buffer(focused_window)->mode &= ~BUFFER_SEARCH_BLOCKING_IDLE; - get_file_buffer(focused_window)->mode |= BUFFER_SEARCH_BLOCKING; - writef_to_status_bar("search: %s", get_file_buffer(focused_window)->search_term); -} - -void -search_next(const Arg* arg) -{ - int new_offset = buffer_seek_string_wrap(focused_window, focused_window->cursor_offset+1, - get_file_buffer(focused_window)->search_term); - if (new_offset < 0) { - writef_to_status_bar("no results for \"%s\"", get_file_buffer(focused_window)->search_term); - return; - } else if (focused_window->cursor_offset > new_offset) { - writef_to_status_bar("search wrapped"); - } - focused_window->cursor_offset = new_offset; -} - -void -search_previous(const Arg* arg) -{ - int new_offset = buffer_seek_string_wrap_backwards(focused_window, focused_window->cursor_offset-1, - get_file_buffer(focused_window)->search_term); - if (new_offset < 0) { - writef_to_status_bar("no results for \"%s\"", get_file_buffer(focused_window)->search_term); - return; - } else if (focused_window->cursor_offset < new_offset) { - writef_to_status_bar("search wrapped"); - } - focused_window->cursor_offset = new_offset; -} - -void -search_for_buffer(const Arg* arg) -{ - if (focused_window->mode != WINDOW_BUFFER_NORMAL) - return; - *focused_node->search = 0; - focused_node->selected = 0; - focused_window->mode = WINDOW_BUFFER_SEARCH_BUFFERS; -} - -void -search_keyword_in_buffers(const Arg* arg) -{ - if (focused_window->mode != WINDOW_BUFFER_NORMAL) - return; - *focused_node->search = 0; - focused_node->selected = 0; - focused_window->mode = WINDOW_BUFFER_KEYWORD_ALL_BUFFERS; -} - - -void -open_file_browser(const Arg* arg) -{ - int last_fb = focused_window->buffer_index; - struct file_buffer* fb = get_file_buffer(focused_window); - - char* path = file_path_get_path(fb->file_path); - *focused_window = window_buffer_new(new_file_buffer_entry(path)); - focused_window->cursor_col = last_fb; - free(path); -} - -void -buffer_kill(const Arg* arg) -{ - destroy_file_buffer_entry(focused_node, &root_node); -} - -////////////////////////////////////////////////////////7 -// Callbacks -// +void(*buffer_written_to_screen_callback)(struct window_buffer* buf, int offset_start, int offset_end, int minx, int miny, int maxx, int maxy) = NULL; +char*(*new_line_draw)(struct window_buffer* buf, int y, int lines_left, int minx, int maxx, Glyph* attr) = NULL; +void(*buffer_written_to_file_callback)(struct file_buffer* fb) = NULL; +int(*write_status_bar)(struct window_buffer* buf, int minx, int maxx, int cx, int cy, char line[LINE_MAX_LEN], Glyph* g) = default_status_bar; void keep_cursor_col(struct window_buffer* buf, enum cursor_reason callback_reason) @@ -439,25 +146,13 @@ cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason) keep_cursor_col(buf, callback_reason); move_selection(buf, callback_reason); - //printf("moved to: %d | reason: %d\n", buf->cursor_offset, callback_reason); -} - -void -buffer_content_callback(struct file_buffer* buffer, int offset, enum buffer_content_reason reason) -{ - buffer_add_to_undo(buffer, offset, reason); + //writef_to_status_bar("moved to: %d | reason: %d\n", buf->cursor_offset, callback_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; - } - } + check_shortcuts(keysym, modkey); // default actions @@ -477,10 +172,11 @@ keypress_actions(KeySym keysym, int modkey) offset = focused_window->cursor_offset; - // FALLTHROUGH + goto skip_delete_remove_selection; case XK_Delete: if (delete_selection(fb)) return 1; +skip_delete_remove_selection: move = buffer_remove(fb, offset, 1, 0, 0); window_move_all_cursors_on_same_buf(&root_node, focused_node, focused_window->buffer_index, offset, @@ -516,14 +212,14 @@ keypress_actions(KeySym keysym, int modkey) return 1; } case XK_Page_Down: - buffer_move_lines(focused_window, (term.row-1) / 2, 0); + buffer_move_lines(focused_window, (focused_node->maxy - focused_node->miny) / 2, 0); buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); - focused_window->y_scroll += (term.row-1) / 2; + focused_window->y_scroll += (focused_node->maxy - focused_node->miny) / 2; return 1; case XK_Page_Up: - buffer_move_lines(focused_window, -((term.row-1) / 2), 0); + buffer_move_lines(focused_window, -((focused_node->maxy - focused_node->miny) / 2), 0); buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); - focused_window->y_scroll -= (term.row-1) / 2; + focused_window->y_scroll -= (focused_node->maxy - focused_node->miny) / 2; return 1; case XK_Tab: buffer_insert(fb, "\t", 1, offset, 0); diff --git a/config.h b/config.h @@ -0,0 +1,52 @@ +/* + * ***THIS FILE IS NOT MADE FOR CUSTOMISATION, FOR CUSTOMISATION USE config.c*** + * + * Unless you are adding new shared globals or heavily modifying the program, + * this file just expands global symbols that the rest of the files + * rely on to configure themselves. + * + * this file can also be used for reference if you are unsure what + * variables you might be missing from your own config. + */ + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#include "se.h" +#include "x.h" +#include "extension.h" + +extern struct 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; + +extern int border_px; +extern float cw_scale; +extern float ch_scale; +extern char* fontconfig; +extern const char* const colors[]; +extern struct 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; + +extern unsigned int tabspaces; +extern unsigned int default_indent_len; // 0 means tab +extern int wrap_buffer; + +// see extension.h and extension.c +extern struct extension_meta* extensions; + +#endif // CONFIG_H_ diff --git a/config.mk b/config.mk @@ -21,8 +21,8 @@ LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ `$(PKG_CONFIG) --libs freetype2` # flags -SECPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -SECFLAGS = $(INCS) $(SECPPFLAGS) $(CPPFLAGS) -g -Wall -Wpedantic -Os +SECPPFLAGS = -D_XOPEN_SOURCE=600 +SECFLAGS = $(INCS) $(SECPPFLAGS) $(CPPFLAGS) -Wall -Wpedantic -O0 -g SELDFLAGS = $(LIBS) $(LDFLAGS) # OpenBSD: diff --git a/extension.h b/extension.h @@ -0,0 +1,89 @@ +#ifndef EXTENSION_H_ +#define EXTENSION_H_ + +#include "se.h" + +struct extension { + char* name; + char* description; + +// standard functions + void(*init)(struct extension* self); + void(*frame)(void); + void(*enable)(void); + void(*disable)(void); + +///////////////////////////////////////// +// callbacks +// +// A non-zero return value from an extension callback +// will not call any further extensions that have the same callback. +// This is necearry for any of the callbacks that have "return" values, +// namely wb_new_line_draw, wb_write_status_bar +// and wn_custom_window_keypress_override +// + + +// window buffer + int(*wb_cursor_movement)(struct window_buffer* wb, enum cursor_reason reason); + + /////////////////////////////////// + // string will be drawn at the start of a new line + int(*wb_new_line_draw)(char** string, struct window_buffer* wb, int y, int lines_left, int minx, int maxx, struct glyph* attr); + + /////////////////////////////////// + // write_again means the line will be written and the callback will + // be called again with minx at the end line + int(*wb_write_status_bar)(int* write_again, struct window_buffer* wb, int minx, int maxx, int cx, int cy, char line[LINE_MAX_LEN], struct glyph* g); + + +// file buffer + int(*fb_new_file_opened)(struct file_buffer* fb); + + int(*fb_paste)(struct file_buffer* fb, char* data, int len); + int(*keypress)(KeySym keycode, int modkey, const char* buf, int len); + int(*fb_written_to_file)(struct file_buffer* fb); + + /////////////////////////////////// + // For undo functionality you can use this function to update the undo buffers + // there is provided the fb_add_to_undo function, but you may implement your own + // see buffer.h/buffer.c fb_add_to_undo() + int(*fb_contents_updated)(struct file_buffer* fb, int offset, enum buffer_content_reason reason); + + +// window node + int(*wn_custom_window_draw)(struct window_split_node* wn); + int(*window_written_to_screen)(struct window_split_node* wn, const int offset_start, const int offset_end, uint8_t* move_buffer, const int move_buffer_len); + int(*wn_custom_window_keypress_override)(int* skip_keypress_callback, struct window_split_node* wn, KeySym keycode, int modkey, const char* buf, int len); +}; + +struct extension_meta { + struct extension e; + size_t enabled; + size_t end; +}; + +#define extension_callback_exists(_callback, ...) \ + do { \ + if (!extensions) \ + break; \ + for (int _iterator = 0; !extensions[_iterator].end; _iterator++) { \ + if (extensions[_iterator].e._callback && extensions[_iterator].enabled) { \ + __VA_ARGS__ \ + break; \ + } \ + } \ + } while (0) + +#define call_extension(_callback, ...) \ + do { \ + if (!extensions) \ + break; \ + for (int _iterator = 0; !extensions[_iterator].end; _iterator++) \ + if (extensions[_iterator].e._callback && extensions[_iterator].enabled) \ + if (extensions[_iterator].e._callback(__VA_ARGS__)) \ + break; \ + } while (0) + + +#endif // EXTENSION_H_ diff --git a/extensions/README.txt b/extensions/README.txt @@ -0,0 +1,3 @@ +TODO: create readme in the different folders + +TODO: make new default configs diff --git a/extensions/default_shortcuts.h b/extensions/default_shortcuts.h @@ -0,0 +1,262 @@ +static void numlock(const shortcut_arg* arg); +static void zoom(const shortcut_arg* arg); +static void zoomabs(const shortcut_arg* arg); +static void zoomreset(const shortcut_arg* arg); + +static void window_split(const shortcut_arg* arg); +static void window_resize(const shortcut_arg* arg); +static void window_delete(const shortcut_arg* arg); +static void window_change(const shortcut_arg* arg); + +static void cursor_move_x_relative(const shortcut_arg* arg); +static void cursor_move_y_relative(const shortcut_arg* arg); +static void move_cursor_to_offset(const shortcut_arg* arg); +static void move_cursor_to_end_of_buffer(const shortcut_arg* arg); + +static void swap_to_next_file_buffer(const shortcut_arg* arg); +static void save_buffer(const shortcut_arg* arg); +static void buffer_kill(const shortcut_arg* arg); + +static void clipboard_copy(const shortcut_arg* arg); +static void clipboard_paste(const shortcut_arg* arg); +static void toggle_selection(const shortcut_arg* arg); +static void undo(const shortcut_arg* arg); +static void redo(const shortcut_arg* arg); + +static void search(const shortcut_arg* arg); +static void search_next(const shortcut_arg* arg); +static void search_previous(const shortcut_arg* arg); +static void search_for_buffer(const shortcut_arg* arg); +static void search_keyword_in_buffers(const shortcut_arg* arg); +static void open_file_browser(const shortcut_arg* arg); + +///////////////////////////////////////////////// +// function implementations +// + +void +numlock(const shortcut_arg* arg) +{ + win.mode ^= MODE_NUMLOCK; +} + +void window_split(const shortcut_arg* arg) +{ + window_node_split(focused_node, 0.5, arg->i); +#if 1 + if (focused_node->node2) { + focused_node = focused_node->node2; + focused_window = &focused_node->window; + } +#else + if (focused_node->node1) { + focused_node = focused_node->node1; + focused_window = &focused_node->window; + } +#endif +} + +void window_resize(const shortcut_arg* arg) +{ + float amount = (arg->i == MOVE_RIGHT || arg->i == MOVE_LEFT) ? 0.1f : 0.05f; + window_node_resize(focused_node, arg->i, amount); +} + +void window_delete(const shortcut_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 shortcut_arg* arg) +{ + focused_node = window_switch_to_window(focused_node, arg->i); + focused_window = &focused_node->window; +} + +void +zoom(const shortcut_arg* arg) +{ + shortcut_arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const shortcut_arg* arg) +{ + xunloadfonts(); + xloadfonts(fontconfig, arg->f); + cresize(0, 0); + xhints(); +} + +void +zoomreset(const shortcut_arg* arg) +{ + shortcut_arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +cursor_move_x_relative(const shortcut_arg* arg) +{ + if (focused_window->mode != WINDOW_BUFFER_FILE_BROWSER) + buffer_move_on_line(focused_window, arg->i, CURSOR_RIGHT_LEFT_MOVEMENT); +} + +void +cursor_move_y_relative(const shortcut_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 +swap_to_next_file_buffer(const shortcut_arg* arg) +{ + focused_window->buffer_index++; +} + +void +save_buffer(const shortcut_arg* arg) +{ + buffer_write_to_filepath(get_file_buffer(focused_window)); +} + +void +toggle_selection(const shortcut_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 shortcut_arg* arg) +{ + focused_window->cursor_offset = arg->i; +} + +void +move_cursor_to_end_of_buffer(const shortcut_arg* arg) +{ + focused_window->cursor_offset = get_file_buffer(focused_window)->len-1; +} + +void +clipboard_copy(const shortcut_arg* arg) +{ + struct file_buffer* fb = get_file_buffer(focused_window); + int len; + char* buf = buffer_get_selection(fb, &len); + set_clipboard_copy(buf, len); + + buffer_move_cursor_to_selection_start(focused_window); + fb->mode &= ~BUFFER_SELECTION_ON; +} + +void +clipboard_paste(const shortcut_arg* arg) +{ + insert_clipboard_at_cursor(); +} + +void +undo(const shortcut_arg* arg) +{ + buffer_undo(get_file_buffer(focused_window)); +} + +void +redo(const shortcut_arg* arg) +{ + buffer_redo(get_file_buffer(focused_window)); +} + +void +search(const shortcut_arg* arg) +{ + get_file_buffer(focused_window)->mode &= ~BUFFER_SEARCH_BLOCKING_IDLE; + get_file_buffer(focused_window)->mode |= BUFFER_SEARCH_BLOCKING; + writef_to_status_bar("search: %s", get_file_buffer(focused_window)->search_term); +} + +void +search_next(const shortcut_arg* arg) +{ + int new_offset = buffer_seek_string_wrap(focused_window, focused_window->cursor_offset+1, + get_file_buffer(focused_window)->search_term); + if (new_offset < 0) { + writef_to_status_bar("no results for \"%s\"", get_file_buffer(focused_window)->search_term); + return; + } else if (focused_window->cursor_offset > new_offset) { + writef_to_status_bar("search wrapped"); + } + focused_window->cursor_offset = new_offset; +} + +void +search_previous(const shortcut_arg* arg) +{ + int new_offset = buffer_seek_string_wrap_backwards(focused_window, focused_window->cursor_offset-1, + get_file_buffer(focused_window)->search_term); + if (new_offset < 0) { + writef_to_status_bar("no results for \"%s\"", get_file_buffer(focused_window)->search_term); + return; + } else if (focused_window->cursor_offset < new_offset) { + writef_to_status_bar("search wrapped"); + } + focused_window->cursor_offset = new_offset; +} + +void +search_for_buffer(const shortcut_arg* arg) +{ + if (focused_window->mode != WINDOW_BUFFER_NORMAL) + return; + *focused_node->search = 0; + focused_node->selected = 0; + focused_window->mode = WINDOW_BUFFER_SEARCH_BUFFERS; +} + +void +search_keyword_in_buffers(const shortcut_arg* arg) +{ + if (focused_window->mode != WINDOW_BUFFER_NORMAL) + return; + *focused_node->search = 0; + focused_node->selected = 0; + focused_window->mode = WINDOW_BUFFER_KEYWORD_ALL_BUFFERS; +} + + +void +open_file_browser(const shortcut_arg* arg) +{ + int last_fb = focused_window->buffer_index; + struct file_buffer* fb = get_file_buffer(focused_window); + + char* path = file_path_get_path(fb->file_path); + *focused_window = window_buffer_new(new_file_buffer_entry(path)); + focused_window->cursor_col = last_fb; + free(path); +} + +void +buffer_kill(const shortcut_arg* arg) +{ + destroy_file_buffer_entry(focused_node, &root_node); +} diff --git a/extensions/default_status_bar.h b/extensions/default_status_bar.h @@ -0,0 +1,58 @@ +#include <math.h> + +extern struct window_buffer* focused_window; + +static int default_status_bar_callback(int* write_again, struct window_buffer* buf, int minx, int maxx, int cx, int cy, char line[LINE_MAX_LEN], struct glyph* g); + +static const struct extension default_status_bar = { + .wb_write_status_bar = default_status_bar_callback +}; + +int +default_status_bar_callback(int* write_again, struct window_buffer* buf, int minx, int maxx, int cx, int cy, char line[LINE_MAX_LEN], struct glyph* g) +{ + static int count = 0; + if (!buf) { + count = 0; + return 0; + } + + struct file_buffer* fb = get_fb(buf); + switch (count) { + const char* name; + int percent; + case 0: + if (fb->mode & FB_SEARCH_BLOCKING_IDLE) { + int before; + int search_count = fb_count_string_instances(fb, fb->search_term, focused_window->cursor_offset, &before); + snprintf(line, LINE_MAX_LEN, " %d/%d", before, search_count); + } + break; + case 1: + snprintf(line, LINE_MAX_LEN, " %dk ", fb->len/1000); + break; + case 2: + g->fg = path_color; + char* path = file_path_get_path(fb->file_path); + snprintf(line, LINE_MAX_LEN, "%s", path); + free(path); + break; + case 3: + name = strrchr(fb->file_path, '/')+1; + if (name) + snprintf(line, LINE_MAX_LEN, "%s", name); + break; + case 4: + percent = ceilf(((float)(buf->cursor_offset)/(float)fb->len)*100.0f); + LIMIT(percent, 0, 100); + snprintf(line, LINE_MAX_LEN, " %d:%d %d%%" , cy+1, cx, percent); + break; + case 5: + count = 0; + *write_again = 0; + return 0; + } + count++; + *write_again = 1; + return 0; +} diff --git a/extensions/keep_cursor_col.h b/extensions/keep_cursor_col.h @@ -0,0 +1,18 @@ +#ifndef KEEP_CURSOR_COL_H_ +#define KEEP_CURSOR_COL_H_ + +static int +keep_cursor_col_callback(struct window_buffer* buf, enum cursor_reason callback_reason) +{ + if (callback_reason == CURSOR_COMMAND_MOVEMENT || callback_reason == CURSOR_RIGHT_LEFT_MOVEMENT) { + int y, tmp; + fb_offset_to_xy(get_fb(buf), buf->cursor_offset, 0, buf->y_scroll, &buf->cursor_col, &y, &tmp); + } + return 0; +} + +static const struct extension keep_cursor_col = { + .wb_cursor_movement = keep_cursor_col_callback +}; + +#endif // KEEP_CURSOR_COL_H_ diff --git a/extensions/line_count.h b/extensions/line_count.h @@ -0,0 +1,21 @@ +static char* line_count(struct window_buffer* wb, int y, int lines_left, int minx, int maxx, struct glyph* attr) +{ + static char line[LINE_MAX_LEN]; + int tmp, tmp2, cy; + fb_offset_to_xy(get_fb(wb), wb->cursor_offset, 0, wb->y_scroll, &tmp, &cy, &tmp2); + + y += wb->y_scroll + 1; + cy += wb->y_scroll + 1; + + snprintf(line, LINE_MAX_LEN, "%3d ", y); + + if (y == cy) { + attr->fg = yellow; + attr->bg = alternate_bg_bright; + } + + return line; +} + +// add with this: +// char*(*wb_new_line_draw)(struct window_buffer* wb, int y, int lines_left, int minx, int maxx, Glyph* attr) = line_count; diff --git a/extensions/line_count_relative.h b/extensions/line_count_relative.h @@ -0,0 +1,28 @@ +static char* line_count_relative(struct window_buffer* wb, int y, int lines_left, int minx, int maxx, struct glyph* attr) +{ + static char line[LINE_MAX_LEN]; + int tmp, tmp2, cy; + fb_offset_to_xy(get_fb(wb), wb->cursor_offset, 0, wb->y_scroll, &tmp, &cy, &tmp2); + + cy += wb->y_scroll + 1; + y += wb->y_scroll + 1; + + if (y == cy) { + attr->fg = yellow; + attr->bg = alternate_bg_bright; + snprintf(line, LINE_MAX_LEN, "%3d ", y); + } else { + int tmp_y = y; + char* offset = line; + while(tmp_y >= 1000 && offset - line < LINE_MAX_LEN) { + *offset++ = ' '; + tmp_y /= 10; + } + snprintf(offset, LINE_MAX_LEN - (offset - line), "%3d ", abs(cy - y)); + } + + return line; +} + +// add with this +// char*(*wb_new_line_draw)(struct window_buffer* wb, int y, int lines_left, int minx, int maxx, struct glyph* attr) = line_count_relative; diff --git a/extensions/move_selection_with_cursor.h b/extensions/move_selection_with_cursor.h @@ -0,0 +1,32 @@ +#ifndef MOVE_SELECTION_WITH_CURSOR_H_ +#define MOVE_SELECTION_WITH_CURSOR_H_ + +static int +move_selection(struct window_buffer* wb, enum cursor_reason callback_reason) +{ + struct file_buffer* fb = get_fb(wb); + if (fb->mode & FB_SELECTION_ON) { + if (fb->mode & FB_LINE_SELECT) { + int twice = 2; + while (twice--) { + if (fb_is_selection_start_top_left(fb)) { + fb->s2o = fb_seek_char(fb, wb->cursor_offset, '\n'); + fb->s1o = fb_seek_char_backwards(fb, fb->s1o, '\n'); + } else { + fb->s2o = fb_seek_char_backwards(fb, wb->cursor_offset, '\n'); + fb->s1o = fb_seek_char(fb, fb->s1o, '\n'); + } + } + } else { + fb->s2o = wb->cursor_offset; + } + + } + return 0; +} + +static const struct extension move_selection_with_cursor = { + .wb_cursor_movement = move_selection, +}; + +#endif // MOVE_SELECTION_WITH_CURSOR_H_ diff --git a/extensions/shortcuts.h b/extensions/shortcuts.h @@ -0,0 +1,47 @@ +typedef union { + int i; + unsigned int ui; + float f; + int vec2i[2]; + const void *v; + const char *s; +} shortcut_arg; + + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const shortcut_arg* arg); + const shortcut_arg arg; +} Shortcut; + +#define shortcuts const Shortcut shortcut_array[] + +#define check_shortcuts(_ksym, _modkey) \ + do { \ + for (int i = 0; i < LEN(shortcut_array); i++) { \ + if (_ksym == shortcut_array[i].keysym && match(shortcut_array[i].mod, _modkey)) { \ + shortcut_array[i].func(&(shortcut_array[i].arg)); \ + return 1; \ + } \ + } \ + }while(0) + +/* +implement with: + +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} }, +}; + +... + +int +keypress_actions(KeySym keysym, int modkey) +{ + check_shortcuts(keysym, modkey); + ... +*/ diff --git a/extensions/startup_message.h b/extensions/startup_message.h @@ -0,0 +1,21 @@ +#ifndef STARTUP_MESSAGE_H_ +#define STARTUP_MESSAGE_H_ + +static const char* const welcome[] = {"Welcome to se!", "Good day, Human", "Happy Coding", "se: the Simple Editor", + "Time to get some progress done!", "Ready for combat", "Initialising...Done", "loaded in %%d seconds", + "Fun fact: vscode has over two times as many lines describing dependencies than se has in total", + "You look based", "Another day, another bug to fix", "Who needs a mouse ¯\\_(ツ)_/¯", "grrgrrggghhaaaaaa (╯°□ °)╯︵ ┻━┻", + "┬┴┬┤(・_├┬┴┬┴┬┴┬┤ʖ ͡°) ├┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴", "ʰᵉˡˡᵒ"}; + +static void +choose_random_message() +{ + writef_to_status_bar(welcome[rand() % LEN(welcome)]); +} + +static const struct extension startup_message = { + .enable = choose_random_message +}; + + +#endif // STARTUP_MESSAGE_H_ diff --git a/extensions/syntax/c.h b/extensions/syntax/c.h @@ -0,0 +1,125 @@ +#include "handy_defines.h" + +#ifdef macro_color +#define color_macro(_str) {COLOR_WORD,{_str}, macro_color} +#endif + +const struct syntax_scheme_entry c_syntax[] = { + // Coloring type arguments Color + + // strings +#ifdef string_color + {COLOR_AROUND_TO_LINE, {"\"", "\""}, string_color}, + {COLOR_STR, {"''"}, normal_color}, + {COLOR_AROUND_TO_LINE, {"'", "'"}, string_color}, + {COLOR_INSIDE_TO_LINE, {"#include <", ">"}, string_color}, + {COLOR_INSIDE_TO_LINE, {"#include<", ">"}, string_color}, +#endif + // comments +#ifdef comment_color + {COLOR_AROUND, {"/*", "*/"}, comment_color}, + {COLOR_AROUND, {"//", "\n"}, comment_color}, +#endif + // macros +#ifdef macro_color +#ifdef 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}, +#endif // constants_color +#ifdef string_color + {COLOR_INSIDE_TO_LINE, {"#error ", "\n"}, string_color}, +#endif // string_color + {COLOR_WORD_STARTING_WITH_STR, {"#"}, {.fg = yellow, .mode = ATTR_BOLD}}, + color_macro("sizeof"), color_macro("alignof"), + color_macro("offsetof"), + {COLOR_STR_AFTER_WORD, {"defined"}, constants_color}, + color_macro("defined"), +#endif + // operators +#ifdef operator_color + {COLOR_STR, {"!="}, normal_color}, + {COLOR_STR, {"!"}, operator_color}, + {COLOR_STR, {"~"}, operator_color}, + {COLOR_STR, {"?"}, operator_color}, +#endif + // keywords +#ifdef keyword_color + {COLOR_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"), +#endif + // functions +#ifdef function_color + {COLOR_WORD_BEFORE_STR, {"("}, function_color}, +#endif +#ifdef constants_color + {COLOR_UPPER_CASE_WORD, {0}, constants_color}, +#endif + // types +#ifdef type_color + 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}, +#endif + // numbers +#ifdef number_color + color_numbers(), +#endif +}; + +#define c_word_seperators default_word_seperators + +const struct indent_scheme_entry c_indent[] = { + {INDENT_REMOVE, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, 0, {"}", "{"} }, + {INDENT_NEW, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, -1, {"{", "}"} }, + + {INDENT_NEW, INDENT_LINE_ONLY_CONTAINS_STR, -1, {"("} }, + {INDENT_KEEP_OPENER, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, -1, {"(", ")"} }, + {INDENT_REMOVE, INDENT_LINE_ONLY_CONTAINS_STR, 0, {")"} }, + {INDENT_REMOVE, INDENT_LINE_ONLY_CONTAINS_STR, 0, {");"} }, + {INDENT_RETURN_TO_OPENER_BASE_INDENT, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, -1, {")", "("} }, + + {INDENT_NEW, INDENT_LINE_ONLY_CONTAINS_STR, -1, {"["} }, + {INDENT_REMOVE, INDENT_LINE_ONLY_CONTAINS_STR, 0, {"]"} }, + {INDENT_REMOVE, INDENT_LINE_ONLY_CONTAINS_STR, 0, {"];"} }, + {INDENT_KEEP_OPENER, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, -1, {"[", "]"} }, + {INDENT_RETURN_TO_OPENER_BASE_INDENT, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, -1, {"]", "["} }, + + {INDENT_REMOVE, INDENT_LINE_ENDS_WITH_STR, 0, {":"} }, + {INDENT_NEW, INDENT_LINE_ENDS_WITH_STR, -1, {":"} }, + {INDENT_KEEP, INDENT_LINE_ENDS_WITH_STR, -1, {";"} }, + {INDENT_KEEP, INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, 0, {"{", "}"} }, + {INDENT_NEW, INDENT_LINE_CONTAINS_WORD, -1, {"else"} }, + {INDENT_NEW, INDENT_LINE_CONTAINS_WORD, -1, {"if"} }, + {INDENT_NEW, INDENT_LINE_CONTAINS_WORD, -1, {"for"} }, + {INDENT_NEW, INDENT_LINE_CONTAINS_WORD, -1, {"while"} }, + +}; diff --git a/extensions/syntax/gd.h b/extensions/syntax/gd.h @@ -0,0 +1,99 @@ +#include "handy_defines.h" + +const struct syntax_scheme_entry gd_syntax[] = { + // Coloring type arguments Color + + // strings +#ifdef string_color + {COLOR_AROUND_TO_LINE, {"\"", "\""}, string_color}, + {COLOR_AROUND, {"\"\"\"", "\"\"\""}, string_color}, + {COLOR_AROUND_TO_LINE, {"'", "'"}, string_color}, + {COLOR_INSIDE_TO_LINE, {"$", "."}, string_color}, + {COLOR_INSIDE_TO_LINE, {"$", " "}, string_color}, + {COLOR_INSIDE_TO_LINE, {"$", "("}, string_color}, + {COLOR_INSIDE_TO_LINE, {"$", "["}, string_color}, + {COLOR_INSIDE_TO_LINE, {"$", "\n"}, string_color}, + {COLOR_STR, {"$"}, string_color}, +#endif + // comments +#ifdef comment_color + {COLOR_AROUND, {"#", "\n"}, comment_color}, +#endif + // operators +#ifdef operator_color + {COLOR_STR, {"!="}, normal_color}, + {COLOR_STR, {"!"}, operator_color}, +#endif + // constants +#ifdef constants_color + {COLOR_UPPER_CASE_WORD, {0}, constants_color}, + {COLOR_WORD, {"PI"}, constants_color}, + {COLOR_WORD, {"TAU"}, constants_color}, + {COLOR_WORD, {"INF"}, constants_color}, + {COLOR_WORD, {"NAN"}, constants_color}, + {COLOR_WORD, {"null"}, constants_color}, + {COLOR_WORD, {"true"}, constants_color}, + {COLOR_WORD, {"false"}, constants_color}, + {COLOR_STR_AFTER_WORD, {"const"}, constants_color}, +#endif + // keywords +#ifdef keyword_color + color_keyword("extends"), color_keyword("class_name"), + color_keyword("var"), color_keyword("const"), + color_keyword("enum"), color_keyword("func"), + color_keyword("if"), color_keyword("else"), + color_keyword("elif"), color_keyword("for"), + color_keyword("while"), color_keyword("in"), + color_keyword("return"), color_keyword("class"), + color_keyword("pass"), color_keyword("continue"), + color_keyword("break"), color_keyword("is"), + color_keyword("as"), color_keyword("self"), + color_keyword("tool"), color_keyword("signal"), + color_keyword("static"), color_keyword("onready"), + color_keyword("export"), color_keyword("setget"), + color_keyword("breakpoint"), color_keyword("yield"), + color_keyword("assert"), color_keyword("preload"), + color_keyword("remote"), color_keyword("master"), + color_keyword("puppet"), color_keyword("remotesync"), + color_keyword("mastersync"), color_keyword("puppetsync"), + color_keyword("and"), color_keyword("or"), +#endif + // functions +#ifdef function_color + {COLOR_WORD_INSIDE, {"func ", "("}, macro_color}, + {COLOR_WORD_INSIDE, {"class ", ":"}, macro_color}, + {COLOR_WORD_BEFORE_STR, {"("}, function_color}, +#endif + // types +#ifdef type_color + color_type("bool"), color_type("int"), + color_type("float"), color_type("String"), + color_type("Vector2"), color_type("Rect2"), + color_type("Vector3"), color_type("Transform2D"), + color_type("Plane"), color_type("Quat"), + color_type("AABB"), color_type("Basis"), + color_type("Transform"), color_type("Color"), + color_type("NodePath"), color_type("RID"), + color_type("Object"), color_type("Array"), + color_type("PoolByteArray"), color_type("PoolIntArray"), + color_type("PoolRealArray"), color_type("PoolStringArray"), + color_type("PoolVector2Array"), color_type("PoolVector3Array"), + color_type("PoolColorArray"), color_type("Dictionary"), + color_type("Dictionary"), color_type("Node"), + color_type("void"), +#endif + // numbers +#ifdef number_color + color_numbers(), +#endif +}; + +#define gd_word_seperators default_word_seperators + +const struct indent_scheme_entry gd_indent[] = { + {INDENT_REMOVE, INDENT_LINE_CONTAINS_WORD, -1, {"return"}}, + {INDENT_REMOVE, INDENT_LINE_CONTAINS_WORD, -1, {"break"}}, + {INDENT_REMOVE, INDENT_LINE_CONTAINS_WORD, -1, {"continue"}}, + {INDENT_REMOVE, INDENT_LINE_CONTAINS_WORD, -1, {"pass"}}, + {INDENT_NEW, INDENT_LINE_ENDS_WITH_STR, -1, {":"}}, +}; diff --git a/extensions/syntax/handy_defines.h b/extensions/syntax/handy_defines.h @@ -0,0 +1,32 @@ +#ifndef HANDY_DEFINES_H_ +#define HANDY_DEFINES_H_ + +#define default_word_seperators "., '\n\t*+-/%!~<>=(){}[]\"^&|\\`´?:;" + +#ifdef keyword_color +#define color_keyword(_str) {COLOR_WORD,{_str}, keyword_color} +#endif + +#ifdef type_color +#define color_type(_str) {COLOR_WORD,{_str}, type_color} +#endif + +#ifdef number_color +#define color_number(_num) \ + {COLOR_WORD_STARTING_WITH_STR, {_num}, number_color}, \ + {COLOR_WORD_ENDING_WITH_STR, {_num".f"},number_color} +#endif + +#define 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") + +#endif // HANDY_DEFINES_H_ diff --git a/extensions/syntax/schemes/gruvbox.h b/extensions/syntax/schemes/gruvbox.h @@ -0,0 +1,71 @@ +#ifndef _color_scheme_ +#define _color_scheme_ + +// 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 +struct glyph default_attributes = {.fg = fg, .bg = bg}; +unsigned int alternate_bg_bright = sel; +unsigned int alternate_bg_dark = bg0_h; + +unsigned int cursor_fg = fg; +unsigned int cursor_bg = bg; +unsigned int mouse_line_bg = line; + +unsigned int selection_bg = sel; +unsigned int highlight_color = yellow; +unsigned int path_color = teal; + +unsigned int error_color = red; +unsigned int warning_color = yellow; +unsigned int ok_color = green; + +#define normal_color {.fg = fg} +#define string_color {.fg = gray} +#define comment_color {.fg = gray} +#define type_color {.fg = teal} +#define keyword_color {.fg = green} +#define macro_color {.fg = yellow} +#define operator_color {.fg = yellow, .mode = ATTR_BOLD} +#define constants_color {.fg = dark_green} +#define number_color {.fg = gray} +#define function_color {.fg = aqua} + +#endif // _color_scheme_ diff --git a/extensions/syntax/syntax.h b/extensions/syntax/syntax.h @@ -0,0 +1,646 @@ +#ifndef SYNTAX_H_ +#define SYNTAX_H_ + +#include "../../se.h" +#include "../../config.h" +#include "../../extension.h" +#include <ctype.h> + + +/////////////////////////////////// +// the user must fill this inn +// null terminated +extern const struct syntax_scheme* syntax_schemes; + +static int fb_set_syntax_scheme(struct file_buffer* fb); +static int apply_syntax(struct window_split_node* wn, const int offset_start, const int offset_end, uint8_t* move_buffer, const int move_buffer_len); + +static const struct extension syntax_e = { + .fb_new_file_opened = fb_set_syntax_scheme, + .window_written_to_screen = apply_syntax +}; + +#define UPPER_CASE_WORD_MIN_LEN 3 + +enum syntax_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 syntax_scheme_entry { + const enum syntax_scheme_mode mode; + const struct delimiter arg; + const struct glyph attr; +}; + +// TODO: INDENT_LINE_CONTAINS_STR_AND_STR +enum indent_scheme_mode { + INDENT_LINE_ENDS_WITH_STR, + INDENT_LINE_DOES_NOT_END_WITH_STR, + + INDENT_LINE_CONTAINS_WORD, + INDENT_LINE_ONLY_CONTAINS_STR, + // neds two strings + INDENT_LINE_CONTAINS_STR_MORE_THAN_STR, +}; + +enum indent_scheme_type { + INDENT_REMOVE = -1, + INDENT_KEEP_OPENER = 0, + INDENT_NEW = 1, + + INDENT_KEEP, + // needs two strings, requires closer to be string 1 and opener to be string two + INDENT_RETURN_TO_OPENER_BASE_INDENT, +}; + +struct indent_scheme_entry { + const enum indent_scheme_type type; + const enum indent_scheme_mode mode; + const unsigned int line_offset; + const struct delimiter arg; +}; + +struct syntax_scheme { + const char* file_ending; + const char* word_seperators; + + const struct syntax_scheme_entry* entries; + const int entry_count; + + const struct indent_scheme_entry* indents; + const int indent_count; +}; + + +static int fb_auto_indent(struct file_buffer* fb, int offset); + +static void do_syntax_scheme(struct file_buffer* fb, const struct syntax_scheme* cs, int offset); + +static void whitespace_count_to_indent_amount(int indent_len, int count, int* indent_count, int* extra_spaces); +static int get_line_leading_whitespace_count(const char* line); +static int get_line_relative_offset(struct file_buffer* fb, int offset, int count); + +static const struct syntax_scheme* +fb_get_syntax_scheme(struct file_buffer* fb) +{ + return fb->syntax_index < 0 ? NULL : &syntax_schemes[fb->syntax_index]; +} + +static int +fb_set_syntax_scheme(struct file_buffer* fb) +{ + for (int i = 0; syntax_schemes[i].file_ending; i++) + if (is_file_type(fb->file_path, syntax_schemes[i].file_ending)) + fb->syntax_index = i; + return 0; +} + +static int +apply_syntax(struct window_split_node* wn, const int offset_start, const int offset_end, uint8_t* move_buffer, const int move_buffer_len) +{ + global_attr = default_attributes; + struct window_buffer* wb = &wn->wb; + struct file_buffer* fb = get_fb(wb); + const struct syntax_scheme* cs = fb_get_syntax_scheme(fb); + if (!cs) + return 0; + + // clear state + do_syntax_scheme(NULL, &(struct syntax_scheme){0}, 0); + + // search backwards to find multi-line syntax highlighting + for (int i = 0; i < cs->entry_count; i++) { + const struct syntax_scheme_entry cse = cs->entries[i]; + if (cse.mode == COLOR_AROUND || cse.mode == COLOR_INSIDE) { + int offset = 0; + int count = 0; + int start_len = strlen(cse.arg.start); + while((offset = fb_seek_string(fb, offset, cse.arg.start)) >= 0) { + offset += start_len; + if (offset >= offset_start) + break; + count++; + } + + if (strcmp(cse.arg.start, cse.arg.end) != 0) { + int end_len = strlen(cse.arg.end); + offset = 0; + while((offset = fb_seek_string(fb, offset, cse.arg.end)) >= 0) { + offset += end_len; + if (offset >= offset_start) + break; + count--; + } + } + if (count > 0) { + offset = fb_seek_string_backwards(fb, offset_start, cse.arg.start); + do_syntax_scheme(fb, cs, offset); + break; + } + } + } + + int x = wn->minx + move_buffer[0], y = wn->miny; + int move_buffer_index = 0; + int charsize = 1; + for(int i = offset_start; i < offset_end && y < wn->maxy + && move_buffer_index < move_buffer_len; i += charsize) { + do_syntax_scheme(fb, cs, i); + screen_set_attr(x, y)->fg = global_attr.fg; + screen_set_attr(x, y)->bg = global_attr.bg; + + uint8_t amount = move_buffer[move_buffer_index]; + if (amount & (1<<7)) { + x = wn->minx; + y++; + amount &= ~(1<<7); + } + x += amount; + + rune_t u; + charsize = utf8_decode_buffer(fb->contents + i, i - offset_start, &u); + if (charsize == 0) + charsize = 1; + move_buffer_index++; + } + + do_syntax_scheme(NULL, &(struct syntax_scheme){0}, 0); + global_attr = default_attributes; + return 0; +} + +void +do_syntax_scheme(struct file_buffer* fb, const struct syntax_scheme* cs, int offset) +{ + static int end_at_whitespace = 0; + static const char* end_condition; + static int end_condition_len; + static struct glyph next_word_attr; + static int color_next_word = 0; + static int around = 0; + + if (!fb || !cs) { + // 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) + 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 (fb_offset_starts_with(fb, offset, end_condition)) { + 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 until later + if (around) + around = 0; + else + global_attr = default_attributes; + + end_condition = NULL; + end_at_whitespace = 0; + } + return; + } else if (end_at_whitespace) { + if (!fb_is_on_a_word(fb, offset, cs->word_seperators)) { + end_at_whitespace = 0; + global_attr = default_attributes; + } else { + return; + } + } else if (color_next_word) { + // check if new word encountered + if (!fb_is_on_a_word(fb, offset, cs->word_seperators)) + 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 + // TODO: ??? + if (--end_condition_len <= 0) + global_attr = default_attributes; + else + return; + } + + for (int i = 0; i < cs->entry_count; i++) { + struct syntax_scheme_entry entry = cs->entries[i]; + enum syntax_scheme_mode mode = entry.mode; + + if (mode == COLOR_UPPER_CASE_WORD) { + if (!fb_is_start_of_a_word(fb, offset, cs->word_seperators)) + continue; + + 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 UPPER_CASE_WORD_MIN_LEN chars + if (end_len < UPPER_CASE_WORD_MIN_LEN) + 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 + offset_tmp = fb_seek_word_end(fb, offset_tmp, cs->word_seperators); + if (offset_tmp == offset && mode == COLOR_WORD_BEFORE_STR_STR) + goto exit_word_before_str_str; + if (first_time) + first_word_len = offset_tmp - offset; + + if (mode != COLOR_WORD_ENDING_WITH_STR) + offset_tmp = fb_seek_not_whitespace(fb, offset_tmp); + + first_time = 0; + } + + if (mode == COLOR_WORD_ENDING_WITH_STR) { + offset_tmp -= len; + if (offset_tmp < 0) + continue; + } + if (fb_offset_starts_with(fb, offset_tmp, entry.arg.start)) { + 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 (fb_offset_starts_with(fb, offset - len, entry.arg.start)) { + if (offset < fb->len && fb_offset_starts_with(fb, offset, entry.arg.end)) + continue; + + if (mode == COLOR_WORD_INSIDE) { + // verify that only one word exists inside + int offset_tmp = offset; + offset_tmp = fb_seek_not_whitespace(fb, offset_tmp); + offset_tmp = fb_seek_whitespace(fb, offset_tmp); + int offset_tmp1 = offset_tmp - strlen(entry.arg.end); + offset_tmp = fb_seek_not_whitespace(fb, offset_tmp); + + if ((!fb_offset_starts_with(fb, offset_tmp, entry.arg.end) + && !fb_offset_starts_with(fb, offset_tmp1, entry.arg.end)) + || offset_tmp1 - offset <= 1 || offset_tmp - offset <= 1) + continue; + } else if (mode == COLOR_INSIDE_TO_LINE) { + if (fb_seek_char(fb, offset, '\n') < fb_seek_string(fb, offset, entry.arg.end)) + continue; + } + + + end_condition = entry.arg.end; + end_condition_len = strlen(entry.arg.end); + global_attr = entry.attr; + around = 0; + return; + } + continue; + } + + if ((mode == COLOR_AROUND || mode == COLOR_AROUND_TO_LINE) && + fb_offset_starts_with(fb, offset, entry.arg.start)) { + 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; + global_attr = entry.attr; + return; + } + 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(!fb_offset_starts_with(fb, offset, entry.arg.start) || + !fb_is_start_of_a_word(fb, offset, cs->word_seperators) || + (fb_is_on_a_word(fb, offset + len, cs->word_seperators) && mode != COLOR_WORD_STARTING_WITH_STR)) + continue; + + if (mode == COLOR_WORD_STR) { + int offset_str = fb_seek_not_whitespace(fb, offset + len); + + if (!fb_offset_starts_with(fb, offset_str, entry.arg.end)) + continue; + end_condition_len = strlen(entry.arg.start); + } else { + end_at_whitespace = 1; + } + if (mode == COLOR_STR_AFTER_WORD) { + next_word_attr = entry.attr; + color_next_word = 1; + continue; + } + global_attr = entry.attr; + return; + } + if (mode == COLOR_STR) { + if (!fb_offset_starts_with(fb, offset, entry.arg.start)) + continue; + end_condition_len = len; + global_attr = entry.attr; + return; + } + } +} + +// TODO: make seperate plugin +// also make the "syntax" member of file buffer not a thing +// needs to be more dynamic +int +fb_auto_indent(struct file_buffer* fb, int offset) +{ + const struct syntax_scheme* cs = fb_get_syntax_scheme(fb); + LIMIT(offset, 0, fb->len-1); + + int indent_diff = 0; + int indent_keep_x = -1; + int keep_pos = 0; + + int get_line_offset; + char* get_line = NULL; + + for (int i = 0; i < cs->indent_count; i++) { + const struct indent_scheme_entry indent = cs->indents[i]; + + get_line_offset = get_line_relative_offset(fb, offset, indent.line_offset); + if (get_line) + free(get_line); + get_line = fb_get_line_at_offset(fb, get_line_offset); + + switch(indent.mode) { + int temp_offset, len; + char* res; + case INDENT_LINE_CONTAINS_WORD: + case INDENT_LINE_ONLY_CONTAINS_STR: + case INDENT_LINE_CONTAINS_STR_MORE_THAN_STR: + res = strstr(get_line, indent.arg.start); + if (!res) + continue; + if (INDENT_LINE_CONTAINS_WORD) { + if (res > get_line && !str_contains_char(cs->word_seperators, *(res-1))) + continue; + res += strlen(indent.arg.start); + if (*res && !str_contains_char(cs->word_seperators, *res)) + continue; + } else if (INDENT_LINE_CONTAINS_STR_MORE_THAN_STR == indent.mode) { + char* start_last = get_line; + char* end_last = get_line; + for (int count = 0; count >= 0; ) { + if (start_last && (start_last = strstr(start_last, indent.arg.start))) { + start_last++; + count--; + } else { + goto indent_for_loop_continue; + } + if (end_last && (end_last = strstr(end_last, indent.arg.end))) { + end_last++; + count++; + } + } + } else if (indent.mode == INDENT_LINE_ONLY_CONTAINS_STR) { + int str_start = fb_seek_string_backwards(fb, get_line_offset, indent.arg.start); + int str_end = str_start + strlen(indent.arg.start); + int line_end = MIN(get_line_offset + 1, fb->len); + int line_start = MAX(fb_seek_char_backwards(fb, get_line_offset, '\n'), 0); + if (fb_seek_not_whitespace_backwards(fb, str_start-1) >= line_start || + fb_seek_not_whitespace(fb, str_end+1) <= line_end) + continue; + } + if (indent.type == INDENT_KEEP_OPENER || indent.type == INDENT_RETURN_TO_OPENER_BASE_INDENT) + keep_pos = fb_seek_string_backwards(fb, get_line_offset, indent.arg.start); + goto set_indent_type; + + case INDENT_LINE_ENDS_WITH_STR: + case INDENT_LINE_DOES_NOT_END_WITH_STR: + len = strlen(indent.arg.start); + temp_offset = fb_seek_not_whitespace_backwards(fb, get_line_offset) - len; + if (temp_offset < 0 || + temp_offset < fb_seek_char_backwards(fb, get_line_offset, '\n')) + continue; + if (memcmp(fb->contents + get_line_offset, indent.arg.start, strlen(indent.arg.start)) == 0) { + if (indent.mode == INDENT_LINE_DOES_NOT_END_WITH_STR) + continue; + } else { + if (indent.mode == INDENT_LINE_ENDS_WITH_STR) + continue; + } + keep_pos = temp_offset; + goto set_indent_type; + + set_indent_type: + if (indent.type == INDENT_KEEP_OPENER) { + if (indent_keep_x >= 0) + continue; + int tmp; + fb_offset_to_xy(fb, keep_pos, 0, 0, &indent_keep_x, &tmp, &tmp); + } else if (indent.type == INDENT_RETURN_TO_OPENER_BASE_INDENT) { + if (indent_keep_x >= 0) + continue; + int opener, closer; + if (!fb_get_delimiter(fb, keep_pos, indent.arg, NULL, &opener, &closer)) { + indent_keep_x = -1; + goto indent_for_loop_continue; + } + keep_pos = fb_seek_not_whitespace(fb, fb_seek_char_backwards(fb, opener, '\n')); + int tmp; + fb_offset_to_xy(fb, keep_pos, 0, 0, &indent_keep_x, &tmp, &tmp); + //TODO: why does this miss by one? + if (indent_keep_x > 0) + indent_keep_x--; + } else { + if (indent_diff) + continue; + indent_diff += indent.type; + } + } + indent_for_loop_continue: + continue; + } + + if (get_line) + free(get_line); + + int indents = 0, extra_spaces = 0; + + int prev_line_offset = fb_seek_char_backwards(fb, offset, '\n') - 1; + if (prev_line_offset < 0) + return 0; + char* prev_line = fb_get_line_at_offset(fb, prev_line_offset); + + if (indent_keep_x >= 0) { + whitespace_count_to_indent_amount(fb->indent_len, indent_keep_x, &indents, &extra_spaces); + } else { + whitespace_count_to_indent_amount(fb->indent_len, get_line_leading_whitespace_count(prev_line), &indents, &extra_spaces); + } + if (indent_diff != INDENT_KEEP) { + indents += indent_diff; + indents = MAX(indents, 0); + } + + // remove the lines existing indent + int removed = 0; + int line_code_start = MIN(fb_seek_not_whitespace(fb, prev_line_offset + 1), fb_seek_char(fb, prev_line_offset + 1, '\n')); + if (line_code_start - (prev_line_offset) >= 1) { + removed = line_code_start - (prev_line_offset+1); + fb_remove(fb, prev_line_offset + 1, removed, 1, 1); + } + + if (indents + extra_spaces <= 0) { + free(prev_line); + return -removed; + } + + unsigned int indent_str_len = 0; + while(indents--) + indent_str_len += fb->indent_len ? fb->indent_len : 1; + char indent_str[indent_str_len + extra_spaces]; + + char space_tab = fb->indent_len > 0 ? ' ' : '\t'; + int i = 0; + if (!fb->indent_len) + for (/* i = 0 */; i < indent_str_len; i++) + indent_str[i] = space_tab; + for (/* i = 0 or indent_str_len */ ; i < indent_str_len + extra_spaces; i++) + indent_str[i] = ' '; + + fb_insert(fb, indent_str, indent_str_len + extra_spaces, prev_line_offset + 1, 0); + + free(prev_line); + return indent_str_len + extra_spaces - removed; +} + +int get_line_leading_whitespace_count(const char* line) +{ + int count = 0; + while(*line) { + if (*line == ' ') + count++; + else if (*line == '\t') + count += tabspaces - (count % tabspaces); + else + break; + line++; + } + return count; +} + +void +whitespace_count_to_indent_amount(int indent_len, int count, int* indent_count, int* extra_spaces) +{ + *indent_count = 0, *extra_spaces = 0; + int space_count = indent_len > 0 ? indent_len : tabspaces; + while(count >= space_count) { + *indent_count += 1; + count -= space_count; + } + *extra_spaces = count; +} + +int +get_line_relative_offset(struct file_buffer* fb, int offset, int count) +{ + offset = fb_seek_char(fb, offset, '\n'); + if (offset < 0) + offset = fb->len; + if (count > 0) { + while(count-- && offset >= 0) + offset = fb_seek_char(fb, offset+1, '\n'); + if (offset < 0) + offset = fb->len; + } else if (count < 0) { + offset = fb_seek_char_backwards(fb, offset, '\n'); + while(count++ && offset >= 0) + offset = fb_seek_char_backwards(fb, offset-1, '\n'); + if (offset < 0) + offset = 0; + } + offset = fb_seek_char(fb, offset, '\n'); + if (offset > 0 && fb->contents[offset-1] != '\n') + offset--; + if (offset < 0) + offset = fb->len; + return offset; +} + + +#endif // SYNTAX_H_ diff --git a/extensions/undo.h b/extensions/undo.h @@ -0,0 +1,24 @@ +#ifndef UNDO_H_ +#define UNDO_H_ + +static int +fb_add_to_undo_callback(struct file_buffer* fb, int offset, enum buffer_content_reason reason) +{ + fb_add_to_undo(fb, offset, reason); + return 0; +} + +static int +cursor_undo(struct window_buffer* wb, enum cursor_reason callback_reason) +{ + fb_add_to_undo(get_fb(wb), wb->cursor_offset, FB_CONTENT_CURSOR_MOVE); + //writef_to_status_bar("moved to: %d | reason: %d\n", wb->cursor_offset, callback_reason); + return 0; +} + +static const struct extension undo = { + .fb_contents_updated = fb_add_to_undo_callback, + .wb_cursor_movement = cursor_undo +}; + +#endif // UNDO_H_ diff --git a/extensions/window_modes/choose_one_of_selection.enums b/extensions/window_modes/choose_one_of_selection.enums @@ -0,0 +1,2 @@ +WB_SEARCH_FOR_BUFFERS, +WB_SEARCH_KEYWORD_ALL_BUFFERS, diff --git a/extensions/window_modes/choose_one_of_selection.h b/extensions/window_modes/choose_one_of_selection.h @@ -0,0 +1,594 @@ +#ifndef CHOOSE_ONE_OF_SELECTION_H_ +#define CHOOSE_ONE_OF_SELECTION_H_ + +#include "../../config.h" +#include "../../extension.h" +#include <ctype.h> +#include <dirent.h> + +static int draw_dir(struct window_split_node* win); +static int draw_search_buffers(struct window_split_node* wn); +static int draw_search_keyword_in_all_buffers(struct window_split_node* wn); + +static int file_buffer_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len); +static int choose_one_of_selection_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len); + +static const struct extension file_browser = { + .wn_custom_window_draw = draw_dir, + .wn_custom_window_keypress_override = file_buffer_keypress_override_callback, +}; + +static const struct extension search_open_fb = { + .wn_custom_window_draw = draw_search_buffers, + .wn_custom_window_keypress_override = choose_one_of_selection_keypress_override_callback +}; + +static const struct extension search_keywords_open_fb = { + .wn_custom_window_draw = draw_search_keyword_in_all_buffers, + .wn_custom_window_keypress_override = choose_one_of_selection_keypress_override_callback +}; + +struct keyword_pos { + int offset, fb_index; +}; + +static void choose_one_of_selection(const char* prefix, const char* search, const char* err, + const char*(*get_next_element)(const char*, const char*, int* offset, struct glyph* attr, void* data), + int* selected_line, int minx, int miny, int maxx, int maxy, int focused); + +// TODO: system for custom buffers +// and allow for input overrides +// have theese in their own file +static const char* file_browser_next_item(const char* path, const char* search, int* offset, struct glyph* attr, void* data); +// data pointer will give the file buffer of the current item +static const char* buffer_search_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data); +// data pointer will give the keyword_pos of the current item +static const char* buffers_search_keyword_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data); + +const char* +file_browser_next_item(const char* path, const char* search, int* offset, struct glyph* attr, void* data) +{ + static char filename_search[PATH_MAX]; + static char filename[PATH_MAX]; + static char full_path[PATH_MAX]; + static DIR* dir; + if (!path || !search) { + if (dir) { + closedir(dir); + dir = NULL; + } + return NULL; + } + if (!dir) + dir = opendir(path); + 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 (attr) + attr->fg = path_color; + } else { + if (attr) + attr->fg = default_attributes.fg; + } + + strcpy(filename_search, filename); + const char* s_repl = search; + while(!isupper(*s_repl++)) { + if (*s_repl == 0) { + for (char* fs = filename_search; *fs; fs++) + *fs = tolower(*fs); + break; + } + } + + int f_len = strlen(filename_search); + char* search_start = filename_search; + if (!*search) + goto search_match; + while((search_start = memchr(search_start, *search, f_len))) { + if (memcmp(search_start, search, len) == 0) { + search_match: + if (search[0] != '.' && folder->d_name[0] == '.') + break; + if (strcmp(filename, "./") == 0 || strcmp(filename, "../") == 0) + break; + if (offset) + *offset = search_start - filename_search; + return filename; + } + search_start++; + } + } + *filename = *full_path = 0; + closedir(dir); + dir = NULL; + return NULL; +} + +const char* +buffer_search_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data) +{ + static struct window_buffer wb; + static char file_path[PATH_MAX]; + static int quit_next = 0; + if (!tmp || !search) { + wb.fb_index = 0; + quit_next = 0; + return NULL; + } + + int len = strlen(search); + for(;;) { + if (quit_next) { + wb.fb_index = 0; + quit_next = 0; + return NULL; + } + struct file_buffer* fb = get_fb(&wb); + int last_fb_index = wb.fb_index; + wb.fb_index++; + get_fb(&wb); + if (wb.fb_index <= last_fb_index) + quit_next = 1; + + strcpy(file_path, fb->file_path); + + const char* s_repl = search; + while(!isupper(*s_repl++)) { + if (*s_repl == 0) { + for (char* fs = file_path; *fs; fs++) + *fs = tolower(*fs); + break; + } + } + char* search_start = file_path; + + if (!len) + goto search_match; + while ((search_start = strchr(search_start+1, *search))) { + if (memcmp(search_start, search, len) == 0) { + search_match: + if (offset) + *offset = search_start - file_path; + if (data) + *(int*)data = last_fb_index; + return fb->file_path; + } + } + } +} + +const char* +buffers_search_keyword_next_item(const char* tmp, const char* search, int* offset, struct glyph* attr, void* data) +{ + static char item[LINE_MAX_LEN]; + static struct window_buffer wb; + static int pos = -1; + if (!tmp || !search) { + wb.fb_index = 0; + pos = -1; + return NULL; + } + int len = strlen(search); + if (!len) + return NULL; + + for (;;) { + struct file_buffer* fb = get_fb(&wb); + + if ((pos = fb_seek_string(fb, pos+1, search)) >= 0) { + const char* filename = strrchr(fb->file_path, '/')+1; + if (!filename) + filename = fb->file_path; + + int tmp, y; + fb_offset_to_xy(fb, pos, 0, wb.y_scroll, &tmp, &y, &tmp); + + snprintf(item, LINE_MAX_LEN, "%s:%d: ", filename, y); + int itemlen = strlen(item); + + char* line = fb_get_line_at_offset(fb, pos); + char* line_no_whitespace = line; + if (!isspace(*search)) + while(isspace(*line_no_whitespace)) line_no_whitespace++; + + snprintf(item + itemlen, LINE_MAX_LEN - itemlen, "%s", line_no_whitespace); + + int line_start = fb_seek_char_backwards(fb, pos, '\n') + (line_no_whitespace - line); + free(line); + + if (line_start < 0) + line_start = 0; + if (offset) + *offset = (pos - line_start) + itemlen; + if (data) + *(struct keyword_pos*)data = (struct keyword_pos){.offset = pos, .fb_index = wb.fb_index}; + pos = fb_seek_char(fb, pos+1, '\n'); + return item; + } else { + int last_fb_index = wb.fb_index; + wb.fb_index++; + get_fb(&wb); + if (wb.fb_index <= last_fb_index) + break; + pos = -1; + } + } + wb.fb_index = 0; + pos = -1; + return NULL; +} + +static void +choose_one_of_selection(const char* prefix, const char* search, const char* err, + const char*(*get_next_element)(const char*, const char*, int* offset, struct glyph* attr, void* data), + int* selected_line, int minx, int miny, int maxx, int maxy, int focused) +{ + soft_assert(prefix, prefix = "ERROR";); + soft_assert(search, search = "ERROR";); + soft_assert(err, search = "ERROR";); + soft_assert(selected_line, static int tmp; selected_line = &tmp;); + soft_assert(get_next_element, die("function choose_one_of_selection REQUIRES get_next_element to be a valid pointer");); + + // change background color + global_attr.bg = alternate_bg_dark; + screen_set_region(minx, miny+1, maxx, maxy, ' '); + global_attr = default_attributes; + get_next_element(NULL, NULL, NULL, NULL, NULL); + + int len = strlen(search); + + // count folders to get scroll + int folder_lines = maxy - miny - 2; + int elements = 0; + int tmp_offset; + int limit = MAX(*selected_line, 999); + while(get_next_element(prefix, search, &tmp_offset, NULL, NULL) && elements <= limit) + elements++; + get_next_element(NULL, NULL, NULL, NULL, NULL); + *selected_line = MIN(*selected_line, elements-1); + int sel_local = *selected_line; + + // print num of files + char count[256]; + if (elements >= 1000) + snprintf(count, sizeof(count), "[>999:%2d] ", *selected_line+1); + else if (*selected_line > folder_lines) + snprintf(count, sizeof(count), "^[%3d:%2d] ", elements, *selected_line+1); + else if (elements-1 > folder_lines) + snprintf(count, sizeof(count), "ˇ[%3d:%2d] ", elements, *selected_line+1); + else + snprintf(count, sizeof(count), " [%3d:%2d] ", elements, *selected_line+1); + + // print search term with prefix in front of it + // prefix is in path_color + global_attr.fg = path_color; + int new_x = write_string(count, miny, minx, maxx+1); + new_x = write_string(prefix, miny, new_x, maxx+1); + global_attr = default_attributes; + new_x = write_string(search, miny, new_x, maxx+1); + + // print elements + int start_miny = miny; + int offset; + elements--; + miny++; + global_attr = default_attributes; + global_attr.bg = alternate_bg_dark; + const char* element; + while(miny <= maxy && (element = get_next_element(prefix, search, &offset, &global_attr, NULL))) { + if (elements > folder_lines && sel_local > folder_lines) { + elements--; + sel_local--; + continue; + } + write_string(element, miny, minx, maxx+1); + + // change the color to highlight search term + for (int i = minx + offset; i < minx + len + offset && i < maxx+1; i++) + screen_set_attr(i, miny)->fg = highlight_color; + // change the background of the selected line + if (miny - start_miny - 1 == sel_local) + for (int i = minx; i < maxx+1; i++) + screen_set_attr(i, miny)->bg = selection_bg; + miny++; + } + + if (elements < 0) { + global_attr = default_attributes; + global_attr.fg = warning_color; + write_string(err, start_miny, new_x, maxx+1); + } + + // draw + + for (int y = start_miny; y < maxy+1; y++) + xdrawline(minx, y, maxx+1); + + draw_horisontal_line(maxy-1, minx, maxx); + xdrawcursor(new_x, start_miny, focused); + + global_attr = default_attributes; +} + +static int +draw_dir(struct window_split_node* wn) +{ + soft_assert(wn->wb.mode < WB_MODES_END, return 1;); + if (wn->wb.mode != WB_FILE_BROWSER) return 0; + + int focused = &wn->wb == focused_window; + struct file_buffer* fb = get_fb(&wn->wb); + char* folder = file_path_get_path(fb->file_path); + + fb_change(fb, "\0", 1, fb->len, 1); + if (fb->len > 0) fb->len--; + + choose_one_of_selection(folder, fb->contents, " [Create New File]", file_browser_next_item, + &wn->selected, wn->minx, wn->miny, wn->maxx, wn->maxy, focused); + + free(folder); + return 1; +} + +static int +draw_search_buffers(struct window_split_node* wn) +{ + soft_assert(wn->wb.mode < WB_MODES_END, return 1;); + if (wn->wb.mode != WB_SEARCH_FOR_BUFFERS) return 0; + + int focused = &wn->wb == focused_window; + choose_one_of_selection("Find loaded buffer: ", wn->search, " [No resuts]", buffer_search_next_item, + &wn->selected, wn->minx, wn->miny, wn->maxx, wn->maxy, focused); + return 1; +} + +static int +draw_search_keyword_in_all_buffers(struct window_split_node* wn) +{ + soft_assert(wn->wb.mode < WB_MODES_END, return 1;); + if (wn->wb.mode != WB_SEARCH_KEYWORD_ALL_BUFFERS) return 0; + + int focused = &wn->wb == focused_window; + choose_one_of_selection("Find in all buffers: ", wn->search, " [No resuts]", buffers_search_keyword_next_item, + &wn->selected, wn->minx, wn->miny, wn->maxx, wn->maxy, focused); + return 1; +} + +static int +file_browser_actions(KeySym keysym, int modkey) +{ + static char full_path[PATH_MAX]; + struct file_buffer* fb = get_fb(focused_window); + int offset = fb->len; + + switch (keysym) { + int new_fb; + case XK_BackSpace: + if (offset <= 0) { + char* dest = strrchr(fb->file_path, '/'); + if (dest && dest != fb->file_path) *dest = 0; + return 1; + } + + focused_window->cursor_offset = offset; + wb_move_on_line(focused_window, -1, 0); + offset = focused_window->cursor_offset; + + fb_remove(fb, offset, 1, 0, 0); + focused_node->selected = 0; + return 1; + case XK_Tab: + case XK_Return: + { + char* path = file_path_get_path(fb->file_path); + fb_change(fb, "\0", 1, fb->len, 1); + if (fb->len > 0) fb->len--; + + file_browser_next_item(NULL, NULL, NULL, NULL, NULL); + const char* filename; + for (int y = 0; (filename = file_browser_next_item(path, fb->contents, NULL, NULL, NULL)); y++) { + strcpy(full_path, path); + strcat(full_path, filename); + if (y == focused_node->selected) { + if (path_is_folder(full_path)) { + strcpy(fb->file_path, full_path); + + fb->len = 0; + fb->contents[0] = '\0'; + focused_node->selected = 0; + + free(path); + file_browser_next_item(NULL, NULL, NULL, NULL, NULL); + *full_path = 0; + return 1; + } + goto open_file; + } + } + + if (fb->contents[fb->len-1] == '/') { + free(path); + *full_path = 0; + return 1; + } + + strcpy(full_path, path); + strcat(full_path, fb->contents); +open_file: + new_fb = fb_new_entry(full_path); + destroy_fb_entry(focused_node, &root_node); + focused_node->wb = wb_new(new_fb); + free(path); + *full_path = 0; + return 1; + } + case XK_Down: + focused_node->selected++; + return 1; + case XK_Up: + focused_node->selected--; + if (focused_node->selected < 0) + focused_node->selected = 0; + return 1; + case XK_Escape: + if (destroy_fb_entry(focused_node, &root_node)) + writef_to_status_bar("Quit"); + return 1; + } + return 0; +} + +static void +file_browser_string_insert(const char* buf, int buflen) +{ + static char full_path[PATH_MAX]; + struct file_buffer* fb = get_fb(focused_window); + + if (fb->len + buflen + strlen(fb->file_path) > PATH_MAX) + return; + + if (buf[0] >= 32 || buflen > 1) { + fb_insert(fb, buf, buflen, fb->len, 0); + focused_node->selected = 0; + + if (*buf == '/') { + fb_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 { + writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); + } +} + +static int +search_for_buffer_actions(KeySym keysym, int modkey) +{ + switch (keysym) { + case XK_Return: + case XK_Tab: + if (focused_window->mode == WB_SEARCH_KEYWORD_ALL_BUFFERS) { + struct keyword_pos kw; + int n = 0; + buffers_search_keyword_next_item(NULL, NULL, NULL, NULL, NULL); + while (buffers_search_keyword_next_item("", focused_node->search, NULL, NULL, &kw)) { + if (n == focused_node->selected) { + *focused_window = wb_new(kw.fb_index); + focused_window->cursor_offset = kw.offset; + return 1; + } + n++; + } + buffers_search_keyword_next_item(NULL, NULL, NULL, NULL, NULL); + } else if (focused_window->mode == WB_SEARCH_FOR_BUFFERS) { + int fb_index; + int n = 0; + buffer_search_next_item(NULL, NULL, NULL, NULL, NULL); + while (buffer_search_next_item("", focused_node->search, NULL, NULL, &fb_index)) { + if (n == focused_node->selected) { + *focused_window = wb_new(fb_index); + return 1; + } + n++; + } + buffer_search_next_item(NULL, NULL, NULL, NULL, NULL); + } + writef_to_status_bar("no results for \"%s\"", focused_node->search); + return 1; + case XK_BackSpace: + utf8_remove_string_end(focused_node->search); + focused_node->selected = 0; + return 1; + case XK_Down: + focused_node->selected++; + return 1; + case XK_Up: + focused_node->selected--; + if (focused_node->selected < 0) + focused_node->selected = 0; + return 1; + case XK_Page_Down: + focused_node->selected += 10; + return 1; + case XK_Page_Up: + focused_node->selected -= 10; + if (focused_node->selected < 0) + focused_node->selected = 0; + return 1; + case XK_Escape: + if (path_is_folder(get_fb(focused_window)->file_path)) + focused_window->mode = WB_FILE_BROWSER; + else + focused_window->mode = WB_NORMAL; + + writef_to_status_bar("Quit"); + return 1; + } + return 0; +} + +static void +search_for_buffer_string_insert(const char* buf, int buflen) +{ + int len = strlen(focused_node->search); + if (buflen + len + 1 > SEARCH_TERM_MAX_LEN) + return; + + if (buf[0] >= 32 || buflen > 1) { + memcpy(focused_node->search + len, buf, buflen); + focused_node->search[len + buflen] = 0; + focused_node->selected = 0; + } else { + writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); + } +} + +int +file_buffer_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len) +{ + soft_assert(wn->wb.mode < WB_MODES_END, return 1;); + if (wn->wb.mode != WB_FILE_BROWSER) return 0; + + if (file_browser_actions(ksym, modkey)) { + *skip_keypress_callback = 1; + return 1; + } + file_browser_string_insert(buf, len); + *skip_keypress_callback = 1; + return 1; +} + +int +choose_one_of_selection_keypress_override_callback(int* skip_keypress_callback, struct window_split_node* wn, KeySym ksym, int modkey, const char* buf, int len) +{ + soft_assert(wn->wb.mode < WB_MODES_END, return 1;); + if (wn->wb.mode != WB_SEARCH_FOR_BUFFERS && + wn->wb.mode != WB_SEARCH_KEYWORD_ALL_BUFFERS) + return 0; + + if (search_for_buffer_actions(ksym, modkey)) { + *skip_keypress_callback = 1; + return 1; + } + search_for_buffer_string_insert(buf, len); + *skip_keypress_callback = 1; + return 1; +} + +#endif // CHOOSE_ONE_OF_SELECTION_H_ diff --git a/normal_config.def.c b/normal_config.def.c @@ -0,0 +1,245 @@ +#include "config.h" + +//////////////////////////////////////// +// apperance +// + +// font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html +//char *fontconfig = "Liberation Mono:pixelsize=16:antialias=true:autohint=true"; +char *fontconfig = "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; + +// Default shape of cursor +// 2: Block ("█") +// 4: Underline ("_") +// 6: Bar ("|") +unsigned int cursor_shape = 2; + +// thickness of underline and bar cursors +unsigned int cursor_thickness = 2; + +/////////////////////////////////////////// +// color scheme +// the syntax highlighting is applied in one pass, +// so you can't have nested syntax highlighting +// + +#include "plugins/color_schemes/gruvbox.h" + +// disable coloring functions for the syntax schemes below +#undef function_color + +#include "plugins/syntax/c.h" + +const struct syntax_scheme syntax_schemes[] = { + {".c", c_word_seperators, c_syntax, LEN(c_syntax)}, + {".h", c_word_seperators, c_syntax, LEN(c_syntax)}, + {0}, +}; + +///////////////////////////////////////// +// Shortcuts +// + +#include "plugins/shortcuts.h" +#include "plugins/default_shortcuts.h" + +#define MODKEY Mod1Mask +#define CtrlShift (ControlMask|ShiftMask) + +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} }, + { CtrlShift, XK_Right, window_resize, {.i = MOVE_RIGHT} }, + { CtrlShift, XK_Left, window_resize, {.i = MOVE_LEFT} }, + { CtrlShift, XK_Down, window_resize, {.i = MOVE_DOWN} }, + { CtrlShift, XK_Up, window_resize, {.i = MOVE_UP} }, + { ControlMask, XK_Tab, swap_to_next_file_buffer, {0} }, + { ControlMask, XK_m, toggle_selection, {0} }, + { ControlMask, XK_g, move_cursor_to_offset, {0} }, + { CtrlShift, XK_G, move_cursor_to_end_of_buffer, {0} }, + { ControlMask, XK_period, open_file_browser, {0} }, + { CtrlShift, XK_D, buffer_kill, {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} }, + { CtrlShift, XK_Z, redo, {0} }, + { ControlMask, XK_s, save_buffer, {0} }, + { ControlMask, XK_f, search, {0} }, + { CtrlShift, XK_F, search_keyword_in_buffers,{0},}, + { ControlMask, XK_space, search_for_buffer,{0}, }, + { ControlMask, XK_n, search_next, {0} }, + { CtrlShift, XK_N, search_previous,{0} }, + { ControlMask, XK_c, clipboard_copy, {0} }, + { ControlMask, XK_v, clipboard_paste,{0} }, + { CtrlShift, XK_Prior, zoom, {.f = +1} }, + { CtrlShift, XK_Next, zoom, {.f = -1} }, + { CtrlShift, XK_Home, zoomreset, {.f = 0} }, + { CtrlShift, XK_Num_Lock, numlock, {.i = 0} }, +}; + +///////////////////////////////////////////////// +// callbacks +// + +static void cursor_callback(struct window_buffer* buf, enum cursor_reason callback_reason); +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 int keypress_actions(KeySym keysym, int modkey); +static void string_insert_callback(const char* buf, int buflen); +#include "plugins/default_status_bar.h" + +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_add_to_undo; +int(*keypress_callback)(KeySym, int) = keypress_actions; +void(*string_input_callback)(const char*, int) = string_insert_callback; +void(*draw_callback)(void) = NULL; +void(*startup_callback)(void) = NULL; +void(*buffer_written_to_screen_callback)(struct window_buffer* buf, int offset_start, int offset_end, int minx, int miny, int maxx, int maxy) = NULL; +char*(*new_line_draw)(struct window_buffer* buf, int y, int lines_left, int minx, int maxx, Glyph* attr) = NULL; +void(*buffer_written_to_file_callback)(struct file_buffer* fb) = NULL; +int(*write_status_bar)(struct window_buffer* buf, int minx, int maxx, int cx, int cy, char line[LINE_MAX_LEN], Glyph* g) = default_status_bar; + +void +keep_cursor_col(struct window_buffer* buf, enum cursor_reason callback_reason) +{ + 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); + } +} + +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); + + //writef_to_status_bar("moved to: %d | reason: %d\n", buf->cursor_offset, callback_reason); +} + +int +keypress_actions(KeySym keysym, int modkey) +{ + check_shortcuts(keysym, modkey); + + // 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; + + goto skip_delete_remove_selection; + case XK_Delete: + + if (delete_selection(fb)) return 1; +skip_delete_remove_selection: + + 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_Escape: + fb->mode &= ~BUFFER_SEARCH_BLOCKING_MASK; + fb->mode &= ~BUFFER_SEARCH_NON_BLOCKING; + fb->mode &= ~BUFFER_SEARCH_NON_BLOCKING_BACKWARDS; + fb->mode &= ~BUFFER_SEARCH_BLOCKING_BACKWARDS; + writef_to_status_bar(""); + 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, (focused_node->maxy - focused_node->miny) / 2, 0); + buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + focused_window->y_scroll += (focused_node->maxy - focused_node->miny) / 2; + return 1; + case XK_Page_Up: + buffer_move_lines(focused_window, -((focused_node->maxy - focused_node->miny) / 2), 0); + buffer_move_to_x(focused_window, focused_window->cursor_col, CURSOR_UP_DOWN_MOVEMENT); + focused_window->y_scroll -= (focused_node->maxy - focused_node->miny) / 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); + + if (buf[0] >= 32 || buflen > 1) { + 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 { + writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); + } +} diff --git a/plugins/color_schemes/gruvbox.h b/plugins/color_schemes/gruvbox.h @@ -1,66 +0,0 @@ -// 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 alternate_bg_bright = sel; -unsigned int alternate_bg_dark = bg0_h; - -unsigned int cursor_fg = fg; -unsigned int cursor_bg = bg; -unsigned int mouse_line_bg = line; - -unsigned int selection_bg = sel; -unsigned int highlight_color = yellow; -unsigned int path_color = teal; - -unsigned int error_color = red; -unsigned int warning_color = yellow; -unsigned int ok_color = green; - -#define normal_color {.fg = fg} -#define string_color {.fg = gray} -#define comment_color {.fg = gray} -#define type_color {.fg = teal} -#define keyword_color {.fg = green} -#define macro_color {.fg = yellow} -#define operator_color {.fg = yellow, .mode = ATTR_BOLD} -#define constants_color {.fg = dark_green} -#define number_color {.fg = gray} -#define function_color {.fg = aqua} diff --git a/plugins/syntax/c.h b/plugins/syntax/c.h @@ -1,101 +0,0 @@ -#include "handy_defines.h" - -#ifdef macro_color -#define color_macro(_str) {COLOR_WORD,{_str}, macro_color} -#endif - -const struct color_scheme_entry c_color_scheme[] = { - // Coloring type arguments Color - - // strings -#ifdef string_color - {COLOR_AROUND_TO_LINE, {"\"", "\""}, string_color}, - {COLOR_STR, {"''"}, normal_color}, - {COLOR_AROUND_TO_LINE, {"'", "'"}, string_color}, - {COLOR_INSIDE_TO_LINE, {"#include <", ">"}, string_color}, - {COLOR_INSIDE_TO_LINE, {"#include<", ">"}, string_color}, -#endif - // comments -#ifdef comment_color - {COLOR_AROUND, {"/*", "*/"}, comment_color}, - {COLOR_AROUND, {"//", "\n"}, comment_color}, -#endif - // macros -#ifdef macro_color -#ifdef 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}, -#endif // constants_color - {COLOR_WORD_STARTING_WITH_STR, {"#"}, {.fg = yellow, .mode = ATTR_BOLD}}, - 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"), -#endif - // operators -#ifdef operator_color - {COLOR_STR, {"!="}, normal_color}, - {COLOR_STR, {"!"}, operator_color}, - {COLOR_STR, {"~"}, operator_color}, - {COLOR_STR, {"?"}, operator_color}, -#endif - // keywords -#ifdef keyword_color - {COLOR_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"), -#endif - // functions -#ifdef function_color - {COLOR_WORD_BEFORE_STR, {"("}, function_color}, -#endif -#ifdef constants_color - {COLOR_UPPER_CASE_WORD, {0}, constants_color}, -#endif - // types -#ifdef type_color - 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}, -#endif - // numbers -#ifdef number_color - 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"), -#endif -}; - -#define c_word_seperators default_word_seperators diff --git a/plugins/syntax/handy_defines.h b/plugins/syntax/handy_defines.h @@ -1,20 +0,0 @@ -#ifndef HANDY_DEFINES_H_ -#define HANDY_DEFINES_H_ - -#define default_word_seperators "., \n\t*+-/%!~<>=(){}[]\"^&|\\\'?:;" - -#ifdef keyword_color -#define color_keyword(_str) {COLOR_WORD,{_str}, keyword_color} -#endif - -#ifdef type_color -#define color_type(_str) {COLOR_WORD,{_str}, type_color} -#endif - -#ifdef number_color -#define color_number(_num) \ - {COLOR_WORD_STARTING_WITH_STR, {_num}, number_color}, \ - {COLOR_WORD_ENDING_WITH_STR, {_num".f"},number_color} -#endif - -#endif // HANDY_DEFINES_H_ diff --git a/se.c b/se.c @@ -3,12 +3,11 @@ /* ** 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 +** can 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 <stdarg.h> #include <time.h> @@ -16,1513 +15,103 @@ #include "se.h" #include "x.h" +#include "config.h" +#include "extension.h" -/////////////////////////////////////////// -// config.c variables and globals -// - -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; - -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 -// - -static void buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx, int maxy); -static void choose_one_of_selection(const char* prefix, const char* search, const char* err, - const char*(*get_next_element)(const char*, const char*, int* offset, Glyph* attr, void* data), - int* selected_line, int minx, int miny, int maxx, int maxy, int focused); -static void draw_dir(struct window_split_node* win, int minx, int miny, int maxx, int maxy, int focused); -static void draw_search_buffers(struct window_split_node* wn, int minx, int miny, int maxx, int maxy, int focused); -static void draw_search_keyword_in_all_buffers(struct window_split_node* wn, int minx, int miny, int maxx, int maxy, int focused); -void(*alternate_buffer_modes[WINDOW_BUFFER_MODE_LEN]) -(struct window_split_node* wn, int minx, int miny, int maxx, int maxy, int focused) = { - [WINDOW_BUFFER_NORMAL] = NULL, - [WINDOW_BUFFER_FILE_BROWSER] = draw_dir, - [WINDOW_BUFFER_SEARCH_BUFFERS] = draw_search_buffers, - [WINDOW_BUFFER_KEYWORD_ALL_BUFFERS] = draw_search_keyword_in_all_buffers -}; - -static void recursive_mkdir(char* path); -static int writef_string(int y, int x1, int x2, const char* fmt, ...); -static void color_selection(Glyph* letter); -static void do_color_scheme(struct file_buffer* fb, const struct color_scheme* cs, int offset); -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); -void buffer_copy_ub_to_current(struct window_buffer* buffer); - -//////////////////////////////////////////// -// 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); -} - -void -tnew(int col, int row) -{ - global_attr = default_attributes; - - term = (Term){0}; - tresize(col, row); -} - -const struct color_scheme* -buffer_get_color_scheme(struct file_buffer* fb) -{ - for (int i = 0; color_schemes[i].file_ending; i++) { - if (is_file_type(fb->file_path, color_schemes[i].file_ending)) { - return &color_schemes[i]; - } - } - return NULL; -} - -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; - writef_to_status_bar("opened file browser %s", get_file_buffer(&wb)->file_path); - } - - return wb; -} - -int -buffer_seek_char(const struct file_buffer* buf, int offset, char byte) -{ - if (offset > buf->len) return -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; -} - -int -buffer_seek_string_wrap(const struct window_buffer* wb, int offset, const char* search) -{ - struct file_buffer* fb = get_file_buffer(focused_window); - if (*search == 0 || !buffer_count_string_instances(fb, search, 0, NULL)) - return -1; - - int new_offset = buffer_seek_string(fb, offset, search); - if (new_offset < 0) - new_offset = buffer_seek_string(fb, 0, search); - - if (!(fb->mode & BUFFER_SEARCH_BLOCKING)) - fb->mode |= BUFFER_SEARCH_BLOCKING_IDLE; - return new_offset; -} - -int -buffer_seek_string_wrap_backwards(const struct window_buffer* wb, int offset, const char* search) -{ - struct file_buffer* fb = get_file_buffer(focused_window); - if (*search == 0 || !buffer_count_string_instances(fb, search, 0, NULL)) - return -1; - - int new_offset = buffer_seek_string_backwards(fb, offset, search); - if (new_offset < 0) - new_offset = buffer_seek_string_backwards(fb, fb->len, search); - - if (!(fb->mode & BUFFER_SEARCH_BLOCKING)) - fb->mode |= BUFFER_SEARCH_BLOCKING_IDLE; - return new_offset; -} - -int -buffer_seek_string_wrap_backwards_ignore_case(const struct window_buffer* wb, int offset, const char* search); - -int -buffer_is_on_a_word(const struct file_buffer* fb, int offset, const char* word_seperators) -{ - LIMIT(offset, 0, fb->len); - return !str_contains_char(word_seperators, fb->contents[offset]); -} - -int -buffer_is_start_of_a_word(const struct file_buffer* fb, int offset, const char* word_seperators) -{ - return buffer_is_on_a_word(fb, offset, word_seperators) && - (offset-1 <= 0 || str_contains_char(word_seperators, fb->contents[offset-1])); -} - -int -buffer_is_on_word(const struct file_buffer* fb, int offset, const char* word_seperators, const char* word) -{ - LIMIT(offset, 0, fb->len); - int word_start = buffer_seek_word_backwards(fb, offset, word_seperators); - int word_len = strlen(word); - if (word_start < offset - (word_len-1)) - return 0; - return buffer_offset_starts_with(fb, word_start, word) && - !buffer_is_on_a_word(fb, word_start + word_len, word_seperators); -} - -int -buffer_offset_starts_with(const struct file_buffer* fb, int offset, const char* start) -{ - LIMIT(offset, 0, fb->len); - int len = strlen(start); - if (len + offset > fb->len) return 0; - - return memcmp(fb->contents + offset, start, len) == 0; -} - -int -buffer_seek_word(const struct file_buffer* fb, int offset, const char* word_seperators) -{ - if (buffer_is_on_a_word(fb, offset, word_seperators)) - offset = buffer_seek_word_end(fb, offset, word_seperators); - while (offset < fb->len && !str_contains_char(word_seperators, fb->contents[offset])) offset++; - return offset; -} - -int -buffer_seek_word_end(const struct file_buffer* fb, int offset, const char* word_seperators) -{ - while (offset < fb->len && !str_contains_char(word_seperators, fb->contents[offset])) offset++; - return offset; -} - -int -buffer_seek_word_backwards(const struct file_buffer* fb, int offset, const char* word_seperators) -{ - LIMIT(offset, 0, fb->len); - if (!buffer_is_on_a_word(fb, offset, word_seperators)) - while (offset > 0 && str_contains_char(word_seperators, fb->contents[offset])) offset--; - while (offset-1 > 0 && !str_contains_char(word_seperators, fb->contents[offset-1])) offset--; - return offset; -} - -int -buffer_seek_whitespace(const struct file_buffer* fb, int offset) -{ - while (offset < fb->len && !isspace(fb->contents[offset])) offset++; - return offset; -} - -int -buffer_seek_whitespace_backwrads(const struct file_buffer* fb, int offset) -{ - LIMIT(offset, 0, fb->len); - while (offset > 0 && !isspace(fb->contents[offset])) offset--; - return offset; -} - -int -buffer_seek_not_whitespace(const struct file_buffer* fb, int offset) -{ - while (offset < fb->len && isspace(fb->contents[offset])) offset++; - return offset; -} - -int -buffer_seek_not_whitespace_backwrads(const struct file_buffer* fb, int offset) -{ - LIMIT(offset, 0, fb->len); - while (offset > 0 && isspace(fb->contents[offset])) offset--; - return offset; -} - -int -buffer_count_string_instances(const struct file_buffer* fb, const char* string, int offset, int* before_offset) -{ - int tmp; - if (!before_offset) - before_offset = &tmp; - if (!string || *string == 0) { - *before_offset = 0; - return 0; - } - - int pos = -1; - int count = 0; - int once = 1; - while((pos = buffer_seek_string(fb, pos+1, string)) >= 0) { - if (once && pos > offset) { - *before_offset = count; - once = 0; - } - count++; - } - if (once) - *before_offset = count; - return count; -} - -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->search = xmalloc(SEARCH_TERM_MAX_LEN); - parent->node2->search = xmalloc(SEARCH_TERM_MAX_LEN); - - *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) { - writef_to_status_bar("can't close root winodw"); - 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->search); - 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, 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); - - for (int y = miny; y < maxy+1; y++) - xdrawline(middlex+1, y, middlex+2); - } else if (root->mode == WINDOW_VERTICAL) { - int middley = ((float)(maxy - miny) * root->ratio) + miny; - - window_draw_tree_to_screen(root->node1, minx, miny, maxx, middley); - window_draw_tree_to_screen(root->node2, minx, middley, maxx, 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) -{ - 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, float amount) -{ - 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; - } - } -} - -void -window_node_resize_absolute(struct window_split_node* node, enum move_directons move, float amount) -{ - for (; node; node = node->parent) { - if (is_correct_mode(node->mode, move)) { - 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); -} - -// result 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; -} - -const char* -file_browser_next_item(const char* path, const char* search, int* offset, Glyph* attr, void* data) -{ - static char filename_search[PATH_MAX]; - static char filename[PATH_MAX]; - static char full_path[PATH_MAX]; - static DIR* dir; - if (!path || !search) { - if (dir) { - closedir(dir); - dir = NULL; - } - return NULL; - } - if (!dir) - dir = opendir(path); - 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 (attr) - attr->fg = path_color; - } else { - if (attr) - attr->fg = default_attributes.fg; - } - - strcpy(filename_search, filename); - const char* s_repl = search; - while(!isupper(*s_repl++)) { - if (*s_repl == 0) { - for (char* fs = filename_search; *fs; fs++) - *fs = tolower(*fs); - break; - } - } - - int f_len = strlen(filename_search); - char* search_start = filename_search; - if (!*search) - goto search_match; - while((search_start = memchr(search_start, *search, f_len))) { - if (memcmp(search_start, search, len) == 0) { - search_match: - if (search[0] != '.' && folder->d_name[0] == '.') - break; - if (strcmp(filename, "./") == 0 || strcmp(filename, "../") == 0) - break; - if (offset) - *offset = search_start - filename_search; - return filename; - } - search_start++; - } - } - *filename = *full_path = 0; - closedir(dir); - dir = NULL; - return NULL; -} - -const char* -buffer_search_next_item(const char* tmp, const char* search, int* offset, Glyph* attr, void* data) -{ - static struct window_buffer wb; - static char file_path[PATH_MAX]; - static int quit_next = 0; - if (!tmp || !search) { - wb.buffer_index = 0; - quit_next = 0; - return NULL; - } - - int len = strlen(search); - for(;;) { - if (quit_next) { - wb.buffer_index = 0; - quit_next = 0; - return NULL; - } - struct file_buffer* fb = get_file_buffer(&wb); - int last_buffer_index = wb.buffer_index; - wb.buffer_index++; - get_file_buffer(&wb); - if (wb.buffer_index <= last_buffer_index) - quit_next = 1; - - strcpy(file_path, fb->file_path); - - const char* s_repl = search; - while(!isupper(*s_repl++)) { - if (*s_repl == 0) { - for (char* fs = file_path; *fs; fs++) - *fs = tolower(*fs); - break; - } - } - char* search_start = file_path; - - if (!len) - goto search_match; - while ((search_start = strchr(search_start+1, *search))) { - if (memcmp(search_start, search, len) == 0) { - search_match: - if (offset) - *offset = search_start - file_path; - if (data) - *(int*)data = last_buffer_index; - return fb->file_path; - } - } - } -} - -const char* -buffers_search_keyword_next_item(const char* tmp, const char* search, int* offset, Glyph* attr, void* data) -{ - static char item[2048]; - static struct window_buffer wb; - static int pos = -1; - if (!tmp || !search) { - wb.buffer_index = 0; - pos = -1; - return NULL; - } - int len = strlen(search); - if (!len) - return NULL; - - for (;;) { - struct file_buffer* fb = get_file_buffer(&wb); - - if ((pos = buffer_seek_string(fb, pos+1, search)) >= 0) { - const char* filename = strrchr(fb->file_path, '/')+1; - if (!filename) - filename = fb->file_path; - - int tmp, y; - buffer_offset_to_xy(&wb, pos, 0, &tmp, &y); - - snprintf(item, 2048, "%s:%d: ", filename, y); - int itemlen = strlen(item); - - char* line = buffer_get_line_at_offset(fb, pos); - char* line_no_whitespace = line; - if (!isspace(*search)) - while(isspace(*line_no_whitespace)) line_no_whitespace++; - - snprintf(item + itemlen, 2048 - itemlen, "%s", line_no_whitespace); - free(line); - - int line_start = buffer_seek_char_backwards(fb, pos, '\n') + (line_no_whitespace - line); - if (line_start < 0) - line_start = 0; - if (offset) - *offset = (pos - line_start) + itemlen; - if (data) - *(struct keyword_pos*)data = (struct keyword_pos){.offset = pos, .buffer_index = wb.buffer_index}; - pos = buffer_seek_char(fb, pos+1, '\n'); - return item; - } else { - int last_buffer_index = wb.buffer_index; - wb.buffer_index++; - get_file_buffer(&wb); - if (wb.buffer_index <= last_buffer_index) - break; - pos = -1; - } - } - wb.buffer_index = 0; - pos = -1; - return NULL; -} - -void -choose_one_of_selection(const char* prefix, const char* search, const char* err, - const char*(*get_next_element)(const char*, const char*, int* offset, Glyph* attr, void* data), - int* selected_line, int minx, int miny, int maxx, int maxy, int focused) -{ - assert(prefix); - assert(search); - assert(err); - assert(selected_line); - assert(get_next_element); - - // change background color - global_attr.bg = alternate_bg_dark; - tsetregion(minx, miny+1, maxx, maxy, ' '); - global_attr = default_attributes; - get_next_element(NULL, NULL, NULL, NULL, NULL); - - int len = strlen(search); - - // count folders to get scroll - int folder_lines = maxy - miny - 2; - int elements = 0; - int tmp_offset; - int limit = MAX(*selected_line, 999); - while(get_next_element(prefix, search, &tmp_offset, NULL, NULL) && elements <= limit) - elements++; - get_next_element(NULL, NULL, NULL, NULL, NULL); - *selected_line = MIN(*selected_line, elements-1); - int sel_local = *selected_line; - - // print num of files - char count[256]; - if (elements >= 1000) - snprintf(count, sizeof(count), "[>999:%2d] ", *selected_line+1); - else if (*selected_line > folder_lines) - snprintf(count, sizeof(count), "^[%3d:%2d] ", elements, *selected_line+1); - else if (elements-1 > folder_lines) - snprintf(count, sizeof(count), "ˇ[%3d:%2d] ", elements, *selected_line+1); - else - snprintf(count, sizeof(count), " [%3d:%2d] ", elements, *selected_line+1); - - // print search term with prefix in front of it - // prefix is in path_color - global_attr.fg = path_color; - int new_x = write_string(count, miny, minx, maxx+1); - new_x = write_string(prefix, miny, new_x, maxx+1); - global_attr = default_attributes; - new_x = write_string(search, miny, new_x, maxx+1); - - // print elements - int start_miny = miny; - int offset; - elements--; - miny++; - global_attr = default_attributes; - global_attr.bg = alternate_bg_dark; - const char* element; - while(miny <= maxy && (element = get_next_element(prefix, search, &offset, &global_attr, NULL))) { - if (elements > folder_lines && sel_local > folder_lines) { - elements--; - sel_local--; - continue; - } - write_string(element, miny, minx, maxx+1); - - // change the color to highlight search term - for (int i = minx + offset; i < minx + len + offset && i < maxx+1; i++) - tsetattr(i, miny)->fg = highlight_color; - // change the background of the selected line - if (miny - start_miny - 1 == sel_local) - for (int i = minx; i < maxx+1; i++) - tsetattr(i, miny)->bg = selection_bg; - miny++; - } - - if (elements < 0) { - global_attr = default_attributes; - global_attr.fg = warning_color; - write_string(err, start_miny, new_x, maxx+1); - } - - // draw - - for (int y = start_miny; y < maxy+1; y++) - xdrawline(minx, y, maxx+1); - - draw_horisontal_line(maxy-1, minx, maxx); - xdrawcursor(new_x, start_miny, focused); - - global_attr = default_attributes; -} - -void -draw_dir(struct window_split_node* wn, int minx, int miny, int maxx, int maxy, int focused) -{ - struct file_buffer* fb = get_file_buffer(&wn->window); - char* folder = file_path_get_path(fb->file_path); - - buffer_change(fb, "\0", 1, fb->len, 1); - if (fb->len > 0) fb->len--; - - choose_one_of_selection(folder, fb->contents, " [Create New File]", - file_browser_next_item, &wn->selected, minx, miny, maxx, maxy, focused); - - free(folder); -} - -void -draw_search_buffers(struct window_split_node* wn, int minx, int miny, int maxx, int maxy, int focused) -{ - choose_one_of_selection("Find loaded buffer: ", wn->search, " [No resuts]", - buffer_search_next_item, &wn->selected, minx, miny, maxx, maxy, focused); -} - -void -draw_search_keyword_in_all_buffers(struct window_split_node* wn, int minx, int miny, int maxx, int maxy, int focused) -{ - choose_one_of_selection("Find in all buffers: ", wn->search, " [No resuts]", - buffers_search_keyword_next_item, &wn->selected, minx, miny, maxx, maxy, focused); -} - -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)); -} - -int -writef_string(int y, int x1, int x2, const char* fmt, ...) -{ - char string[STATUS_BAR_MAX_LEN]; - - va_list args; - va_start(args, fmt); - vsnprintf(string, STATUS_BAR_MAX_LEN, fmt, args); - va_end(args); - - return write_string(string, y, x1, x2); -} - -int -writef_to_status_bar(const char* fmt, ...) -{ - static char string[STATUS_BAR_MAX_LEN]; - if (!fmt) - return write_string(string, term.row-1, 0, term.col); - - va_list args; - va_start(args, fmt); - vsnprintf(string, STATUS_BAR_MAX_LEN, fmt, args); - va_end(args); - - return write_string(string, term.row-1, 0, term.col); -} - -void -draw_status_bar() -{ - // change background color - global_attr = default_attributes; - global_attr.bg = alternate_bg_dark; - tsetregion(0, term.row-1, term.col-1, term.row-1, ' '); - int x_end = writef_to_status_bar(NULL); - global_attr = default_attributes; - - xdrawline(0, term.row-1, term.col); - draw_horisontal_line(term.row-2, 0, term.col-1); - if (get_file_buffer(focused_window)->mode & BUFFER_SEARCH_BLOCKING) - xdrawcursor(x_end, term.row-1, 1); -} - -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 -buffer_undo(struct file_buffer* buf) -{ - struct file_buffer* fb = get_file_buffer(focused_window); - if (fb->current_undo_buffer == 0) { - writef_to_status_bar("end of undo buffer"); - return; - } - fb->current_undo_buffer--; - fb->available_redo_buffers++; - - buffer_copy_ub_to_current(focused_window); - writef_to_status_bar("undo"); -} - -void -buffer_redo(struct file_buffer* buf) -{ - struct file_buffer* fb = get_file_buffer(focused_window); - if (fb->available_redo_buffers == 0) { - writef_to_status_bar("end of redo buffer"); - return; - } - fb->available_redo_buffers--; - fb->current_undo_buffer++; - - buffer_copy_ub_to_current(focused_window); - writef_to_status_bar("redo"); -} - -void -buffer_add_to_undo(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; -} - - -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); - writef_to_status_bar("created new file %s", 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); - buffer.search_term = xmalloc(SEARCH_TERM_MAX_LEN); - buffer.non_blocking_search_term = xmalloc(SEARCH_TERM_MAX_LEN); - memset(buffer.ub, 0, sizeof(struct undo_buffer) * UNDO_BUFFERS_COUNT); - memset(buffer.search_term, 0, SEARCH_TERM_MAX_LEN); - memset(buffer.non_blocking_search_term, 0, SEARCH_TERM_MAX_LEN); - - // change line endings - int offset = 0; - while((offset = buffer_seek_string(&buffer, offset, "\r\n")) >= 0) - buffer_remove(&buffer, offset, 1, 1, 1); - offset = 0; - while((offset = buffer_seek_char(&buffer, offset, '\r')) >= 0) - buffer_change(&buffer, "\n", 1, offset, 1); +// se.c globals - if (buffer_contents_updated) - buffer_contents_updated(&buffer, 0, BUFFER_CONTENT_INIT); +///////////////////////////////////////////// +// Internal functions +// - if (res) - writef_to_status_bar("new buffer %s", buffer.file_path); - return buffer; -} +static int writef_string(int y, int x1, int x2, const char* fmt, ...); +static void color_selection(struct glyph* letter); -void -buffer_destroy(struct file_buffer* fb) -{ - free(fb->ub); - free(fb->contents); - free(fb->file_path); - free(fb->search_term); - free(fb->non_blocking_search_term); - *fb = (struct file_buffer){0}; -} +//////////////////////////////////////////// +// function implementations +// -void -buffer_insert(struct file_buffer* buf, const char* new_content, const int len, const int offset, int do_not_callback) +int +writef_string(int y, int x1, int x2, const char* fmt, ...) { - assert(buf->contents); - if (offset > buf->len || offset < 0) { - fprintf(stderr, "writing past buf %s\n", buf->file_path); - return; - } + char string[STATUS_BAR_MAX_LEN]; - 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; + va_list args; + va_start(args, fmt); + vsnprintf(string, STATUS_BAR_MAX_LEN, fmt, args); + va_end(args); - memcpy(buf->contents+offset, new_content, len); - if (buffer_contents_updated && !do_not_callback) - buffer_contents_updated(buf, offset, BUFFER_CONTENT_NORMAL_EDIT); + return write_string(string, y, x1, x2); } +char status_bar_contents[STATUS_BAR_MAX_LEN] = {0}; +static int status_bar_end; +uint32_t status_bar_bg; 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) +writef_to_status_bar(const char* fmt, ...) { - 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; -} + if (fmt) { + if (status_bar_bg == error_color || + status_bar_bg == warning_color || + status_bar_bg == ok_color) + return; -void -remove_utf8_string_end(char* string) -{ - int line_start = 0; + va_list args; + va_start(args, fmt); + vsnprintf(status_bar_contents, STATUS_BAR_MAX_LEN, fmt, args); + va_end(args); - int len = strlen(string); - if (len <= 0) + status_bar_bg = alternate_bg_dark; return; - int move; - int seek_pos = line_start, follower_pos = line_start; - int n = 0; - while (seek_pos < len) { - Rune u; - int charsize = t_decode_utf8_buffer(string + seek_pos, len - seek_pos, &u); - seek_pos += charsize; - if (!n) { - move = charsize; - n = 1; - } else { - follower_pos += move; - move = charsize; - } } - string[follower_pos] = 0; -} -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); + global_attr = default_attributes; + global_attr.bg = status_bar_bg; + status_bar_end = write_string(status_bar_contents, screen.row-1, 0, screen.col); + screen_set_region(status_bar_end, screen.row-1, screen.col-1, screen.row-1, ' '); + + global_attr = default_attributes; } void -buffer_offset_to_xy(struct window_buffer* buf, int offset, int maxx, int* cx, int* cy) +draw_status_bar() { - 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); - } + writef_to_status_bar(NULL); + xdrawline(0, screen.row-1, screen.col); + draw_horisontal_line(screen.row-2, 0, screen.col-1); + if (get_fb(focused_window)->mode & FB_SEARCH_BLOCKING) + xdrawcursor(status_bar_end, screen.row-1, 1); + status_bar_bg = alternate_bg_dark; } void -buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx, int maxy) +window_node_draw_to_screen(struct window_split_node* wn) { - struct window_buffer* buf = &wn->window; - 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); - LIMIT(miny, 0, maxy); - LIMIT(buf->cursor_offset, 0, fb->len); - tsetregion(minx, miny, maxx, maxy, ' '); - int focused = buf == focused_window && !(fb->mode & BUFFER_SEARCH_BLOCKING); + struct window_buffer* wb = &wn->wb; + struct file_buffer* fb = get_fb(wb); + int minx = wn->minx, miny = wn->miny, + maxx = wn->maxx, maxy = wn->maxy; - if (buf->mode > WINDOW_BUFFER_NORMAL) { - // alternate buffer modes - assert(buf->mode < WINDOW_BUFFER_MODE_LEN); - alternate_buffer_modes[buf->mode](wn, minx, miny, maxx, maxy, focused); - return; - } + LIMIT(wb->cursor_offset, 0, fb->len); + screen_set_region(minx, miny, maxx, maxy, ' '); + int focused = wb == focused_window && !(fb->mode & FB_SEARCH_BLOCKING); 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; + int ox, oy, xscroll; + fb_offset_to_xy(fb, wb->cursor_offset, maxx - minx, wb->y_scroll, &ox, &oy, &xscroll); if (oy < 0) { - buf->y_scroll += oy; + wb->y_scroll += oy; } else { oy += miny - maxy+2; if (oy > 0) - buf->y_scroll += oy; + wb->y_scroll += oy; } - if (buf->y_scroll < 0) - buf->y_scroll = 0; + if (wb->y_scroll < 0) + wb->y_scroll = 0; if (wrap_buffer) xscroll = 0; @@ -1531,7 +120,7 @@ buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx char* repl = fb->contents; char* last = repl + fb->len; char* new_repl; - int line = buf->y_scroll; + int line = wb->y_scroll; while ((new_repl = memchr(repl, '\n', last - repl))) { if (--line < 0) break; @@ -1540,51 +129,47 @@ buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx else return; } - int offset_start = repl - fb->contents; + int offset_start = repl - fb->contents - 1; int cursor_x = 0, cursor_y = 0; - const struct color_scheme* cs = buffer_get_color_scheme(fb); - - // search backwards to find multi-line syntax highlighting - if (cs) { - for (int i = 0; i < cs->entry_count; i++) { - const struct color_scheme_entry cse = cs->entries[i]; - if (cse.mode == COLOR_AROUND || cse.mode == COLOR_INSIDE) { - int offset = 0; - int count = 0; - int start_len = strlen(cse.arg.start); - while((offset = buffer_seek_string(fb, offset, cse.arg.start)) >= 0) { - offset += start_len; - if (offset >= offset_start) - break; - count++; - } - - if (strcmp(cse.arg.start, cse.arg.end) != 0) { - int end_len = strlen(cse.arg.end); - offset = 0; - while((offset = buffer_seek_string(fb, offset, cse.arg.end)) >= 0) { - offset += end_len; - if (offset >= offset_start) - break; - count--; - } - } - if (count > 0) { - offset = buffer_seek_string_backwards(fb, offset_start, cse.arg.start); - do_color_scheme(fb, cs, offset); - break; - } - } - } - } - // actually write to the screen int once = 0; int search_found = 0; int non_blocking_search_found = 0; + + // TODO: verify that last - repl is the same as offset_last - offset_start + int move_buffer_len = last - repl + 2; + uint8_t* move_buffer = xmalloc(move_buffer_len); + memset(move_buffer, 0, move_buffer_len); + move_buffer[0] = 0; + int lastx = x, lasty = y; + int move_buffer_index = 0; + + // TODO: write max string len of 127 + // TODO: make the write thing similar to syntax? + char* new_line_start = NULL; + call_extension(wb_new_line_draw, &new_line_start, wb, y - miny, maxy - y, minx, maxx, &global_attr); + if (new_line_start) { + struct glyph old_attr = global_attr; + global_attr = default_attributes; + x = write_string(new_line_start, y, minx, maxx+1); + global_attr = old_attr; + } + + int tmp = 0; + call_extension(wb_write_status_bar, &tmp, NULL, 0, 0, 0, 0, NULL, NULL); + for (int charsize = 1; repl < last && charsize; repl += charsize) { - if (!once && repl - fb->contents >= buf->cursor_offset) { + if (y > lasty) { + move_buffer[move_buffer_index] = x - minx; + move_buffer[move_buffer_index] |= 1<<7; + } else { + move_buffer[move_buffer_index] = x - lastx; + } + move_buffer_index++; + lastx = x, lasty = y; + + if (!once && repl - fb->contents >= wb->cursor_offset) { // if the buffer being drawn is focused, set the cursor position global once = 1; cursor_x = x - xscroll; @@ -1593,9 +178,6 @@ buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx LIMIT(cursor_y, miny, maxy); } - if (cs) - do_color_scheme(fb, cs, repl - fb->contents); - if (!wrap_buffer && x - xscroll > maxx && *repl != '\n') { charsize = 1; x++; @@ -1609,47 +191,55 @@ buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx if (wrap_buffer && *repl != '\n') continue; charsize = 1; + char* new_line_start = NULL; + call_extension(wb_new_line_draw, &new_line_start, wb, y - miny, maxy - y, minx, maxx, &global_attr); + if (new_line_start) { + struct glyph old_attr = global_attr; + global_attr = default_attributes; + x = write_string(new_line_start, y, minx, maxx+1); + global_attr = old_attr; + } continue; } else if (*repl == '\t') { charsize = 1; if ((x - minx) <= 0) { - x += tsetchar(' ', x - xscroll, y); + x += screen_set_char(' ', x - xscroll, y); if (x >= maxx) continue; } while ((x - minx) % tabspaces != 0 && x - xscroll <= maxx) - x += tsetchar(' ', x - xscroll, y); + x += screen_set_char(' ', x - xscroll, y); if (x - xscroll <= maxx) - x += tsetchar(' ', x, y); + x += screen_set_char(' ', x, y); continue; } - Rune u; - charsize = t_decode_utf8_buffer(repl, last - repl, &u); + rune_t u; + charsize = utf8_decode_buffer(repl, last - repl, &u); int width; if (x - xscroll >= minx) - width = tsetchar(u, x - xscroll, y); + width = screen_set_char(u, x - xscroll, y); else width = wcwidth(u); // drawing search highlight - if (fb->mode & BUFFER_SEARCH_BLOCKING_MASK) { - if (!search_found && buffer_offset_starts_with(fb, repl - fb->contents, fb->search_term)) + if (fb->mode & FB_SEARCH_BLOCKING_MASK) { + if (!search_found && fb_offset_starts_with(fb, repl - fb->contents, fb->search_term)) search_found = strlen(fb->search_term); if (search_found) { - tsetattr(x - xscroll, y)->bg = highlight_color; - tsetattr(x - xscroll, y)->fg = default_attributes.bg; + screen_set_attr(x - xscroll, y)->bg = highlight_color; + screen_set_attr(x - xscroll, y)->fg = default_attributes.bg; search_found--; } } - if (fb->mode & BUFFER_SEARCH_NON_BLOCKING) { - if (!non_blocking_search_found && buffer_offset_starts_with(fb, repl - fb->contents, fb->non_blocking_search_term)) + if (fb->mode & FB_SEARCH_NON_BLOCKING) { + if (!non_blocking_search_found && fb_offset_starts_with(fb, repl - fb->contents, fb->non_blocking_search_term)) non_blocking_search_found = strlen(fb->search_term); if (non_blocking_search_found) { - tsetattr(x - xscroll, y)->fg = highlight_color; - tsetattr(x - xscroll, y)->mode |= ATTR_UNDERLINE; + screen_set_attr(x - xscroll, y)->fg = highlight_color; + screen_set_attr(x - xscroll, y)->mode |= ATTR_UNDERLINE; non_blocking_search_found--; } } @@ -1659,52 +249,45 @@ buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx int offset_end = repl - fb->contents; global_attr = default_attributes; - if (buf->cursor_offset >= fb->len) { + if (wb->cursor_offset >= fb->len) { cursor_x = x - xscroll; cursor_y = MIN(y, maxy); } - if(buffer_written_to_screen_callback) - buffer_written_to_screen_callback(buf, offset_start, offset_end, minx, miny, maxx, maxy); + call_extension(window_written_to_screen, wn, offset_start, offset_end, move_buffer, move_buffer_len); - // TODO: let the user do this int status_end = minx; - if (fb->mode & BUFFER_SEARCH_BLOCKING_IDLE) { - int before; - int search_count = buffer_count_string_instances(fb, fb->search_term, focused_window->cursor_offset, &before); - status_end = writef_string(maxy-1, status_end, maxx+1, " %d/%d", before, search_count); - } - status_end = writef_string(maxy-1, status_end, maxx+1, " %dk ", fb->len/1000); + int write_again; + do { + write_again = 0; + char bar[LINE_MAX_LEN]; + *bar = 0; - char* path = file_path_get_path(fb->file_path); - global_attr.fg = path_color; - status_end = writef_string(maxy-1, status_end, maxx+1, "%s", path); - global_attr = default_attributes; - free(path); - const char* name = strrchr(fb->file_path, '/'); - if (name) - status_end = writef_string(maxy-1, status_end, maxx+1, "%s", name+1); - status_end = writef_string(maxy-1, status_end, maxx+1, " %d:%d %d%%" , cursor_y + buf->y_scroll, cursor_x - minx + xscroll, - (int)(((float)(buf->cursor_offset+1)/(float)fb->len)*100.0f)); - if (fb->mode & BUFFER_SELECTION_ON) { + call_extension(wb_write_status_bar, &write_again, wb, status_end, maxx+1, cursor_x - minx + xscroll, cursor_y - miny + wb->y_scroll, bar, &global_attr); + status_end = write_string(bar, maxy-1, status_end, maxx+1); + + global_attr = default_attributes; + } while (write_again); + + if (fb->mode & FB_SELECTION_ON) { int y1, y2, tmp; - buffer_offset_to_xy(buf, fb->s1o, 0, &tmp, &y1); - buffer_offset_to_xy(buf, fb->s2o, 0, &tmp, &y2); + fb_offset_to_xy(fb, fb->s1o, 0, wb->y_scroll, &tmp, &y1, &tmp); + fb_offset_to_xy(fb, fb->s2o, 0, wb->y_scroll, &tmp, &y2, &tmp); writef_string(maxy-1, status_end, maxx, " %dL", abs(y1-y2)); } if (focused) { for (int i = minx; i < maxx+1; i++) { - if (!(fb->mode & BUFFER_SELECTION_ON)) { - if (tsetattr(i, cursor_y)->bg == default_attributes.bg) - tsetattr(i, cursor_y)->bg = mouse_line_bg; + if (!(fb->mode & FB_SELECTION_ON)) { + if (screen_set_attr(i, cursor_y)->bg == default_attributes.bg) + screen_set_attr(i, cursor_y)->bg = mouse_line_bg; } - tsetattr(i, maxy-1)->bg = alternate_bg_bright; + screen_set_attr(i, maxy-1)->bg = alternate_bg_bright; } } - buffer_write_selection(buf, minx, miny, maxx, maxy); - do_color_scheme(NULL, &(struct color_scheme){0}, 0); + wb_write_selection(wb, minx, miny, maxx, maxy); + //do_syntax_scheme(NULL, &(struct syntax_scheme){0}, 0); for (int i = miny; i < maxy; i++) xdrawline(minx, i, maxx+1); @@ -1712,508 +295,82 @@ buffer_draw_to_screen(struct window_split_node* wn, int minx, int miny, int maxx draw_horisontal_line(maxy-1, minx, maxx); xdrawcursor(cursor_x, cursor_y, focused); -} - -// TODO: Scope checks (with it's own system, so that it can be used for auto indent as well) -void -do_color_scheme(struct file_buffer* fb, const 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 || !cs) { - // 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 (buffer_offset_starts_with(fb, offset, end_condition)) { - 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 until later - if (around) - around = 0; - else - global_attr = default_attributes; - - end_condition = NULL; - end_at_whitespace = 0; - } - return; - } else if (end_at_whitespace) { - if (!buffer_is_on_a_word(fb, offset, cs->word_seperators)) { - end_at_whitespace = 0; - global_attr = default_attributes; - } else { - return; - } - } else if (color_next_word) { - // check if new word encountered - if (!buffer_is_on_a_word(fb, offset, cs->word_seperators)) - 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) { - if (!buffer_is_start_of_a_word(fb, offset, cs->word_seperators)) - continue; - - 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 UPPER_CASE_WORD_MIN_LEN chars - if (end_len < UPPER_CASE_WORD_MIN_LEN) - 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 - offset_tmp = buffer_seek_word_end(fb, offset_tmp, cs->word_seperators); - if (offset_tmp == offset && mode == COLOR_WORD_BEFORE_STR_STR) - goto exit_word_before_str_str; - if (first_time) - first_word_len = offset_tmp - offset; - - if (mode != COLOR_WORD_ENDING_WITH_STR) - offset_tmp = buffer_seek_not_whitespace(fb, offset_tmp); - - first_time = 0; - } - - if (mode == COLOR_WORD_ENDING_WITH_STR) { - offset_tmp -= len; - if (offset_tmp < 0) - continue; - } - if (buffer_offset_starts_with(fb, offset_tmp, entry.arg.start)) { - 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 (buffer_offset_starts_with(fb, offset - len, entry.arg.start)) { - if (offset < fb->len && buffer_offset_starts_with(fb, offset, entry.arg.end)) - continue; - - if (mode == COLOR_WORD_INSIDE) { - // verify that only one word exists inside - int offset_tmp = offset; - offset_tmp = buffer_seek_not_whitespace(fb, offset_tmp); - offset_tmp = buffer_seek_word_end(fb, offset_tmp, cs->word_seperators); - offset_tmp = buffer_seek_not_whitespace(fb, offset_tmp); - - if (!buffer_offset_starts_with(fb, offset_tmp, entry.arg.end) || - offset_tmp - offset <= 1) - continue; - } - - end_condition = entry.arg.end; - end_condition_len = strlen(entry.arg.end); - global_attr = entry.attr; - around = 0; - if (entry.mode == COLOR_INSIDE_TO_LINE) - end_at_whitespace = 1; - return; - } - continue; - } - - if ((mode == COLOR_AROUND || mode == COLOR_AROUND_TO_LINE) && - buffer_offset_starts_with(fb, offset, entry.arg.start)) { - 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; - global_attr = entry.attr; - return; - } - 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(!buffer_offset_starts_with(fb, offset, entry.arg.start) || - !buffer_is_start_of_a_word(fb, offset, cs->word_seperators) || - (buffer_is_on_a_word(fb, offset + len, cs->word_seperators) && mode != COLOR_WORD_STARTING_WITH_STR)) - continue; - - if (mode == COLOR_WORD_STR) { - int offset_str = buffer_seek_not_whitespace(fb, offset + len); - - if (!buffer_offset_starts_with(fb, offset_str, entry.arg.end)) - continue; - end_condition_len = strlen(entry.arg.start); - } else { - end_at_whitespace = 1; - } - if (mode == COLOR_STR_AFTER_WORD) { - next_word_attr = entry.attr; - color_next_word = 1; - continue; - } - global_attr = entry.attr; - return; - } - if (mode == COLOR_STR) { - if (!buffer_offset_starts_with(fb, offset, entry.arg.start)) - continue; - end_condition_len = len; - global_attr = entry.attr; - return; - } - } + free(move_buffer); } int write_string(const char* string, int y, int minx, int maxx) { - LIMIT(maxx, 0, term.col); + if (!string) + return 0; + LIMIT(maxx, 0, screen.col); LIMIT(minx, 0, maxx); 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); + rune_t u; + int charsize = utf8_decode_buffer(string + offset, len - offset, &u); offset += charsize; if (charsize == 1 && u <= 32) u = ' '; - minx += tsetchar(u, minx, y); + minx += screen_set_char(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) +color_selection(struct glyph* letter) { if (letter->bg == default_attributes.bg) 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) +wb_move_cursor_to_selection_start(struct window_buffer* wb) { - 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); + const struct file_buffer* fb = get_fb(wb); + if (fb_is_selection_start_top_left(fb)) + wb_move_to_offset(wb, fb->s1o, CURSOR_SNAPPED); else - buffer_move_to_offset(buf, fb->s2o, CURSOR_SNAPPED); + wb_move_to_offset(wb, fb->s2o, CURSOR_SNAPPED); } void -buffer_write_selection(struct window_buffer* buf, int minx, int miny, int maxx, int maxy) +wb_write_selection(struct window_buffer* wb, int minx, int miny, int maxx, int maxy) { - assert(buf); - struct file_buffer* fb = get_file_buffer(buf); + soft_assert(wb, return;); + struct file_buffer* fb = get_fb(wb); - LIMIT(maxx, 0, term.col-1); - LIMIT(maxy, 0, term.row-1); + LIMIT(maxx, 0, screen.col-1); + LIMIT(maxy, 0, screen.row-1); LIMIT(minx, 0, maxx); LIMIT(miny, 0, maxy); - //TODO: implement alternative selection modes - if (!(fb->mode & BUFFER_SELECTION_ON)) + if (!(fb->mode & FB_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); + int x, y, x2, y2, tmp, xscroll; + if (fb_is_selection_start_top_left(fb)) { + fb_offset_to_xy(fb, fb->s1o, maxx - minx, wb->y_scroll, &x, &y, &tmp); + fb_offset_to_xy(fb, fb->s2o, maxx - minx, wb->y_scroll, &x2, &y2, &xscroll); } else { - buffer_offset_to_xy(buf, fb->s2o, maxx - minx, &x, &y); - buffer_offset_to_xy(buf, fb->s1o, maxx - minx, &x2, &y2); + fb_offset_to_xy(fb, fb->s2o, maxx - minx, wb->y_scroll, &x, &y, &xscroll); + fb_offset_to_xy(fb, fb->s1o, maxx - minx, wb->y_scroll, &x2, &y2, &tmp); } x += minx, x2 += minx + 1; y += miny, y2 += miny; + if (!wrap_buffer) { + x -= xscroll; + x2 -= xscroll; + } for(; y < y2; y++) { - for(; x < maxx; x++) - color_selection(tsetattr(x, y)); + for(; x <= maxx; x++) + color_selection(screen_set_attr(x, y)); x = 0; } for(; x < x2; x++) - color_selection(tsetattr(x, y)); -} - -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); -} - -char* -buffer_get_line_at_offset(const struct file_buffer* fb, int offset) -{ - int start = buffer_seek_char_backwards(fb, offset, '\n'); - if (start < 0) start = 0; - int end = buffer_seek_char(fb, offset, '\n'); - if (end < 0) end = fb->len-1; - - int len = end - start; - - char* res = xmalloc(len + 1); - if (len > 0) - memcpy(res, fb->contents+start, len); - res[len] = 0; - return res; -} - -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); - writef_to_status_bar("written buffer to %s", buffer->file_path); - - 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; - - if (u == 0) - u = term.line[y][x].u; - 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 = ' '; - term.line[y][x+1].mode |= ATTR_WDUMMY; - } - } else if (term.line[y][x].mode & ATTR_WDUMMY && x-1 >= 0) { - 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; -} - -Glyph* -tsetattr(int x, int y) -{ - static Glyph dummy; - if (y >= term.row || x >= term.col || - y < 0 || x < 0) - return &dummy; - - return &term.line[y][x]; -} - -Rune -tgetrune(int x, int y) -{ - if (y >= term.row || x >= term.col || - y < 0 || x < 0) - return 0; - return term.line[y][x].u; -} - -void -tsetregion(int x1, int y1, int x2, int y2, Rune u) -{ - for (int y = y1; y <= y2; y++) - for (int x = x1; x <= x2; x++) - tsetchar(u, x, y); -} - -void -tresize(int col, int row) -{ - int i; - - 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[i] = NULL; - } - } - - 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; -} - -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; + color_selection(screen_set_attr(x, y)); } diff --git a/se.h b/se.h @@ -2,335 +2,22 @@ #ifndef _ST_H #define _ST_H -#include <stdint.h> -#include <sys/types.h> -#include <wchar.h> -#include <limits.h> -#include <dirent.h> +#include "utf8.h" +#include "buffer.h" +#include "seek.h" +#include "x.h" -// Arbitrary sizes -#define UTF_INVALID 0xFFFD -#define UTF_SIZ 4 -#define UNDO_BUFFERS_COUNT 32 -#define UPPER_CASE_WORD_MIN_LEN 3 #define STATUS_BAR_MAX_LEN 4096 -#define SEARCH_TERM_MAX_LEN PATH_MAX -#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 -// - -// NOTE: -// refrain from entering the buffers or buffer keyword searching -// mode if you already are in file browser mode -enum window_buffer_mode { - WINDOW_BUFFER_NORMAL = 0, - WINDOW_BUFFER_FILE_BROWSER, - WINDOW_BUFFER_SEARCH_BUFFERS, - WINDOW_BUFFER_KEYWORD_ALL_BUFFERS, - WINDOW_BUFFER_MODE_LEN, -}; - -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); - -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; - char* search; - int selected; -}; - -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, float amount); -void window_node_resize_absolute(struct window_split_node* node, enum move_directons move, float amount); - -//////////////////////////////////////////////// -// 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; -}; - -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, - BUFFER_SEARCH_BLOCKING = 1 << 5, - BUFFER_SEARCH_BLOCKING_IDLE = 1 << 6, - BUFFER_SEARCH_BLOCKING_MASK = (BUFFER_SEARCH_BLOCKING | BUFFER_SEARCH_BLOCKING_IDLE), - BUFFER_SEARCH_NON_BLOCKING = 1 << 7, - BUFFER_SEARCH_BLOCKING_BACKWARDS = 1 << 8, - BUFFER_SEARCH_NON_BLOCKING_BACKWARDS = 1 << 9, -}; - -// Contents of a file buffer -struct file_buffer { - char* file_path; - char* contents; // !! NOT NULL TERMINATED !! - int len; - int capacity; - enum buffer_flags mode; - struct undo_buffer* ub; - int current_undo_buffer; - int available_redo_buffers; - int s1o, s2o; // selection start offset and end offset - char* search_term; - char* non_blocking_search_term; -}; - -const struct color_scheme* buffer_get_color_scheme(struct file_buffer* fb); -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); -void buffer_undo(struct file_buffer* buf); -void buffer_redo(struct file_buffer* buf); - -int buffer_is_on_a_word(const struct file_buffer* fb, int offset, const char* word_seperators); -int buffer_is_start_of_a_word(const struct file_buffer* fb, int offset, const char* word_seperators); -int buffer_is_on_word(const struct file_buffer* fb, int offset, const char* word_seperators, const char* word); -int buffer_offset_starts_with(const struct file_buffer* fb, int offset, const char* start); -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); -int buffer_seek_string_wrap(const struct window_buffer* wb, int offset, const char* search); -int buffer_seek_string_wrap_backwards(const struct window_buffer* wb, int offset, const char* search); -int buffer_seek_word(const struct file_buffer* fb, int offset, const char* word_seperators); -int buffer_seek_word_end(const struct file_buffer* fb, int offset, const char* word_seperators); -int buffer_seek_word_backwards(const struct file_buffer* fb, int offset, const char* word_seperators); -int buffer_seek_whitespace(const struct file_buffer* fb, int offset); -int buffer_seek_whitespace_backwrads(const struct file_buffer* fb, int offset); -int buffer_seek_not_whitespace(const struct file_buffer* fb, int offset); -int buffer_seek_not_whitespace_backwrads(const struct file_buffer* fb, int offset); - -int buffer_count_string_instances(const struct file_buffer* fb, const char* string, int offset, int* before_offset); - -/////////////////////////////////// -// 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); - -/////////////////////////////////// -// returns a null terminated string containing the current line -// the returned value must be freed by the reciever -char* buffer_get_line_at_offset(const struct file_buffer* fb, int offset); -// result must be freed -char* file_path_get_path(const char* path); - -//////////////////////////////////////////////// -// Other -// - -struct keyword_pos { - int offset, buffer_index; -}; - -const char* file_browser_next_item(const char* path, const char* search, int* offset, Glyph* attr, void* data); -// data pointer will give the file buffer of the current item -const char* buffer_search_next_item(const char* tmp, const char* search, int* offset, Glyph* attr, void* data); -// data pointer will give the keyword_pos of the current item -const char* buffers_search_keyword_next_item(const char* tmp, const char* search, int* offset, Glyph* attr, void* data); - -void die(const char *, ...); -int is_file_type(const char* file_path, const char* file_type); -int path_is_folder(const char* path); int write_string(const char* string, int y, int minx, int maxx); -int writef_to_status_bar(const char* fmt, ...); -void draw_status_bar(); -void remove_utf8_string_end(char* string); - -// Internal representation of the screen -typedef struct { - int row; // row count - int col; // column count - Line *line; // array -} Term; - -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); -Glyph* tsetattr(int x, int y); -Rune tgetrune(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, -}; +// TODO: make this user addable +// so lessen the size of the root window and put this at the bottom (each frame) +extern char status_bar_contents[STATUS_BAR_MAX_LEN]; +extern uint32_t status_bar_bg; +void writef_to_status_bar(const char* fmt, ...); +void draw_status_bar(); -void buffer_add_to_undo(struct file_buffer* buffer, int offset, enum buffer_content_reason reason); +void window_node_draw_to_screen(struct window_split_node* wn); #endif // _ST_H diff --git a/seek.c b/seek.c @@ -0,0 +1,484 @@ + + +#if 1 +#define _GNU_SOURCE +#endif + +#include "seek.h" + +#include <string.h> +#include <ctype.h> + +#include "config.h" +#include <sys/stat.h> + +#if 0 +#define BOUNDS_CHECK(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#else +#define BOUNDS_CHECK(x, a, b) +#endif + +int +str_contains_char(const char* string, char check) +{ + return strchr(string, check) != NULL; +} + +inline 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; +} + +char* +file_path_get_path(const char* path) +{ + soft_assert(path, 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; +} + +inline int +path_is_folder(const char* path) +{ + struct stat statbuf; + if (stat(path, &statbuf) != 0) + return 0; + return S_ISDIR(statbuf.st_mode); +} + +inline int +fb_seek_char(const struct file_buffer* fb, int offset, char byte) +{ + if (offset > fb->len) return -1; + char* new_buf = memchr(fb->contents + offset, byte, fb->len - offset); + if (!new_buf) return -1; + return new_buf - fb->contents; +} + +inline int +fb_seek_char_backwards(const struct file_buffer* fb, int offset, char byte) +{ + BOUNDS_CHECK(offset, 0, fb->len-1); + for (int n = offset-1; n >= 0; n--) { + if (fb->contents[n] == byte) { + return n+1; + } + } + return -1; +} + +inline int +fb_seek_string(const struct file_buffer* fb, int offset, const char* string) +{ + BOUNDS_CHECK(offset, 0, fb->len-1); + int str_len = strlen(string); + +#if 0 + for (int n = offset; n < fb->len - str_len; n++) + if (!memcmp(fb->contents + n, string, str_len)) + return n; +#else + char* res = memmem(fb->contents + offset, fb->len - offset, + string, str_len); + if (res) + return res - fb->contents; +#endif + return -1; +} + +inline int +fb_seek_string_backwards(const struct file_buffer* fb, int offset, const char* string) +{ + int str_len = strlen(string); + offset += str_len; + BOUNDS_CHECK(offset, 0, fb->len-1); + + for (int n = offset - str_len; n >= 0; n--) + if (!memcmp(fb->contents + n, string, str_len)) + return n; + return -1; +} + +inline int +wb_seek_string_wrap(const struct window_buffer* wb, int offset, const char* search) +{ + struct file_buffer* fb = get_fb(focused_window); + if (*search == 0 || !fb_count_string_instances(fb, search, 0, NULL)) + return -1; + + int new_offset = fb_seek_string(fb, offset, search); + if (new_offset < 0) + new_offset = fb_seek_string(fb, 0, search); + + if (!(fb->mode & FB_SEARCH_BLOCKING)) + fb->mode |= FB_SEARCH_BLOCKING_IDLE; + return new_offset; +} + +inline int +wb_seek_string_wrap_backwards(const struct window_buffer* wb, int offset, const char* search) +{ + struct file_buffer* fb = get_fb(focused_window); + if (*search == 0 || !fb_count_string_instances(fb, search, 0, NULL)) + return -1; + + int new_offset = fb_seek_string_backwards(fb, offset, search); + if (new_offset < 0) + new_offset = fb_seek_string_backwards(fb, fb->len, search); + + if (!(fb->mode & FB_SEARCH_BLOCKING)) + fb->mode |= FB_SEARCH_BLOCKING_IDLE; + return new_offset; +} + +int +fb_is_on_a_word(const struct file_buffer* fb, int offset, const char* word_seperators) +{ + BOUNDS_CHECK(offset, 0, fb->len); + return !str_contains_char(word_seperators, fb->contents[offset]); +} + +inline int +fb_is_start_of_a_word(const struct file_buffer* fb, int offset, const char* word_seperators) +{ + BOUNDS_CHECK(offset, 0, fb->len); + return fb_is_on_a_word(fb, offset, word_seperators) && + (offset-1 <= 0 || str_contains_char(word_seperators, fb->contents[offset-1])); +} + +inline int +fb_is_on_word(const struct file_buffer* fb, int offset, const char* word_seperators, const char* word) +{ + BOUNDS_CHECK(offset, 0, fb->len); + int word_start = fb_seek_start_of_word_backwards(fb, offset, word_seperators); + int word_len = strlen(word); + if (word_start < offset - (word_len-1)) + return 0; + return fb_offset_starts_with(fb, word_start, word) && + !fb_is_on_a_word(fb, word_start + word_len, word_seperators); +} + +inline int +fb_offset_starts_with(const struct file_buffer* fb, int offset, const char* start) +{ + BOUNDS_CHECK(offset, 0, fb->len); + if (offset > 0 && fb->contents[offset-1] == '\\') return 0; + + int len = strlen(start); + int mlen = MIN(len, fb->len - offset); + return memcmp(fb->contents + offset, start, mlen) == 0; +} + +inline int +fb_seek_word(const struct file_buffer* fb, int offset, const char* word_seperators) +{ + if (fb_is_on_a_word(fb, offset, word_seperators)) + offset = fb_seek_word_end(fb, offset, word_seperators); + while (offset < fb->len && str_contains_char(word_seperators, fb->contents[offset])) offset++; + return offset; +} + +inline int +fb_seek_word_end(const struct file_buffer* fb, int offset, const char* word_seperators) +{ + BOUNDS_CHECK(offset, 0, fb->len); + if (!fb_is_on_a_word(fb, offset, word_seperators)) + offset = fb_seek_word(fb, offset, word_seperators); + while (offset < fb->len && !str_contains_char(word_seperators, fb->contents[offset])) offset++; + return offset; +} + +inline int +fb_seek_word_backwards(const struct file_buffer* fb, int offset, const char* word_seperators) +{ + BOUNDS_CHECK(offset, 0, fb->len); + if (!fb_is_on_a_word(fb, offset, word_seperators)) + while (offset > 0 && str_contains_char(word_seperators, fb->contents[offset])) offset--; + return offset; +} + +inline int +fb_seek_start_of_word_backwards(const struct file_buffer* fb, int offset, const char* word_seperators) +{ + BOUNDS_CHECK(offset, 0, fb->len); + if (!fb_is_on_a_word(fb, offset, word_seperators)) + while (offset > 0 && str_contains_char(word_seperators, fb->contents[offset])) offset--; + while (offset > 0 && !str_contains_char(word_seperators, fb->contents[offset])) offset--; + return offset+1; +} + +inline int +fb_seek_whitespace(const struct file_buffer* fb, int offset) +{ + BOUNDS_CHECK(offset, 0, fb->len); + while (offset < fb->len && !isspace(fb->contents[offset])) offset++; + return offset; +} + +inline int +fb_seek_whitespace_backwards(const struct file_buffer* fb, int offset) +{ + BOUNDS_CHECK(offset, 0, fb->len); + while (offset > 0 && !isspace(fb->contents[offset])) offset--; + return offset; +} + +inline int +fb_seek_not_whitespace(const struct file_buffer* fb, int offset) +{ + BOUNDS_CHECK(offset, 0, fb->len); + while (offset < fb->len && isspace(fb->contents[offset])) offset++; + return offset; +} + +inline int +fb_seek_not_whitespace_backwards(const struct file_buffer* fb, int offset) +{ + BOUNDS_CHECK(offset, 0, fb->len); + while (offset > 0 && isspace(fb->contents[offset])) offset--; + return offset; +} + +inline int +fb_count_string_instances(const struct file_buffer* fb, const char* string, int offset, int* before_offset) +{ + int tmp; + if (!before_offset) + before_offset = &tmp; + if (!string || *string == 0) { + *before_offset = 0; + return 0; + } + + int pos = -1; + int count = 0; + int once = 1; + while((pos = fb_seek_string(fb, pos+1, string)) >= 0) { + if (pos > 0 && fb->contents[pos-2] == '\\') + continue; + if (once && pos > offset) { + *before_offset = count; + once = 0; + } + count++; + } + if (once) + *before_offset = count; + return count; +} + +/* +** Search from the start of the file +** Once any patterns are found, other patterns are disabled. +** If the pattern we are searching for extends beyond *offset* then we will use that as our delimiter +*/ + +/////////////// +// " \" // we're here " +// ↑ ↑ +// start seeked end +int +fb_seek_string_not_escaped(const struct file_buffer* fb, int offset, const char* string) +{ + for (;;) { + offset = fb_seek_string(fb, offset, string); + if (offset >= 0 && fb->contents[offset-1] == '\\') + offset++; + else + break; + } + return offset; +} + +inline int +fb_seek_string_backwards_not_escaped(const struct file_buffer* fb, int offset, const char* string) +{ + for (;;) { + offset = fb_seek_string_backwards(fb, offset, string); + if (offset >= 0 && fb->contents[offset-1] == '\\') + offset--; + else + break; + } + return offset; +} + +inline static int +fb_get_delimiter_equal_start_end(const struct file_buffer* fb, int offset, const char* string, struct delimiter* ignore, int* start, int* end) +{ + int ignore_index = 0; + int last_distance = 0; + + while (last_distance >= 0) { + int d_res = INT32_MAX; + int i = 0; + if (ignore) { + for (struct delimiter* ign = ignore; ign->start; ign++) { + int d = fb_seek_string_not_escaped(fb, last_distance, ign->start); + if (d < 0 || d > offset) { + i++; + continue; + } + + if (d < d_res) { + d_res = d; + ignore_index = i; + } + i++; + } + } + int str_d = fb_seek_string_not_escaped(fb, last_distance, string); + + if ((str_d <= 0 || str_d > offset) && d_res == INT32_MAX) + return 0; + + const char* end_str; + if (str_d >= 0 && str_d <= d_res) { + last_distance = str_d; + end_str = string; + *start = last_distance; + } else { + last_distance = d_res; + end_str = ignore[ignore_index].end; + } + + last_distance = fb_seek_string_not_escaped(fb, last_distance+1, end_str); + + if (end_str == string && last_distance > offset) { + *end = last_distance; + return 1; + } + if (last_distance >= 0) + last_distance++; + } + return 0; +} + +inline static int +fb_get_matched_delimiter_end(const struct file_buffer* fb, int offset, struct delimiter d) +{ + int closers_left = 1; + while (closers_left) { + int next_open = fb_seek_string_not_escaped(fb, offset, d.start); + int next_close = fb_seek_string_not_escaped(fb, offset, d.end); + + if (next_close < 0 || next_open < 0) + return next_close; + + if (next_open < next_close) { + closers_left++; + offset = next_open+1; + } else { + closers_left--; + offset = next_close; + if (closers_left) + offset++; + } + } + return offset; +} + +inline static int +fb_get_matched_delimiter_start(const struct file_buffer* fb, int offset, struct delimiter d) +{ + int openers_left = 1; + while (openers_left) { + int next_open = fb_seek_string_backwards_not_escaped(fb, offset, d.start); + int next_close = fb_seek_string_backwards_not_escaped(fb, offset, d.end); + + if (next_close < 0 || next_open < 0) + return next_open; + + if (next_open < next_close) { + openers_left++; + offset = next_open-1; + } else { + openers_left--; + offset = next_open; + if (openers_left) + offset--; + } + } + return offset; +} + +inline int +fb_get_delimiter(const struct file_buffer* fb, int offset, struct delimiter d, struct delimiter* ignore, int* start, int* end) +{ + const char* start_word = d.start; + const char* end_word = d.end; + + soft_assert(start, return 0;); + soft_assert(end, return 0;); + soft_assert(start_word, return 0;); + soft_assert(end_word, return 0;); + + if (strcmp(start_word, end_word) == 0) + return fb_get_delimiter_equal_start_end(fb, offset, start_word, ignore, start, end); + int wanted_opener = fb_get_matched_delimiter_start(fb, offset, d); + + int ignore_index = 0; + int last_distance = 0; + + while (last_distance >= 0) { + int d_res = INT32_MAX; + int i = 0; + if (ignore) { + for (struct delimiter* ign = ignore; ign->start; ign++) { + int d = fb_seek_string_not_escaped(fb, last_distance, ign->start); + if (d < 0 || d > offset) { + i++; + continue; + } + + if (d < d_res) { + d_res = d; + ignore_index = i; + } + i++; + } + } + int str_d = fb_seek_string_not_escaped(fb, last_distance, start_word); + + if ((str_d <= 0 || str_d > offset) && d_res == INT32_MAX) + return 0; + + if (str_d >= 0 && str_d <= d_res) { + if (str_d == wanted_opener) { + last_distance = str_d; + *start = last_distance; + + last_distance = fb_get_matched_delimiter_end(fb, last_distance+1, d); + + if (last_distance > offset) { + *end = last_distance; + return 1; + } + } + } else { + last_distance = d_res; + + last_distance = fb_seek_string_not_escaped(fb, last_distance+1, ignore[ignore_index].end); + } + + if (last_distance >= 0) + last_distance++; + } + return 0; +} diff --git a/seek.h b/seek.h @@ -0,0 +1,57 @@ +#ifndef SEEK_H_ +#define SEEK_H_ + +// TODO: check that all functions are in the right place +// complete rework / file renaming first + +#include "buffer.h" +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) + +struct delimiter { + char* start; + char* end; +}; + +int str_contains_char(const char* string, char check); +///////////// +// result must be freed +char* file_path_get_path(const char* path); +int is_file_type(const char* file_path, const char* file_type); +int path_is_folder(const char* path); + +int fb_is_on_a_word(const struct file_buffer* fb, int offset, const char* word_seperators); +int fb_is_start_of_a_word(const struct file_buffer* fb, int offset, const char* word_seperators); +int fb_is_on_word(const struct file_buffer* fb, int offset, const char* word_seperators, const char* word); + +int fb_offset_starts_with(const struct file_buffer* fb, int offset, const char* start); + +int fb_seek_char(const struct file_buffer* fb, int offset, char byte); +int fb_seek_char_backwards(const struct file_buffer* fb, int offset, char byte); + +int fb_seek_string(const struct file_buffer* fb, int offset, const char* string); +int fb_seek_string_not_escaped(const struct file_buffer* fb, int offset, const char* string); +int fb_seek_string_backwards(const struct file_buffer* fb, int offset, const char* string); +int fb_seek_string_backwards_not_escaped(const struct file_buffer* fb, int offset, const char* string); +int wb_seek_string_wrap(const struct window_buffer* wb, int offset, const char* search); +int wb_seek_string_wrap_backwards(const struct window_buffer* wb, int offset, const char* search); + +int fb_seek_word(const struct file_buffer* fb, int offset, const char* word_seperators); +int fb_seek_word_end(const struct file_buffer* fb, int offset, const char* word_seperators); +int fb_seek_word_backwards(const struct file_buffer* fb, int offset, const char* word_seperators); +int fb_seek_start_of_word_backwards(const struct file_buffer* fb, int offset, const char* word_seperators); + +int fb_seek_whitespace(const struct file_buffer* fb, int offset); +int fb_seek_whitespace_backwards(const struct file_buffer* fb, int offset); +int fb_seek_not_whitespace(const struct file_buffer* fb, int offset); +int fb_seek_not_whitespace_backwards(const struct file_buffer* fb, int offset); +// TODO: +//int fb_has_whitespace_between(const struct file_buffer* fb, int start, int end); + +int fb_count_string_instances(const struct file_buffer* fb, const char* string, int offset, int* before_offset); + +//////////////////////// +// struct delimiter* ignore is a null terminated pointer array +int fb_get_delimiter(const struct file_buffer* fb, int offset, struct delimiter d, struct delimiter* ignore, int* start, int* end); + + +#endif // SEEK_H_ diff --git a/utf8.c b/utf8.c @@ -0,0 +1,117 @@ +#include "utf8.h" + +// TODO(not important): +// optimize charsize algorthim + +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) + +static const uint8_t utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const uint8_t utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; + +static size_t utf8_decode(const char *, rune_t *, size_t); +static rune_t utf8_decodebyte(char, size_t *); +static char utf8_encodebyte(rune_t, size_t); +static size_t utf8_validate(rune_t *, size_t); + +rune_t +utf8_decodebyte(char c, size_t *i) +{ + for (*i = 0; *i < LEN(utfmask); ++(*i)) + if (((uint8_t)c & utfmask[*i]) == utfbyte[*i]) + return (uint8_t)c & ~utfmask[*i]; + return 0; +} + +size_t +utf8_encode(rune_t u, char *c) +{ + size_t len, i; + + len = utf8_validate(&u, 0); + if (len > UTF_SIZ) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = utf8_encodebyte(u, 0); + u >>= 6; + } + c[0] = utf8_encodebyte(u, len); + + return len; +} + +char +utf8_encodebyte(rune_t u, size_t i) +{ + return utfbyte[i] | (u & ~utfmask[i]); +} + +size_t +utf8_validate(rune_t *u, size_t i) +{ + const rune_t utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; + const rune_t 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; +} + +size_t +utf8_decode(const char *c, rune_t *u, size_t clen) +{ + size_t i, j, len, type; + rune_t udecoded; + + *u = UTF_INVALID; + if (!clen) + return 0; + udecoded = utf8_decodebyte(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) | utf8_decodebyte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8_validate(u, len); + + return len; +} + +int +utf8_decode_buffer(const char* buffer, const int buflen, rune_t* u) +{ + if (!buflen) return 0; + + rune_t u_tmp; + int charsize; + if (!u) + u = &u_tmp; + + // process a complete utf8 char + charsize = utf8_decode(buffer, u, buflen); + + return charsize; +} + +void +utf8_remove_string_end(char* string) +{ + char* end = string + strlen(string); + if (end == string) + return; + + do { + end--; + // if byte starts with 0b10, byte is an UTF-8 extender + } while (end > string && (*end & 0xC0) == 0x80); + *end = 0; +} diff --git a/utf8.h b/utf8.h @@ -0,0 +1,19 @@ +#ifndef UTF8_H_ +#define UTF8_H_ + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <wchar.h> + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +typedef uint_least32_t rune_t; + +size_t utf8_encode(rune_t, char *); +int utf8_decode_buffer(const char* buffer, const int buflen, rune_t* u); +void utf8_remove_string_end(char* string); + + +#endif // UTF8_H_ diff --git a/x.c b/x.c @@ -5,11 +5,9 @@ ** 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> @@ -18,46 +16,17 @@ #include <stdarg.h> #include <unistd.h> #include <dirent.h> +#include <assert.h> +#include "se.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); -extern void(*startup_callback)(void); -// TODO: planned callbacks: -// buffer focused -// window focused +#include "config.h" +#include "extension.h" ////////////////////////////////// // 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 @@ -72,21 +41,98 @@ extern void(*startup_callback)(void); #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#include <X11/Xatom.h> +#include <X11/cursorfont.h> +#include <X11/Xft/Xft.h> +#include <X11/XKBlib.h> + +// 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 +} TermWindow; +extern TermWindow win; + +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; +extern XWindow xw; + +// 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_t unicodep; +} Fontcache; + +extern Fontcache *frc; +extern int frclen; + +// Drawing Context +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; +extern DC dc; + //////////////////////////////////////// // Internal Functions // -static int file_browser_actions(KeySym keysym, int modkey); -static void file_browser_string_insert(const char* buf, int buflen); -static int buffer_search_actions(KeySym keysym, int modkey); -static void buffer_search_string_insert(const char* buf, int buflen); - -static int search_term_actions(KeySym keysym, int modkey); -static void search_term_string_insert(const char* buf, int buflen); -static int open_seproj(struct file_buffer fb); -static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); -static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); -static void xdrawglyph(Glyph, int, int); +static void xunloadfont(Font *); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const struct glyph *, int, int, int); +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, struct glyph, int, int, int); +static void xdrawglyph(struct glyph, int, int); static void xclear(int, int, int, int); static int xgeommasktogravity(int); static int ximopen(Display *); @@ -135,26 +181,20 @@ static void (*handler[LASTEvent])(XEvent *) = { // Globals // -extern Term term; +struct screen screen; +struct glyph global_attr; -static struct file_buffer* file_buffers; -static int available_buffer_slots = 0; +static Atom xtarget; +static char* copy_buffer; +static int copy_len; -static char root_node_search[SEARCH_TERM_MAX_LEN]; -struct window_split_node root_node = {.mode = WINDOW_SINGULAR, .search = root_node_search}; -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; +DC dc; // Fontcache is an array. A new font will be appended to the array. -int frccap = 0; Fontcache *frc = NULL; +int frccap = 0; int frclen = 0; double defaultfontsize = 0; double usedfontsize = 0; @@ -163,157 +203,171 @@ double usedfontsize = 0; // function implementations // -struct file_buffer* -get_file_buffer(struct window_buffer* buf) +void +screen_init(int col, int row) { - assert(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; - - if (!file_buffers[buf->buffer_index].contents) { - for(int n = buf->buffer_index; n < available_buffer_slots; n++) { - if (file_buffers[n].contents) { - buf->buffer_index = n; - assert(file_buffers[n].contents); - return &file_buffers[n]; - } - } - 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]; - } + global_attr = default_attributes; - buf->buffer_index = new_file_buffer_entry(NULL); - writef_to_status_bar("all buffers were somehow deleted, creating new one"); - return get_file_buffer(buf); + screen.col = 0; + screen.row = 0; + screen.lines = NULL; + screen_resize(col, row); } - -int -open_seproj(struct file_buffer fb) +void +draw_horisontal_line(int y, int x1, int x2) { - int first = -1; + if (y < 0 || y > screen.row || + x2 < x1 || x2 > screen.col || + x1 < 0 || x1 > x2-1) + return; - char* path = file_path_get_path(fb.file_path); - chdir(path); + Color drawcol = dc.col[default_attributes.fg]; + XftDrawRect(xw.draw, &drawcol, + border_px + x1 * win.cw, border_px + (y + 1) * win.ch - cursor_thickness, + win.cw * (x2-x1+1), 1); +} - int offset = -1; - while((offset = buffer_seek_char(&fb, offset+1, '\n')) >= 0) { - char* line = buffer_get_line_at_offset(&fb, offset); - if (strlen(line) && !is_file_type(line, ".seproj")) { - if (first < 0) - first = new_file_buffer_entry(line); - else - new_file_buffer_entry(line); - } - free(line); - } +void +set_clipboard_copy(char* buffer, int len) +{ + if (!buffer) + return; + if (copy_buffer) + free(copy_buffer); + copy_buffer = buffer; + copy_len = len; + + Atom clipboard; + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); +} - if (first < 0) - first = new_file_buffer_entry(NULL); - writef_to_status_bar("opened project %s", path); - free(path); +void +execute_clipbaord_event() +{ + Atom clipboard; - buffer_destroy(&fb); - return first; + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xtarget, clipboard, + xw.win, CurrentTime); } int -new_file_buffer_entry(const char* file_path) +screen_set_char(rune_t u, int x, int y) { - 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) { - writef_to_status_bar("buffer exits"); - return n; - } - } - } - } else { - strcpy(full_path, file_path); - } + struct glyph attr = global_attr; + if (y >= screen.row || x >= screen.col || + y < 0 || x < 0) + return 1; - for(int n = 0; n < available_buffer_slots; n++) { - if (!file_buffers[n].contents) { - if (is_file_type(full_path, ".seproj")) - return open_seproj(buffer_new(full_path)); - file_buffers[n] = buffer_new(full_path); - return n; - } + if (u == 0) + u = screen.lines[y][x].u; + int width = wcwidth(u); + if (width == -1) + width = 1; + else if (width > 1) + attr.mode |= ATTR_WIDE; + + if (screen.lines[y][x].mode & ATTR_WIDE || attr.mode & ATTR_WIDE) { + if (x+1 < screen.col) { + screen.lines[y][x+1].u = ' '; + screen.lines[y][x+1].mode |= ATTR_WDUMMY; } + } else if (screen.lines[y][x].mode & ATTR_WDUMMY && x-1 >= 0) { + screen.lines[y][x-1].u = ' '; + screen.lines[y][x-1].mode &= ~ATTR_WIDE; } - if (is_file_type(full_path, ".seproj")) - return open_seproj(buffer_new(full_path)); + screen.lines[y][x] = attr; + screen.lines[y][x].u = u; - 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 width; +} - return available_buffer_slots-1; +void* +xmalloc(size_t len) +{ + void *p; + if (!(p = malloc(len))) + die("malloc: error, reutrned NULL | errno: %s\n", strerror(errno)); + return p; } -int -destroy_file_buffer_entry(struct window_split_node* node, struct window_split_node* root) +void* +xrealloc(void *p, size_t len) { - // 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) { - writef_to_status_bar("can't delete last buffer"); - return 0; - } + if ((p = realloc(p, len)) == NULL) + die("realloc: error, returned NULL | errno: %s\n", strerror(errno)); + return p; +} - if (window_other_nodes_contain_file_buffer(node, root)) { - node->window.buffer_index++; - node->window = window_buffer_new(node->window.buffer_index); - writef_to_status_bar("swapped buffer"); - return 0; - } - buffer_destroy(get_file_buffer(&node->window)); +void +die(const char *errstr, ...) +{ + va_list ap; - 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); + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + assert(0); +} - return 1; +//////////////////////////////////////////////// +// X11 and drawing +// + +struct glyph* +screen_set_attr(int x, int y) +{ + static struct glyph dummy; + if (y >= screen.row || x >= screen.col || + y < 0 || x < 0) + return &dummy; + + return &screen.lines[y][x]; } -int -delete_selection(struct file_buffer* buf) +void +screen_set_region(int x1, int y1, int x2, int y2, rune_t u) { - 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; + for (int y = y1; y <= y2; y++) + for (int x = x1; x <= x2; x++) + screen_set_char(u, x, y); +} + +void +screen_resize(int col, int row) +{ + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; } - return 0; + + // resize to new height + for (int i = row; i < screen.row; i++) + free(screen.lines[i]); + + screen.lines = xrealloc(screen.lines, row * sizeof(*screen.lines)); + + for (int i = screen.row; i < row; i++) + screen.lines[i] = NULL; + + // resize each row to new width, zero-pad if needed + for (int i = 0; i < row; i++) { + screen.lines[i] = xrealloc(screen.lines[i], col * sizeof(struct glyph)); + memset(screen.lines[i], 0, col * sizeof(struct glyph)); + } + + // update terminal size + screen.col = col; + screen.row = row; } + + + void propnotify(XEvent *e) { @@ -331,9 +385,9 @@ propnotify(XEvent *e) void selnotify(XEvent *e) { - ulong nitems, ofs, rem; + unsigned long nitems, ofs, rem; int format; - uchar *data, *last, *repl; + uint8_t *data, *last, *repl; Atom type, incratom, property = None; incratom = XInternAtom(xw.dpy, "INCR", 0); @@ -390,18 +444,15 @@ selnotify(XEvent *e) *repl++ = '\n'; } - struct file_buffer* fb = get_file_buffer(focused_window); + struct file_buffer* fb = get_fb(focused_window); if (fb->contents) { - if (fb->mode & BUFFER_SELECTION_ON) { - buffer_remove_selection(fb); - buffer_move_cursor_to_selection_start(focused_window); - fb->mode &= ~(BUFFER_SELECTION_ON); + if (fb->mode & FB_SELECTION_ON) { + fb_remove_selection(fb); + wb_move_cursor_to_selection_start(focused_window); + fb->mode &= ~(FB_SELECTION_ON); } - int offset = focused_window->cursor_offset; - buffer_insert(fb, (char*)data, nitems * format / 8, offset, 1); - buffer_move_to_offset(focused_window, offset + (nitems * format / 8), 0); - if (buffer_contents_updated) - buffer_contents_updated(fb, offset, BUFFER_CONTENT_BIG_CHANGE); + call_extension(fb_paste, fb, (char*)data, nitems * format / 8); + call_extension(fb_contents_updated, fb, focused_window->cursor_offset, FB_CONTENT_BIG_CHANGE); } XFree(data); /* number of 32-bit chunks returned */ @@ -416,31 +467,6 @@ selnotify(XEvent *e) } void -set_clipboard_copy(char* buffer, int len) -{ - if (!buffer) - return; - if (copy_buffer) - free(copy_buffer); - copy_buffer = buffer; - copy_len = len; - - Atom clipboard; - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); -} - -void -insert_clipboard_at_cursor() -{ - Atom clipboard; - - clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); - XConvertSelection(xw.dpy, clipboard, xtarget, clipboard, - xw.win, CurrentTime); -} - -void selrequest(XEvent *e) { XSelectionRequestEvent *xsre = (XSelectionRequestEvent *) e; @@ -465,7 +491,7 @@ selrequest(XEvent *e) string = xtarget; XChangeProperty(xsre->display, xsre->requestor, xsre->property, XA_ATOM, 32, PropModeReplace, - (uchar *) &string, 1); + (uint8_t *) &string, 1); xev.property = xsre->property; } else if (xsre->target == xtarget || xsre->target == XA_STRING) { /* @@ -475,7 +501,7 @@ selrequest(XEvent *e) clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); int sel_len; if (xsre->selection == XA_PRIMARY) { - seltext = buffer_get_selection(get_file_buffer(focused_window), &sel_len); + seltext = fb_get_selection(get_fb(focused_window), &sel_len); } else if (xsre->selection == clipboard) { seltext = copy_buffer; sel_len = copy_len; @@ -489,7 +515,7 @@ selrequest(XEvent *e) XChangeProperty(xsre->display, xsre->requestor, xsre->property, xsre->target, 8, PropModeReplace, - (uchar *)seltext, sel_len); + (uint8_t *)seltext, sel_len); xev.property = xsre->property; if (seltext != copy_buffer) free(seltext); @@ -506,9 +532,9 @@ cresize(int width, int height) { int col, row; - if (width != 0) + if (width > 0) win.w = width; - if (height != 0) + if (height > 0) win.h = height; col = (win.w - 2 * border_px) / win.cw; @@ -516,7 +542,7 @@ cresize(int width, int height) col = MAX(1, col); row = MAX(1, row); - tresize(col, row); + screen_resize(col, row); xresize(col, row); } @@ -662,7 +688,7 @@ xloadfont(Font *f, FcPattern *pattern) /* * Manually configure instead of calling XftMatchFont * so that we can use the configured pattern for - * "missing glyph" lookups. + * "missing struct glyph" lookups. */ configured = FcPatternDuplicate(pattern); if (!configured) @@ -947,7 +973,7 @@ xinit(int cols, int rows) xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, - PropModeReplace, (uchar *)&thispid, 1); + PropModeReplace, (uint8_t *)&thispid, 1); win.mode = MODE_NUMLOCK; xsettitle(NULL); @@ -961,14 +987,14 @@ xinit(int cols, int rows) } int -xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const struct glyph *glyphs, int len, int x, int y) { float winx = border_px + x * win.cw, winy = border_px + y * win.ch, xp, yp; - ushort mode, prevmode = USHRT_MAX; + unsigned short mode, prevmode = USHRT_MAX; Font *font = &dc.font; int frcflags = FRC_NORMAL; float runewidth = win.cw; - Rune rune; + rune_t rune; FT_UInt glyphidx; FcResult fcres; FcPattern *fcpattern, *fontpattern; @@ -977,7 +1003,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x int i, f, numspecs = 0; for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { - /* Fetch rune and mode for current glyph. */ + /* Fetch rune and mode for current struct glyph. */ rune = glyphs[i].u; mode = glyphs[i].mode; @@ -985,7 +1011,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x if (mode & ATTR_WDUMMY) continue; - /* Determine font for glyph if different from previous glyph. */ + /* Determine font for struct glyph if different from previous struct glyph. */ if (prevmode != mode) { prevmode = mode; font = &dc.font; @@ -1022,7 +1048,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x /* Everything correct. */ if (glyphidx && frc[f].flags == frcflags) break; - /* We got a default font for a not found glyph. */ + /* We got a default font for a not found struct glyph. */ if (!glyphidx && frc[f].flags == frcflags && frc[f].unicodep == rune) { break; @@ -1093,7 +1119,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x } void -xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, struct glyph base, int len, int x, int y) { int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); int winx = border_px + x * win.cw, winy = border_px + y * win.ch, @@ -1199,7 +1225,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i } void -xdrawglyph(Glyph g, int x, int y) +xdrawglyph(struct glyph g, int x, int y) { int numspecs; XftGlyphFontSpec spec; @@ -1211,9 +1237,9 @@ xdrawglyph(Glyph g, int x, int y) void xdrawcursor(int cx, int cy, int focused) { - LIMIT(cx, 0, term.col-1); - LIMIT(cy, 0, term.row-1); - Glyph g = term.line[cy][cx]; + LIMIT(cx, 0, screen.col-1); + LIMIT(cy, 0, screen.row-1); + struct glyph g = screen.lines[cy][cx]; if (IS_SET(MODE_HIDE)) return; g.mode &= ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; @@ -1222,8 +1248,8 @@ xdrawcursor(int cx, int cy, int focused) Color drawcol = dc.col[g.bg]; /* draw the new one */ - if (IS_SET(MODE_FOCUSED) && !(get_file_buffer(focused_window)->mode & BUFFER_SELECTION_ON) && focused) { - switch (win.cursor) { + if (IS_SET(MODE_FOCUSED) && !(get_fb(focused_window)->mode & FB_SELECTION_ON) && focused) { + switch (cursor_shape) { case 0: // Blinking Block case 1: // Blinking Block (Default) case 2: // Steady Block @@ -1266,20 +1292,6 @@ xdrawcursor(int cx, int cy, int focused) } void -draw_horisontal_line(int y, int x1, int x2) -{ - if (y < 0 || y > term.row || - x2 < x1 || x2 > term.col || - x1 < 0 || x1 > x2-1) - return; - - Color drawcol = dc.col[default_attributes.fg]; - XftDrawRect(xw.draw, &drawcol, - border_px + x1 * win.cw, border_px + (y + 1) * win.ch - cursor_thickness, - win.cw * (x2-x1+1), 1); -} - -void xseticontitle(char *p) { XTextProperty prop; @@ -1310,12 +1322,12 @@ xsettitle(char *p) void xdrawline(int x1, int y1, int x2) { - LIMIT(y1, 0, term.row); - LIMIT(x2, 0, term.col); + LIMIT(y1, 0, screen.row); + LIMIT(x2, 0, screen.col); LIMIT(x1, 0, x2); - Line line = term.line[y1]; + struct glyph* line = screen.lines[y1]; int i, x, ox, numspecs; - Glyph base, new; + struct glyph base, new; XftGlyphFontSpec *specs = xw.specbuf; numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); @@ -1369,13 +1381,6 @@ xsetpointermotion(int set) XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); } -int xsetcursor(int cursor) { - if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ - return 1; - win.cursor = cursor; - return 0; -} - void xseturgency(int add) { XWMHints *h = XGetWMHints(xw.dpy, xw.win); MODBIT(h->flags, add, XUrgencyHint); @@ -1399,9 +1404,9 @@ xunloadfonts(void) void xunloadfont(Font *f) { - assert(f); - assert(f->match); - assert(f->pattern); + soft_assert(f, return;); + soft_assert(f->match, return;); + soft_assert(f->pattern, return;); XftFontClose(xw.dpy, f->match); FcPatternDestroy(f->pattern); if (f->set) @@ -1429,237 +1434,60 @@ focus(XEvent *ev) } int -file_browser_actions(KeySym keysym, int modkey) -{ - static char full_path[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, '/'); - 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_node->selected = 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--; - - file_browser_next_item(NULL, NULL, NULL, NULL, NULL); - const char* filename; - for (int y = 0; (filename = file_browser_next_item(path, fb->contents, NULL, NULL, NULL)); y++) { - strcpy(full_path, path); - strcat(full_path, filename); - if (y == focused_node->selected) { - if (path_is_folder(full_path)) { - strcpy(fb->file_path, full_path); - - fb->len = 0; - fb->contents[0] = '\0'; - focused_node->selected = 0; - - free(path); - file_browser_next_item(NULL, NULL, NULL, NULL, NULL); - *full_path = 0; - return 1; - } - goto open_file; - } - } - - if (fb->contents[fb->len-1] == '/') { - free(path); - *full_path = 0; - 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); - *full_path = 0; - return 1; - } - case XK_Down: - focused_node->selected++; - return 1; - case XK_Up: - focused_node->selected--; - if (focused_node->selected < 0) - focused_node->selected = 0; - return 1; - case XK_Escape: - if (destroy_file_buffer_entry(focused_node, &root_node)) - writef_to_status_bar("file browser clsoed"); - 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; +match(unsigned int mask, unsigned int state) { + const unsigned int ignoremod = Mod2Mask|XK_SWITCH_MOD; + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); } -void -file_browser_string_insert(const char* buf, int buflen) +static void +search_term_string_insert(const char* buf, int buflen) { - static char full_path[PATH_MAX]; - struct file_buffer* fb = get_file_buffer(focused_window); + static int first = 0; - if (fb->len + buflen + strlen(fb->file_path) > PATH_MAX) + struct file_buffer* fb = get_fb(focused_window); + if (!buf) { + first = 1; return; - - if (buf[0] >= 32 || buflen > 1) { - buffer_insert(fb, buf, buflen, fb->len, 0); - focused_node->selected = 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 { - writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); } -} - -int -buffer_search_actions(KeySym keysym, int modkey) -{ - switch (keysym) { - case XK_Return: - case XK_Tab: - if (focused_window->mode == WINDOW_BUFFER_KEYWORD_ALL_BUFFERS) { - struct keyword_pos kw; - int n = 0; - buffers_search_keyword_next_item(NULL, NULL, NULL, NULL, NULL); - while (buffers_search_keyword_next_item("", focused_node->search, NULL, NULL, &kw)) { - if (n == focused_node->selected) { - *focused_window = window_buffer_new(kw.buffer_index); - focused_window->cursor_offset = kw.offset; - return 1; - } - n++; - } - buffers_search_keyword_next_item(NULL, NULL, NULL, NULL, NULL); - } else if (focused_window->mode == WINDOW_BUFFER_SEARCH_BUFFERS) { - int buffer_index; - int n = 0; - buffer_search_next_item(NULL, NULL, NULL, NULL, NULL); - while (buffer_search_next_item("", focused_node->search, NULL, NULL, &buffer_index)) { - if (n == focused_node->selected) { - *focused_window = window_buffer_new(buffer_index); - return 1; - } - n++; - } - buffer_search_next_item(NULL, NULL, NULL, NULL, NULL); - } - puts("n"); - return 1; - case XK_BackSpace: - remove_utf8_string_end(focused_node->search); - focused_node->selected = 0; - return 1; - case XK_Down: - focused_node->selected++; - return 1; - case XK_Up: - focused_node->selected--; - if (focused_node->selected < 0) - focused_node->selected = 0; - return 1; - case XK_Page_Down: - focused_node->selected += 10; - return 1; - case XK_Page_Up: - focused_node->selected -= 10; - if (focused_node->selected < 0) - focused_node->selected = 0; - return 1; - case XK_Escape: - if (path_is_folder(get_file_buffer(focused_window)->file_path)) - focused_window->mode = WINDOW_BUFFER_FILE_BROWSER; - else - focused_window->mode = WINDOW_BUFFER_NORMAL; - return 1; + if (first) { + *fb->search_term = 0; + first = 0; } - return 0; -} - -void -buffer_search_string_insert(const char* buf, int buflen) -{ - int len = strlen(focused_node->search); - if (buflen + len + 1 > SEARCH_TERM_MAX_LEN) - return; - if (buf[0] >= 32 || buflen > 1) { - memcpy(focused_node->search + len, buf, buflen); - focused_node->search[len + buflen] = 0; - focused_node->selected = 0; - } else { - writef_to_status_bar("unhandled control character 0x%x\n", buf[0]); + int len = strlen(fb->search_term); + if (len + buflen + 1 > SEARCH_TERM_MAX_LEN) + return; + memcpy(fb->search_term + len, buf, buflen); + fb->search_term[len + buflen] = 0; + if (fb->mode & FB_SEARCH_BLOCKING_BACKWARDS) + focused_window->cursor_offset = fb_seek_string_backwards(fb, focused_window->cursor_offset, fb->search_term); + else + focused_window->cursor_offset = fb_seek_string(fb, focused_window->cursor_offset, fb->search_term); + writef_to_status_bar("search: %s", fb->search_term); } } -int + +static int search_term_actions(KeySym keysym, int modkey) { static int first = 0; - struct file_buffer* fb = get_file_buffer(focused_window); + struct file_buffer* fb = get_fb(focused_window); if (keysym == XK_Return || keysym == XK_Escape) { - int count = buffer_count_string_instances(fb, fb->search_term, 0, NULL); + int count = fb_count_string_instances(fb, fb->search_term, 0, NULL); if (!count) { - fb->mode &= ~BUFFER_SEARCH_BLOCKING_MASK; + fb->mode &= ~FB_SEARCH_BLOCKING_MASK; writef_to_status_bar("no resulrs for \"%s\"", fb->search_term); } else { - fb->mode &= ~BUFFER_SEARCH_BLOCKING; - fb->mode &= ~BUFFER_SEARCH_BLOCKING_BACKWARDS; - fb->mode |= BUFFER_SEARCH_BLOCKING_IDLE; + fb->mode &= ~FB_SEARCH_BLOCKING; + fb->mode &= ~FB_SEARCH_BLOCKING_BACKWARDS; + fb->mode |= FB_SEARCH_BLOCKING_IDLE; writef_to_status_bar("%d results for \"%s\"", count, fb->search_term); - if (fb->mode & BUFFER_SEARCH_BLOCKING_BACKWARDS) - focused_window->cursor_offset = buffer_seek_string_backwards(fb, focused_window->cursor_offset, fb->search_term); + if (fb->mode & FB_SEARCH_BLOCKING_BACKWARDS) + focused_window->cursor_offset = fb_seek_string_backwards(fb, focused_window->cursor_offset, fb->search_term); else - focused_window->cursor_offset = buffer_seek_string(fb, focused_window->cursor_offset, fb->search_term); + focused_window->cursor_offset = fb_seek_string(fb, focused_window->cursor_offset, fb->search_term); } first = 1; search_term_string_insert(NULL, 0); @@ -1670,8 +1498,8 @@ search_term_actions(KeySym keysym, int modkey) first = 0; *fb->search_term = 0; } else { - remove_utf8_string_end(fb->search_term); - focused_window->cursor_offset = buffer_seek_string_wrap(focused_window, focused_window->cursor_offset, fb->search_term); + utf8_remove_string_end(fb->search_term); + focused_window->cursor_offset = wb_seek_string_wrap(focused_window, focused_window->cursor_offset, fb->search_term); } writef_to_status_bar("search: %s", fb->search_term); return 1; @@ -1680,38 +1508,6 @@ search_term_actions(KeySym keysym, int modkey) return 0; } -void -search_term_string_insert(const char* buf, int buflen) -{ - static int first = 0; - - struct file_buffer* fb = get_file_buffer(focused_window); - if (!buf) { - first = 1; - return; - } - if (first) { - *fb->search_term = 0; - first = 0; - } - if (buf[0] >= 32 || buflen > 1) { - int len = strlen(fb->search_term); - if (len + buflen + 1 > SEARCH_TERM_MAX_LEN) - return; - memcpy(fb->search_term + len, buf, buflen); - fb->search_term[len + buflen] = 0; - if (fb->mode & BUFFER_SEARCH_BLOCKING_BACKWARDS) - focused_window->cursor_offset = buffer_seek_string_backwards(fb, focused_window->cursor_offset, fb->search_term); - else - focused_window->cursor_offset = buffer_seek_string(fb, focused_window->cursor_offset, fb->search_term); - writef_to_status_bar("search: %s", fb->search_term); - } -} - -int match(uint mask, uint state) { - const uint ignoremod = Mod2Mask|XK_SWITCH_MOD; - return mask == XK_ANY_MOD || mask == (state & ~ignoremod); -} void kpress(XEvent *ev) @@ -1720,54 +1516,46 @@ kpress(XEvent *ev) KeySym ksym; char buf[64]; int len; - Rune c; + rune_t c; Status status; if (IS_SET(MODE_KBDLOCK)) return; if (xw.ime.xic) - len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + len = XmbLookupString(xw.ime.xic, e, buf, sizeof(buf), &ksym, &status); else - len = XLookupString(e, buf, sizeof buf, &ksym, NULL); - const struct file_buffer* fb = get_file_buffer(focused_window); + len = XLookupString(e, buf, sizeof(buf), &ksym, NULL); + if (len == 1 && e->state & Mod1Mask) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8_encode(c, buf); + } + } + const struct file_buffer* fb = get_fb(focused_window); // keysym callback - if (focused_window->mode == WINDOW_BUFFER_FILE_BROWSER) { - if (file_browser_actions(ksym, e->state)) - return; - } else if (focused_window->mode & WINDOW_BUFFER_SEARCH_BUFFERS || - focused_window->mode & WINDOW_BUFFER_KEYWORD_ALL_BUFFERS) { - if (buffer_search_actions(ksym, e->state)) - return; - } else if (fb->mode & BUFFER_SEARCH_BLOCKING) { + if (fb->mode & FB_SEARCH_BLOCKING) { if (search_term_actions(ksym, e->state)) return; - } else if (keypress_callback) { - if (keypress_callback(ksym, e->state)) - return; + } + if (fb->mode & FB_SEARCH_BLOCKING) { + search_term_string_insert(buf, len); + return; } - // composed string from input method and send to callback - if (string_input_callback) { - if (len == 0) + if (focused_window->mode != WB_NORMAL) { + int override = 0; + call_extension(wn_custom_window_keypress_override, &override, focused_node, ksym, e->state, buf, len); + if (override) return; - if (len == 1 && e->state & Mod1Mask) { - if (*buf < 0177) { - c = *buf | 0x80; - len = utf8encode(c, buf); - } - } - if (focused_window->mode == WINDOW_BUFFER_FILE_BROWSER) - file_browser_string_insert(buf, len); - else if (focused_window->mode & WINDOW_BUFFER_SEARCH_BUFFERS || - focused_window->mode & WINDOW_BUFFER_KEYWORD_ALL_BUFFERS) - buffer_search_string_insert(buf, len); - else if (fb->mode & BUFFER_SEARCH_BLOCKING) - search_term_string_insert(buf, len); - else - string_input_callback(buf, len); + + int wn_custom_window_keypress_override_callback_exists = 0; + extension_callback_exists(wn_custom_window_keypress_override, wn_custom_window_keypress_override_callback_exists = 1;); + soft_assert(wn_custom_window_keypress_override_callback_exists, ); } + + call_extension(keypress, ksym, e->state, buf, len); } void @@ -1794,7 +1582,7 @@ resize(XEvent *e) return; cresize(e->xconfigure.width, e->xconfigure.height); - writef_to_status_bar("window resize: %d:%d", term.col, term.row); + writef_to_status_bar("window resize: %d:%d", screen.col, screen.row); } void @@ -1822,6 +1610,7 @@ run(void) cresize(w, h); for (;;) { + struct timespec ts_start; int xev = 0; while (XPending(xw.dpy)) { @@ -1829,6 +1618,7 @@ run(void) if (XFilterEvent(&ev, None)) continue; if (handler[ev.type]) { + clock_gettime(CLOCK_MONOTONIC, &ts_start); (handler[ev.type])(&ev); xev = 1; } @@ -1839,42 +1629,39 @@ run(void) continue; } - tsetregion(0, 0, term.col-1, term.row-1, ' '); - if (term.row-2 >= 0) - window_draw_tree_to_screen(&root_node, 0, 0, term.col-1, term.row-1); + screen_set_region(0, 0, screen.col-1, screen.row-1, ' '); + if (screen.row-2 >= 0) + window_node_draw_tree_to_screen(&root_node, 0, 0, screen.col-1, screen.row-1); draw_status_bar(); - if (draw_callback) - draw_callback(); - xfinishdraw(); XFlush(xw.dpy); + + struct timespec ts_end; + clock_gettime(CLOCK_MONOTONIC, &ts_end); + double dif = 0; + dif = (ts_end.tv_sec - ts_start.tv_sec) * 1e6; + dif += (ts_end.tv_nsec - ts_start.tv_nsec) * 1e-3; + printf("%lf\n", dif); } } int main(int argc, char *argv[]) { - if (startup_callback) { - startup_callback(); - } xw.l = xw.t = 0; xw.isfixed = False; - xsetcursor(cursor_shape); setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); int cols = MAX(default_cols, 1); int rows = MAX(default_rows, 1); - tnew(cols, rows); + screen_init(cols, rows); xinit(cols, rows); xsetenv(); - // TODO: fix things when the file buffer is empty - // for example on a new file - if (argc <= 1) { - *focused_window = window_buffer_new(new_file_buffer_entry(NULL)); + *focused_window = wb_new(fb_new_entry(NULL)); } else { int master_stack = 1; for (int i = 1; i < argc; i++) { @@ -1885,7 +1672,7 @@ main(int argc, char *argv[]) window_node_split(focused_node, 0.5, WINDOW_HORISONTAL); master_stack = 0; } else if (master_stack > 0) { - *focused_window = window_buffer_new(new_file_buffer_entry(argv[i])); + *focused_window = wb_new(fb_new_entry(argv[i])); master_stack = -1; continue; } else { @@ -1894,23 +1681,27 @@ main(int argc, char *argv[]) if (focused_node->node2) { focused_node = focused_node->node2; - focused_window = &focused_node->window; + focused_window = &focused_node->wb; if (!master_stack) - *focused_window = window_buffer_new(new_file_buffer_entry(argv[i])); + *focused_window = wb_new(fb_new_entry(argv[i])); } master_stack = 0; } } } - static const char* const welcome[] = {"Welcome to se!", "Good day, Human", "Happy Coding", "se: the Simple Editor", - "Time to get some progress done!", "Ready for combat", "Initialising...Done", "loaded in %%d seconds", - "Fun fact: vscode has over two times as many lines describing dependencies than se has in total", - "You look based", "Another day, another bug to fix", "Who needs a mouse ¯\\_(ツ)_/¯", "grrgrrggghhaaaaaa (╯°□ °)╯︵ ┻━┻", - "┬┴┬┤(・_├┬┴┬┤├┬┴┬┴┬┤ʖ ͡°) ├┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴┬┴", "ʰᵉˡˡᵒ", "(=^ ◡ ^=)"}; - srand(time(NULL)); - writef_to_status_bar(welcome[rand() % LEN(welcome)]); + + // TODO: start screen extension + + if (extensions) { + for (int i = 0; !extensions[i].end; i++) { + if (extensions[i].e.init) + extensions[i].e.init(&extensions[i].e); + if (extensions[i].e.enable) + extensions[i].e.enable(); + } + } run(); diff --git a/x.h b/x.h @@ -1,18 +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. +*/ + #ifndef _X_H #define _X_H -#include "se.h" -#undef Glyph +#include "utf8.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 <stdio.h> +#include <limits.h> + +void draw_horisontal_line(int y, int x1, int x2); +// TODO: vertical line +// !!! NOTE: +// !!! buffer MUST be malloced and NOT be freed after it is passed +void set_clipboard_copy(char* buffer, int len); +void execute_clipbaord_event(); + +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, +}; + +struct glyph { + rune_t u; // character code + uint16_t mode; // attribute flags + uint32_t fg; // foreground + uint32_t bg; // background +}; + +// Internal representation of the screen +struct screen { + int row; // row count + int col; // column count + struct glyph** lines; // screen letters 2d array +}; -#define Glyph Glyph_ +extern struct screen screen; +extern struct glyph global_attr; + +void screen_init(int col, int row); +void screen_resize(int col, int row); +void screen_set_region(int x1, int y1, int x2, int y2, rune_t u); +int screen_set_char(rune_t u, int x, int y); +struct glyph* screen_set_attr(int x, int y); + +void* xmalloc(size_t len); +void* xrealloc(void *p, size_t len); +void die(const char *, ...); + +// the va_args can be used to return; or any other stuff like that +// TODO: optionally crash the program for debugging +#define soft_assert(condition, ...) \ + do { \ + if(!(condition)) { \ + fprintf(stderr, "SOFT ASSERT ERROR: (%s) failed at %s %s():%d\n", #condition, __FILE__, __func__, __LINE__); \ + writef_to_status_bar("SOFT ASSERT ERROR: (%s) failed at %s %s():%d", #condition, __FILE__, __func__, __LINE__); \ + status_bar_bg = error_color; \ + __VA_ARGS__ \ + } \ + } while(0) \ enum win_mode { MODE_VISIBLE = 1 << 0, @@ -28,81 +91,24 @@ enum win_mode { 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 -}; +#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)) + +//////////////////////////////////////////////// +// X11 and drawing +// + +// X modifiers +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +extern double defaultfontsize; +extern double usedfontsize; -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(int, int, int); void xfinishdraw(void); @@ -110,22 +116,11 @@ 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); -int destroy_file_buffer_entry(struct window_split_node* node, struct window_split_node* root); -int delete_selection(struct file_buffer* buf); -void draw_horisontal_line(int y, int x1, int x2); -// buffer MUST be malloced and NOT be freed after it is passed -void set_clipboard_copy(char* buffer, int len); -void insert_clipboard_at_cursor(); +int match(unsigned int, unsigned int); #endif // _X_H