tAdd initial work for selection support - 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 10a6b45de3f15406bb82817a1bf28c79d492145f | |
parent b4ed6ddf9920d90814860156ed32a40404ece243 | |
Author: lumidify <[email protected]> | |
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(-) | |
--- | |
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 | |
diff --git a/LICENSE b/LICENSE | |
t@@ -0,0 +1,15 @@ | |
+ISC License | |
+ | |
+Copyright (c) 2021 lumidify <[email protected]> | |
+ | |
+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 | |
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 e… | |
+ 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 | |
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 select… | |
}; | |
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, … | |
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 *… | |
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; | |
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… | |
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 … | |
+ 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… | |
+ 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… | |
+ 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… | |
+ 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_ne… | |
+ } else { | |
+ ledit_line *ll1 = ledit_get_line(buffer, l1_new); | |
+ ledit_set_line_selection(buffer, l1_new, b1_new, ll1->… | |
+ 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… | |
+ ledit_line *llx = ledit_get_line(buffe… | |
+ ledit_set_line_selection(buffer, i, 0,… | |
+ } | |
+ } | |
+ } | |
+ } | |
+ 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); |