commit 10a6b45de3f15406bb82817a1bf28c79d492145f
parent b4ed6ddf9920d90814860156ed32a40404ece243
Author: lumidify <nobody@lumidify.org>
Date: Sat, 15 May 2021 21:07:54 +0200
Add initial work for selection support
Diffstat:
6 files changed, 152 insertions(+), 0 deletions(-)
diff --git a/IDEAS b/IDEAS
@@ -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
diff --git a/LICENSE b/LICENSE
@@ -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.
diff --git a/buffer.c b/buffer.c
@@ -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;
@@ -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);
diff --git a/buffer.h b/buffer.h
@@ -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. */
@@ -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);
diff --git a/common.h b/common.h
@@ -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;
diff --git a/ledit.c b/ledit.c
@@ -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);
@@ -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
@@ -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;
@@ -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:
@@ -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;
@@ -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;
}
@@ -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);