ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit b3a32dd52672a8a385308afb73198d129294c7c3
parent 91a730677ac95f922d6e5bca531f9cf0811e0fd6
Author: lumidify <nobody@lumidify.org>
Date:   Thu, 11 Nov 2021 13:41:07 +0100

Make selecting and scrolling work somewhat better

Diffstat:
MMakefile | 1+
Mbuffer.c | 41+++++++++++++++++++++++++++++++----------
Aconfig.h | 5+++++
Mledit.c | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtheme_config.h | 2+-
Mwindow.c | 16+++++-----------
Mwindow.h | 2+-
7 files changed, 106 insertions(+), 31 deletions(-)

diff --git a/Makefile b/Makefile @@ -47,6 +47,7 @@ LDFLAGS_LEDIT = ${LDFLAGS} `pkg-config --libs x11 xkbfile pangoxft xext` -lm all: ${BIN} +ledit.o : config.h theme.o : theme_config.h keys_basic.o : keys_basic_config.h keys_command.o : keys_command_config.h diff --git a/buffer.c b/buffer.c @@ -1976,18 +1976,22 @@ ledit_buffer_set_selection(ledit_buffer *buffer, int line1, int byte1, int line2 byte1 == buffer->sel.byte1 && byte2 == buffer->sel.byte2) { return; } - if (buffer->sel.line1 >= 0) { + /* FIXME: maybe check both lines and bytes? */ + if (buffer->sel.line1 >= 0 || line1 >= 0) { int l1_new = line1, l2_new = line2; int b1_new = byte1, b2_new = byte2; - ledit_buffer_sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2); ledit_buffer_sort_selection(&l1_new, &b1_new, &l2_new, &b2_new); + ledit_buffer_sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2); + /* FIXME: make this a bit nicer and optimize it */ if (buffer->sel.line1 > l2_new || buffer->sel.line2 < l1_new) { for (int i = buffer->sel.line1; i <= buffer->sel.line2; i++) { - ledit_buffer_wipe_line_cursor_attrs(buffer, i); + if (i >= 0) + ledit_buffer_wipe_line_cursor_attrs(buffer, i); } } else { for (int i = buffer->sel.line1; i < l1_new; i++) { - ledit_buffer_wipe_line_cursor_attrs(buffer, i); + if (i >= 0) + ledit_buffer_wipe_line_cursor_attrs(buffer, i); } for (int i = buffer->sel.line2; i > l2_new; i--) { ledit_buffer_wipe_line_cursor_attrs(buffer, i); @@ -2029,12 +2033,20 @@ ledit_buffer_button_handler(void *data, XEvent *event) { ledit_buffer *buffer = (ledit_buffer *)data; int x = event->xbutton.x; int y = event->xbutton.y; + int snap; switch (event->type) { case ButtonPress: - ledit_xy_to_line_byte(buffer, x, y, 0, &l, &b); + snap = buffer->common->mode == NORMAL ? 0 : 1; + ledit_xy_to_line_byte(buffer, x, y, snap, &l, &b); buffer->selecting = 1; + if (buffer->common->mode == NORMAL) + ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); buffer->cur_line = l; buffer->cur_index = b; + /* don't set selection yet because the mouse may not be + dragged, so we don't want to switch to visual (this + allows setting just the cursor position in normal mode + without always switching to visual) */ ledit_buffer_set_selection(buffer, -1, -1, -1, -1); if (buffer->common->mode == NORMAL) ledit_buffer_set_line_cursor_attrs(buffer, l, b); @@ -2046,17 +2058,26 @@ ledit_buffer_button_handler(void *data, XEvent *event) { if (buffer->selecting) { y = y >= 0 ? y : 0; ledit_xy_to_line_byte(buffer, x, y, 1, &l, &b); - if (buffer->sel.line1 < 0 || buffer->sel.byte1 < 0) { - ledit_buffer_set_selection(buffer, l, b, l, b); - } else { - ledit_buffer_set_selection(buffer, buffer->sel.line1, buffer->sel.byte1, l, b); - } if (buffer->common->mode == NORMAL) { ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); /* FIXME: return to old mode afterwards? */ /* should change_mode_group even be called here? */ ledit_buffer_set_mode(buffer, VISUAL); } + if (buffer->sel.line1 < 0 || buffer->sel.byte1 < 0) { + /* the selection has just started, so the current + position is already set to the beginning of the + selection (see case ButtonPress above) */ + ledit_buffer_set_selection( + buffer, + buffer->cur_line, buffer->cur_index, l, b + ); + } else { + ledit_buffer_set_selection( + buffer, + buffer->sel.line1, buffer->sel.byte1, l, b + ); + } buffer->cur_line = l; buffer->cur_index = b; } diff --git a/config.h b/config.h @@ -0,0 +1,5 @@ +/* minimum time between redraws (nanoseconds) */ +#define TICK (long long)20000000 +/* minimum time between processing of mouse events - + events inbetween are discarded (nanoseconds) */ +#define MOUSE_TICK (long long)100000000 diff --git a/ledit.c b/ledit.c @@ -1,3 +1,4 @@ +/* FIXME: Make scrolling more smooth */ /* FIXME: Document that everything is assumed to be utf8 */ /* FIXME: Only redraw part of screen if needed */ /* FIXME: overflow in repeated commands */ @@ -9,6 +10,7 @@ /* FIXME: sort out types for indices (currently just int, but that might overflow) */ /* TODO: allow extending selection with shift+mouse like in e.g. gtk */ #include <math.h> +#include <time.h> #include <stdio.h> #include <errno.h> #include <assert.h> @@ -29,6 +31,7 @@ #include <X11/extensions/XKBrules.h> #include <X11/extensions/Xdbe.h> +#include "config.h" #include "memory.h" #include "common.h" #include "txtbuf.h" @@ -48,7 +51,7 @@ static void mainloop(void); static void setup(int argc, char *argv[]); static void cleanup(void); static void redraw(void); -static int button_press(XEvent *event); +static int button_press(XEvent *event, int scroll_num); static int button_release(XEvent *event); static int drag_motion(XEvent *event); @@ -91,9 +94,19 @@ mainloop(void) { int need_redraw = 0; redraw(); - + /* store last time that a mouse event was processed in order to + avoid sending too many mouse events to be processed */ + /* also store last draw time so framerate can be limited */ + struct timespec now, elapsed, last, last_scroll, last_motion, sleep_time; + clock_gettime(CLOCK_MONOTONIC, &last); + last_scroll = last_motion = last; + sleep_time.tv_sec = 0; + XEvent last_scroll_event, last_motion_event; + int last_scroll_valid = 0, last_motion_valid = 0; + int scroll_num = 0; + int scroll_delta = 0; while (running) { - do { + while (XPending(common.dpy)) { XNextEvent(common.dpy, &event); if (event.type == xkb_event_type) { change_kbd = 1; @@ -115,13 +128,28 @@ mainloop(void) { need_redraw = 1; break; case ButtonPress: - need_redraw |= button_press(&event); + /* FIXME: this is all a bit hacky */ + if (event.xbutton.button == Button4 || + event.xbutton.button == Button5) { + scroll_delta = event.xbutton.button == Button4 ? -1 : 1; + if (last_scroll_valid) { + scroll_num += scroll_delta; + } else { + last_scroll_event = event; + last_scroll_valid = 1; + scroll_num = scroll_delta; + } + } else { + need_redraw |= button_press(&event, 0); + } break; case ButtonRelease: need_redraw |= button_release(&event); break; case MotionNotify: - need_redraw |= drag_motion(&event); + /* FIXME: is it legal to just copy event like this? */ + last_motion_event = event; + last_motion_valid = 1; break; case KeyPress: need_redraw = 1; @@ -143,7 +171,25 @@ mainloop(void) { default: break; } - } while (XPending(common.dpy)); + }; + if (last_motion_valid) { + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &last_motion, &elapsed); + if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) { + need_redraw |= drag_motion(&last_motion_event); + last_motion = now; + last_motion_valid = 0; + } + } + if (last_scroll_valid) { + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &last_scroll, &elapsed); + if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= MOUSE_TICK) { + need_redraw |= button_press(&last_scroll_event, scroll_num); + last_scroll = now; + last_scroll_valid = 0; + } + } if (change_kbd) { change_kbd = 0; @@ -163,6 +209,14 @@ mainloop(void) { redraw(); need_redraw = 0; } + + clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, &last, &elapsed); + if (elapsed.tv_sec == 0 && elapsed.tv_nsec < TICK) { + sleep_time.tv_nsec = TICK - elapsed.tv_nsec; + nanosleep(&sleep_time, NULL); + } + last = now; } } @@ -251,8 +305,8 @@ redraw(void) { } static int -button_press(XEvent *event) { - return ledit_window_button_press(window, event); +button_press(XEvent *event, int scroll_num) { + return ledit_window_button_press(window, event, scroll_num); } static int diff --git a/theme_config.h b/theme_config.h @@ -4,6 +4,6 @@ static const char *TEXT_BG = "#FFFFFF"; /* FIXME: give in units other than pixels */ static const int SCROLLBAR_WIDTH = 10; -static const int SCROLLBAR_STEP = 10; +static const int SCROLLBAR_STEP = 20; static const char *SCROLLBAR_BG = "#CCCCCC"; static const char *SCROLLBAR_FG = "#000000"; diff --git a/window.c b/window.c @@ -700,7 +700,7 @@ clipboard_selrequest(ledit_window *window, XEvent *e) /* FIXME: improve set_scroll_pos; make it a bit clearer */ int -ledit_window_button_press(ledit_window *window, XEvent *event) { +ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num) { int x, y; double scroll_h, scroll_y; switch (event->xbutton.button) { @@ -723,18 +723,12 @@ ledit_window_button_press(ledit_window *window, XEvent *event) { } break; case Button4: - window->scroll_offset -= window->theme->scrollbar_step; + case Button5: + window->scroll_offset += scroll_num * window->theme->scrollbar_step; if (window->scroll_offset < 0) window->scroll_offset = 0; - if (window->scroll_callback) - window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); - return 1; - case Button5: - if (window->scroll_offset + window->text_h < window->scroll_max) { - window->scroll_offset += window->theme->scrollbar_step; - if (window->scroll_offset + window->text_h > window->scroll_max) { - window->scroll_offset = window->scroll_max - window->text_h; - } + if (window->scroll_offset + window->text_h > window->scroll_max) { + window->scroll_offset = window->scroll_max - window->text_h; } if (window->scroll_callback) window->scroll_callback(window->scroll_cb_data, (long)window->scroll_offset); diff --git a/window.h b/window.h @@ -74,7 +74,7 @@ void clipboard_paste_clipboard(ledit_window *window); void clipboard_paste_primary(ledit_window *window); txtbuf *ledit_window_get_primary_clipboard_buffer(void); -int ledit_window_button_press(ledit_window *window, XEvent *event); +int ledit_window_button_press(ledit_window *window, XEvent *event, int scroll_num); int ledit_window_button_release(ledit_window *window, XEvent *event); int ledit_window_drag_motion(ledit_window *window, XEvent *event); int ledit_window_clipboard_event(ledit_window *window, XEvent *event);