diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2023-11-09 23:19:53 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2023-11-09 23:19:53 +0100 |
| commit | 1566b6faa8534118c3566188181367cd0868468f (patch) | |
| tree | 1de8d4b369efb5e592685a31088f798a6b63ffa1 /examples/dte/window.c | |
| parent | 349991bf6efe473ab9a5cbdae0a8114d72b997e3 (diff) | |
| download | crep-1566b6faa8534118c3566188181367cd0868468f.tar.gz | |
Added partial matching and introduced threads
Diffstat (limited to 'examples/dte/window.c')
| -rw-r--r-- | examples/dte/window.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/examples/dte/window.c b/examples/dte/window.c new file mode 100644 index 0000000..6b0c5cb --- /dev/null +++ b/examples/dte/window.c @@ -0,0 +1,507 @@ +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include "window.h" +#include "editor.h" +#include "error.h" +#include "file-history.h" +#include "load-save.h" +#include "lock.h" +#include "move.h" +#include "util/path.h" +#include "util/str-util.h" +#include "util/strtonum.h" +#include "util/xmalloc.h" + +Window *new_window(EditorState *e) +{ + Window *window = xnew0(Window, 1); + window->editor = e; + return window; +} + +View *window_add_buffer(Window *window, Buffer *buffer) +{ + // We rely on this being 0, for implicit initialization of + // View::selection and View::select_mode + static_assert(SELECT_NONE == 0); + + View *view = xnew(View, 1); + *view = (View) { + .buffer = buffer, + .window = window, + .cursor = { + .blk = BLOCK(buffer->blocks.next), + .head = &buffer->blocks, + } + }; + + ptr_array_append(&buffer->views, view); + ptr_array_append(&window->views, view); + window->update_tabbar = true; + return view; +} + +View *window_open_empty_buffer(Window *window) +{ + EditorState *e = window->editor; + return window_add_buffer(window, open_empty_buffer(&e->buffers, &e->options)); +} + +View *window_open_buffer ( + Window *window, + const char *filename, + bool must_exist, + const Encoding *encoding +) { + if (unlikely(filename[0] == '\0')) { + error_msg("Empty filename not allowed"); + return NULL; + } + + EditorState *e = window->editor; + bool dir_missing = false; + char *absolute = path_absolute(filename); + if (absolute) { + // Already open? + Buffer *buffer = find_buffer(&e->buffers, absolute); + if (buffer) { + if (!streq(absolute, buffer->abs_filename)) { + const char *bufname = buffer_filename(buffer); + char *s = short_filename(absolute, &e->home_dir); + info_msg("%s and %s are the same file", s, bufname); + free(s); + } + free(absolute); + return window_get_view(window, buffer); + } + } else { + // Let load_buffer() create error message + dir_missing = (errno == ENOENT); + } + + /* + /proc/$PID/fd/ contains symbolic links to files that have been opened + by process $PID. Some of the files may have been deleted but can still + be opened using the symbolic link but not by using the absolute path. + + # create file + mkdir /tmp/x + echo foo > /tmp/x/file + + # in another shell: keep the file open + tail -f /tmp/x/file + + # make the absolute path unavailable + rm /tmp/x/file + rmdir /tmp/x + + # this should still succeed + dte /proc/$(pidof tail)/fd/3 + */ + + Buffer *buffer = buffer_new(&e->buffers, &e->options, encoding); + if (!load_buffer(buffer, filename, &e->options, must_exist)) { + remove_and_free_buffer(&e->buffers, buffer); + free(absolute); + return NULL; + } + if (unlikely(buffer->file.mode == 0 && dir_missing)) { + // New file in non-existing directory; this is usually a mistake + error_msg("Error opening %s: Directory does not exist", filename); + remove_and_free_buffer(&e->buffers, buffer); + free(absolute); + return NULL; + } + + if (absolute) { + buffer->abs_filename = absolute; + } else { + // FIXME: obviously wrong + buffer->abs_filename = xstrdup(filename); + } + update_short_filename(buffer, &e->home_dir); + + if (e->options.lock_files) { + if (!lock_file(buffer->abs_filename)) { + buffer->readonly = true; + } else { + buffer->locked = true; + } + } + + if (buffer->file.mode != 0 && !buffer->readonly && access(filename, W_OK)) { + error_msg("No write permission to %s, marking read-only", filename); + buffer->readonly = true; + } + + return window_add_buffer(window, buffer); +} + +View *window_get_view(Window *window, Buffer *buffer) +{ + View *view = window_find_view(window, buffer); + if (!view) { + // Open the buffer in other window to this window + view = window_add_buffer(window, buffer); + view->cursor = ((View*)buffer->views.ptrs[0])->cursor; + } + return view; +} + +View *window_find_view(Window *window, Buffer *buffer) +{ + for (size_t i = 0, n = buffer->views.count; i < n; i++) { + View *view = buffer->views.ptrs[i]; + if (view->window == window) { + return view; + } + } + // Buffer isn't open in this window + return NULL; +} + +View *window_find_unclosable_view(Window *window) +{ + // Check active view first + if (window->view && !view_can_close(window->view)) { + return window->view; + } + for (size_t i = 0, n = window->views.count; i < n; i++) { + View *view = window->views.ptrs[i]; + if (!view_can_close(view)) { + return view; + } + } + return NULL; +} + +static void window_remove_views(Window *window) +{ + while (window->views.count > 0) { + View *view = window->views.ptrs[window->views.count - 1]; + remove_view(view); + } +} + +// NOTE: window->frame isn't removed +void window_free(Window *window) +{ + window_remove_views(window); + free(window->views.ptrs); + window->frame = NULL; + free(window); +} + +// Remove view from view->window and view->buffer->views and free it +size_t remove_view(View *view) +{ + Window *window = view->window; + EditorState *e = window->editor; + if (view == window->prev_view) { + window->prev_view = NULL; + } + if (view == e->view) { + e->view = NULL; + e->buffer = NULL; + } + + size_t idx = ptr_array_idx(&window->views, view); + BUG_ON(idx >= window->views.count); + ptr_array_remove_idx(&window->views, idx); + window->update_tabbar = true; + + Buffer *buffer = view->buffer; + ptr_array_remove(&buffer->views, view); + if (buffer->views.count == 0) { + if (buffer->options.file_history && buffer->abs_filename) { + FileHistory *hist = &e->file_history; + file_history_add(hist, view->cy + 1, view->cx_char + 1, buffer->abs_filename); + } + remove_and_free_buffer(&e->buffers, buffer); + } + + free(view); + return idx; +} + +void window_close_current_view(Window *window) +{ + size_t idx = remove_view(window->view); + if (window->prev_view) { + window->view = window->prev_view; + window->prev_view = NULL; + return; + } + if (window->views.count == 0) { + window_open_empty_buffer(window); + } + if (window->views.count == idx) { + idx--; + } + window->view = window->views.ptrs[idx]; +} + +static void restore_cursor_from_history(const FileHistory *hist, View *view) +{ + unsigned long row, col; + if (file_history_find(hist, view->buffer->abs_filename, &row, &col)) { + move_to_filepos(view, row, col); + } +} + +void set_view(View *view) +{ + EditorState *e = view->window->editor; + if (e->view == view) { + return; + } + + // Forget previous view when changing view using any other command but open + if (e->window) { + e->window->prev_view = NULL; + } + + e->view = view; + e->buffer = view->buffer; + e->window = view->window; + e->window->view = view; + + if (!view->buffer->setup) { + buffer_setup(e, view->buffer); + if (view->buffer->options.file_history && view->buffer->abs_filename) { + restore_cursor_from_history(&e->file_history, view); + } + } + + // view.cursor can be invalid if same buffer was modified from another view + if (view->restore_cursor) { + view->cursor.blk = BLOCK(view->buffer->blocks.next); + block_iter_goto_offset(&view->cursor, view->saved_cursor_offset); + view->restore_cursor = false; + view->saved_cursor_offset = 0; + } + + // Save cursor states of views sharing same buffer + for (size_t i = 0, n = view->buffer->views.count; i < n; i++) { + View *other = view->buffer->views.ptrs[i]; + if (other != view) { + other->saved_cursor_offset = block_iter_get_offset(&other->cursor); + other->restore_cursor = true; + } + } +} + +View *window_open_new_file(Window *window) +{ + View *prev = window->view; + View *view = window_open_empty_buffer(window); + set_view(view); + window->prev_view = prev; + return view; +} + +static bool buffer_is_empty_and_untouched(const Buffer *b) +{ + return !b->abs_filename && b->change_head.nr_prev == 0 && !b->display_filename; +} + +// If window contains only one untouched buffer it'll be closed after +// opening another file. This is done because closing the last buffer +// causes an empty buffer to be opened (windows must contain at least +// one buffer). +static bool is_useless_empty_view(const View *v) +{ + return v && v->window->views.count == 1 && buffer_is_empty_and_untouched(v->buffer); +} + +View *window_open_file(Window *window, const char *filename, const Encoding *encoding) +{ + View *prev = window->view; + bool useless = is_useless_empty_view(prev); + View *view = window_open_buffer(window, filename, false, encoding); + if (view) { + set_view(view); + if (useless) { + remove_view(prev); + } else { + window->prev_view = prev; + } + } + return view; +} + +// Open multiple files in window and return the first opened View +View *window_open_files(Window *window, char **filenames, const Encoding *encoding) +{ + View *empty = window->view; + bool useless = is_useless_empty_view(empty); + View *first = NULL; + + for (size_t i = 0; filenames[i]; i++) { + View *view = window_open_buffer(window, filenames[i], false, encoding); + if (view && !first) { + set_view(view); + first = view; + } + } + + if (useless && window->view != empty) { + remove_view(empty); + } + + return first; +} + +void mark_buffer_tabbars_changed(Buffer *buffer) +{ + for (size_t i = 0, n = buffer->views.count; i < n; i++) { + View *view = buffer->views.ptrs[i]; + view->window->update_tabbar = true; + } +} + +static int line_numbers_width(const Window *window, const GlobalOptions *options) +{ + if (!options->show_line_numbers || !window->view) { + return 0; + } + size_t width = size_str_width(window->view->buffer->nl) + 1; + return MAX(width, LINE_NUMBERS_MIN_WIDTH); +} + +static int edit_x_offset(const Window *window, const GlobalOptions *options) +{ + return line_numbers_width(window, options); +} + +static int edit_y_offset(const GlobalOptions *options) +{ + return options->tab_bar ? 1 : 0; +} + +static void set_edit_size(Window *window, const GlobalOptions *options) +{ + int xo = edit_x_offset(window, options); + int yo = edit_y_offset(options); + + window->edit_w = window->w - xo; + window->edit_h = window->h - yo - 1; // statusline + window->edit_x = window->x + xo; +} + +void calculate_line_numbers(Window *window) +{ + const GlobalOptions *options = &window->editor->options; + int w = line_numbers_width(window, options); + if (w != window->line_numbers.width) { + window->line_numbers.width = w; + window->line_numbers.first = 0; + window->line_numbers.last = 0; + mark_all_lines_changed(window->view->buffer); + } + set_edit_size(window, options); +} + +void set_window_coordinates(Window *window, int x, int y) +{ + const GlobalOptions *options = &window->editor->options; + window->x = x; + window->y = y; + window->edit_x = x + edit_x_offset(window, options); + window->edit_y = y + edit_y_offset(options); +} + +void set_window_size(Window *window, int w, int h) +{ + window->w = w; + window->h = h; + calculate_line_numbers(window); +} + +int window_get_scroll_margin(const Window *window, unsigned int scroll_margin) +{ + int max = (window->edit_h - 1) / 2; + BUG_ON(max < 0); + return MIN(max, scroll_margin); +} + +void frame_for_each_window(const Frame *frame, void (*func)(Window*, void*), void *data) +{ + if (frame->window) { + func(frame->window, data); + return; + } + for (size_t i = 0, n = frame->frames.count; i < n; i++) { + frame_for_each_window(frame->frames.ptrs[i], func, data); + } +} + +typedef struct { + const Window *const target; // Window to search for (set at init.) + Window *first; // Window passed in first callback invocation + Window *last; // Window passed in last callback invocation + Window *prev; // Window immediately before target (if any) + Window *next; // Window immediately after target (if any) + bool found; // Set to true when target is found +} WindowCallbackData; + +static void find_prev_and_next(Window *window, void *ud) +{ + WindowCallbackData *data = ud; + data->last = window; + if (data->found) { + if (!data->next) { + data->next = window; + } + return; + } + if (!data->first) { + data->first = window; + } + if (window == data->target) { + data->found = true; + return; + } + data->prev = window; +} + +Window *prev_window(Window *window) +{ + WindowCallbackData data = {.target = window}; + frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data); + BUG_ON(!data.found); + return data.prev ? data.prev : data.last; +} + +Window *next_window(Window *window) +{ + WindowCallbackData data = {.target = window}; + frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data); + BUG_ON(!data.found); + return data.next ? data.next : data.first; +} + +void window_close(Window *window) +{ + EditorState *e = window->editor; + if (!window->frame->parent) { + // Don't close last window + window_remove_views(window); + set_view(window_open_empty_buffer(window)); + return; + } + + WindowCallbackData data = {.target = window}; + frame_for_each_window(e->root_frame, find_prev_and_next, &data); + BUG_ON(!data.found); + Window *next_or_prev = data.next ? data.next : data.prev; + BUG_ON(!next_or_prev); + + remove_frame(e, window->frame); + e->window = NULL; + set_view(next_or_prev->view); + + mark_everything_changed(e); + debug_frame(e->root_frame); +} |
