URI: 
       tAdd initial work for selection support - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 10a6b45de3f15406bb82817a1bf28c79d492145f
   DIR parent b4ed6ddf9920d90814860156ed32a40404ece243
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Sat, 15 May 2021 21:07:54 +0200
       
       Add initial work for selection support
       
       Diffstat:
         M IDEAS                               |       1 +
         A LICENSE                             |      15 +++++++++++++++
         M buffer.c                            |      19 +++++++++++++++++++
         M buffer.h                            |       9 +++++++++
         M common.h                            |       1 +
         M ledit.c                             |     107 +++++++++++++++++++++++++++++++
       
       6 files changed, 152 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/IDEAS b/IDEAS
       t@@ -1,2 +1,3 @@
        * allow editing same file in multiple places at same time (like in acme)
        * add different (more basic) text backend
       +* visual selection mode - allow to switch cursor between selection ends
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -0,0 +1,15 @@
       +ISC License
       +
       +Copyright (c) 2021 lumidify <nobody@lumidify.org>
       +
       +Permission to use, copy, modify, and/or distribute this software for any
       +purpose with or without fee is hereby granted, provided that the above
       +copyright notice and this permission notice appear in all copies.
       +
       +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   DIR diff --git a/buffer.c b/buffer.c
       t@@ -35,6 +35,8 @@ ledit_create_buffer(ledit_common_state *state) {
                buffer->end_of_soft_line = 0;
                buffer->total_height = 0;
                buffer->display_offset = 0;
       +        buffer->sel.line1 = buffer->sel.byte1 = -1;
       +        buffer->sel.line2 = buffer->sel.byte2 = -1;
                ledit_append_line(buffer, -1, -1);
        
                return buffer;
       t@@ -51,6 +53,23 @@ ledit_destroy_buffer(ledit_buffer *buffer) {
        }
        
        void
       +ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte) {
       +        PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0);
       +        PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535);
       +        attr0->start_index = start_byte;
       +        attr0->end_index = end_byte;
       +        attr1->start_index = start_byte;
       +        attr1->end_index = end_byte;
       +        PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
       +        PangoAttrList *list = pango_attr_list_new();
       +        pango_attr_list_insert(list, attr0);
       +        pango_attr_list_insert(list, attr1);
       +        pango_attr_list_insert(list, attr2);
       +        pango_layout_set_attributes(buffer->lines[line].layout, list);
       +        buffer->lines[line].dirty = 1;
       +}
       +
       +void
        ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index) {
                if (buffer->state->mode == NORMAL) {
                        PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0);
   DIR diff --git a/buffer.h b/buffer.h
       t@@ -1,3 +1,10 @@
       +typedef struct {
       +        int line1;
       +        int byte1;
       +        int line2;
       +        int byte2;
       +} ledit_selection;
       +
        typedef struct ledit_buffer ledit_buffer;
        
        /* FIXME: size_t for len, etc. */
       t@@ -28,10 +35,12 @@ struct ledit_buffer {
                long total_height; /* total pixel height of all lines */
                double display_offset; /* current pixel offset of viewport - this
                                        * is a double to make scrolling smoother */
       +        ledit_selection sel; /* current selection; all entries -1 if no selection */
        };
        
        ledit_buffer *ledit_create_buffer(ledit_common_state *state);
        void ledit_destroy_buffer(ledit_buffer *buffer);
       +void ledit_set_line_selection(ledit_buffer *buffer, int line, int start_byte, int end_byte);
        void ledit_set_line_cursor_attrs(ledit_buffer *buffer, int line, int index);
        void ledit_wipe_line_cursor_attrs(ledit_buffer *buffer, int line);
        void ledit_insert_text(ledit_buffer *buffer, int line_index, int index, char *text, int len);
   DIR diff --git a/common.h b/common.h
       t@@ -21,6 +21,7 @@ typedef struct {
                int h;
                int scroll_dragging;
                int scroll_grab_handle;
       +        int selecting;
                enum ledit_mode mode;
                XIM xim;
                XIC xic;
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -476,6 +476,7 @@ setup(int argc, char *argv[]) {
        
                state.scroll_dragging = 0;
                state.scroll_grab_handle = 0;
       +        state.selecting = 0;
                state.w = 500;
                state.h = 500;
                state.dpy = XOpenDisplay(NULL);
       t@@ -536,6 +537,7 @@ setup(int argc, char *argv[]) {
                    InputOutput, state.vis,
                    CWBackPixel | CWColormap | CWBitGravity, &attrs
                );
       +        XSetStandardProperties(state.dpy, state.win, "ledit", NULL, None, argv, argc, NULL);
        
                state.back_buf = XdbeAllocateBackBufferName(
                    state.dpy, state.win, XdbeBackground
       t@@ -711,6 +713,97 @@ redraw(void) {
                XFlush(state.dpy);
        }
        
       +static void
       +xy_to_line_byte(int x, int y, int *line_ret, int *byte_ret) {
       +        /* FIXME: store current line offset to speed this up */
       +        /* FIXME: use y_offset in lines */
       +        long h = 0;
       +        double pos = buffer->display_offset + y;
       +        for (int i = 0; i < buffer->lines_num; i++) {
       +                ledit_line *line = ledit_get_line(buffer, i);
       +                if ((h <= pos && h + line->h > pos) || i == buffer->lines_num - 1) {
       +                        int index, trailing;
       +                        pango_layout_xy_to_index(
       +                            line->layout,
       +                            x * PANGO_SCALE, (int)(pos - h) * PANGO_SCALE,
       +                            &index, &trailing
       +                        );
       +                        /* FIXME: make this a separate, reusable function */
       +                        while (trailing > 0) {
       +                                trailing--;
       +                                index++;
       +                                while (index < line->len && ((line->text[index] & 0xC0) == 0x80))
       +                                        index++;
       +                        }
       +                        *line_ret = i;
       +                        *byte_ret = index;
       +                        break;
       +                }
       +                h += line->h;
       +        }
       +}
       +
       +static void
       +swap(int *a, int *b) {
       +        int tmp = *a;
       +        *a = *b;
       +        *b = tmp;
       +}
       +
       +static void
       +sort_selection(int *line1, int *byte1, int *line2, int *byte2) {
       +        if (*line1 > *line2) {
       +                swap(line1, line2);
       +                swap(byte1, byte2);
       +        } else if (*line1 == *line2 && *byte1 > *byte2) {
       +                swap(byte1, byte2);
       +        }
       +}
       +
       +static void
       +set_selection(int line1, int byte1, int line2, int byte2) {
       +        if (line1 == buffer->sel.line1 && line2 == buffer->sel.line2 &&
       +            byte1 == buffer->sel.byte1 && byte2 == buffer->sel.byte2) {
       +                return;
       +        }
       +        if (buffer->sel.line1 >= 0) {
       +                int l1_new = line1, l2_new = line2;
       +                int b1_new = byte1, b2_new = byte2;
       +                sort_selection(&buffer->sel.line1, &buffer->sel.byte1, &buffer->sel.line2, &buffer->sel.byte2);
       +                sort_selection(&l1_new, &b1_new, &l2_new, &b2_new);
       +                if (buffer->sel.line1 > l2_new || buffer->sel.line2 < l1_new) {
       +                        for (int i = buffer->sel.line1; i <= buffer->sel.line2; i++) {
       +                                ledit_wipe_line_cursor_attrs(buffer, i);
       +                        }
       +                } else {
       +                        for (int i = buffer->sel.line1; i < l1_new; i++) {
       +                                ledit_wipe_line_cursor_attrs(buffer, i);
       +                        }
       +                        for (int i = buffer->sel.line2; i > l2_new; i--) {
       +                                ledit_wipe_line_cursor_attrs(buffer, i);
       +                        }
       +                }
       +                if (l1_new == l2_new) {
       +                        ledit_set_line_selection(buffer, l1_new, b1_new, b2_new);
       +                } else {
       +                        ledit_line *ll1 = ledit_get_line(buffer, l1_new);
       +                        ledit_set_line_selection(buffer, l1_new, b1_new, ll1->len);
       +                        ledit_set_line_selection(buffer, l2_new, 0, b2_new);
       +                        /* FIXME: optimize this */
       +                        for (int i = l1_new + 1; i < l2_new; i++) {
       +                                if (i <= buffer->sel.line1 || i >= buffer->sel.line2) {
       +                                        ledit_line *llx = ledit_get_line(buffer, i);
       +                                        ledit_set_line_selection(buffer, i, 0, llx->len);
       +                                }
       +                        }
       +                }
       +        }
       +        buffer->sel.line1 = line1;
       +        buffer->sel.byte1 = byte1;
       +        buffer->sel.line2 = line2;
       +        buffer->sel.byte2 = byte2;
       +}
       +
        static int
        button_press(XEvent *event) {
                int x, y;
       t@@ -728,6 +821,12 @@ button_press(XEvent *event) {
                                                set_scroll_pos(new_scroll_y);
                                        }
                                        return 1;
       +                        } else {
       +                                int l, b;
       +                                xy_to_line_byte(x, y, &l, &b);
       +                                set_selection(l, b, l, b);
       +                                state.selecting = 1;
       +                                return 1;
                                }
                                break;
                        case Button4:
       t@@ -753,6 +852,7 @@ static int
        button_release(XEvent *event) {
                if (event->xbutton.button == Button1) {
                        state.scroll_dragging = 0;
       +                state.selecting = 0;
                        return 1;
                }
                return 0;
       t@@ -767,6 +867,12 @@ drag_motion(XEvent *event) {
                        state.scroll_grab_handle = event->xbutton.y;
                        set_scroll_pos(scroll_y);
                        return 1;
       +        } else if (state.selecting) {
       +                int l, b;
       +                int y = event->xbutton.y >= 0 ? event->xbutton.y : 0;
       +                xy_to_line_byte(event->xbutton.x, y, &l, &b);
       +                set_selection(buffer->sel.line1, buffer->sel.byte1, l, b);
       +                return 1;
                }
                return 0;
        }
       t@@ -950,6 +1056,7 @@ return_key(void) {
        static void
        escape_key(void) {
                state.mode = NORMAL;
       +        clear_key_stack();
                PangoDirection dir = PANGO_DIRECTION_RTL;
                int tmp_index = buffer->cur_index;
                ledit_line *cur_line = ledit_get_line(buffer, buffer->cur_line);