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 <nobody@lumidify.org>
Date:   Sat, 15 May 2021 21:07:54 +0200

Add initial work for selection support

Diffstat:
MIDEAS | 1+
ALICENSE | 15+++++++++++++++
Mbuffer.c | 19+++++++++++++++++++
Mbuffer.h | 9+++++++++
Mcommon.h | 1+
Mledit.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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);