Commit: 9f07a3c8ab0721beb511b6f28186481a901cfdce
Parent: 978e8aeb16dc03fe47a476093c5f4fc4a593daa2
Author: Randy Palamar
Date: Fri, 23 Aug 2024 21:10:08 -0600
add word and elastic selections
Diffstat:
M | terminal.c | | | 14 | ++++++++++++++ |
M | util.c | | | 23 | +++++++++++++++++++++++ |
M | util.h | | | 13 | ++++++++----- |
M | vtgl.c | | | 64 | +++++++++++++++++++++++++++++++++++++++++----------------------- |
4 files changed, 86 insertions(+), 28 deletions(-)
diff --git a/terminal.c b/terminal.c
@@ -23,6 +23,20 @@ static const u8 utf8overhangmask[32] = {
0, 0, 0, 0, 0, 0, 0, 0
};
+static Range
+get_word_around_cell(Term *t, iv2 cell)
+{
+ Range result = {.start = cell, .end = cell};
+ Framebuffer *fb = &t->views[t->view_idx].fb;
+
+ b32 isspace = ISSPACE(fb->rows[cell.y][cell.x].cp);
+ while (result.start.x > 0 && isspace == ISSPACE(fb->rows[cell.y][result.start.x - 1].cp))
+ result.start.x--;
+ while (result.end.x < t->size.w - 1 && isspace == ISSPACE(fb->rows[cell.y][result.end.x + 1].cp))
+ result.end.x++;
+ return result;
+}
+
static void
set_window_title(GLFWwindow *win, Arena a, s8 title)
{
diff --git a/util.c b/util.c
@@ -10,6 +10,29 @@ equal_iv2(iv2 a, iv2 b)
return result;
}
+static b32
+is_valid_range(Range r)
+{
+ b32 result = !equal_iv2(r.end, INVALID_RANGE_END);
+ return result;
+}
+
+static Range
+normalize_range(Range r)
+{
+ Range result;
+ if (r.start.y < r.end.y) {
+ result = r;
+ } else if (r.end.y < r.start.y) {
+ result = (Range){.start = r.end, .end = r.start};
+ } else {
+ result.start.y = result.end.y = r.start.y;
+ result.start.x = MIN(r.start.x, r.end.x);
+ result.end.x = MAX(r.start.x, r.end.x);
+ }
+ return result;
+}
+
static void __attribute__((noreturn))
die(char *fmt, ...)
{
diff --git a/util.h b/util.h
@@ -86,6 +86,9 @@ typedef struct { u8 *beg, *end; } Arena;
typedef struct { size len; u8 *data; } s8;
#define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s}
+typedef struct { iv2 start, end; } Range;
+#define INVALID_RANGE_END (iv2){.x = -1, .y = -1}
+
enum cell_attribute {
ATTR_NULL = 0,
ATTR_BOLD = 1 << 0,
@@ -286,12 +289,12 @@ enum selection_states {
};
typedef struct {
- iv2 start, end;
- v2 mouse_start;
- f32 click_param;
- enum selection_states state;
+ Range range;
+ Range anchor;
+ v2 mouse_start;
+ f32 click_param;
+ enum selection_states state;
} Selection;
-#define INVALID_SELECTION_POINT (iv2){.x = -1, .y = -1}
enum terminal_mode {
TM_ALTSCREEN = 1 << 0,
diff --git a/vtgl.c b/vtgl.c
@@ -138,8 +138,6 @@ update_font_textures(Term *t)
static void
update_uniforms(Term *t, enum shader_stages stage)
{
- ASSERT(stage != SHADER_LAST);
-
switch (stage) {
case SHADER_RENDER:
for (u32 i = 0; i < ARRAY_COUNT(t->gl.render.uniforms); i++) {
@@ -341,14 +339,16 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
/* NOTE: draw selection if active */
/* TODO: combine with original push_cell? */
- if (!equal_iv2(t->selection.end, INVALID_SELECTION_POINT)) {
- iv2 curs = t->selection.start;
+ if (is_valid_range(t->selection.range)) {
+ Range sel = t->selection.range;
+ iv2 curs = sel.start;
+ iv2 end = sel.end;
Rect cr = {
.pos = {.x = tl.x + cs.w * curs.x, .y = tl.h - cs.h * (curs.y + 1)},
.size = cs,
};
/* NOTE: do full rows first */
- for (; curs.y < t->selection.end.y; curs.y++) {
+ for (; curs.y < end.y; curs.y++) {
for (; curs.x < t->size.w; curs.x++) {
Cell cell = tv->fb.rows[curs.y][curs.x];
cell.style.attr ^= ATTR_INVERSE;
@@ -360,7 +360,7 @@ render_framebuffer(Term *t, RenderPushBuffer *rpb)
cr.pos.y -= cs.h;
}
/* NOTE: do the last row */
- for (; curs.x < t->selection.end.x; curs.x++) {
+ for (; curs.x <= end.x; curs.x++) {
Cell cell = tv->fb.rows[curs.y][curs.x];
cell.style.attr ^= ATTR_INVERSE;
push_cell(rpb, &t->gl, cell, cr, t->fa.deltay);
@@ -405,31 +405,43 @@ update_selection(Term *t)
glfwGetCursorPos(t->gl.window, &xpos, &ypos);
v2 mouse = {.x = xpos, .y = ypos};
- if (mouse.x == sel->mouse_start.x && mouse.y == sel->mouse_start.y)
+ if (sel->state != SS_WORDS && (mouse.x == sel->mouse_start.x && mouse.y == sel->mouse_start.y))
return;
- iv2 newp = mouse_to_cell_space(t, mouse);
-
- if (0 && t->selection.state == SS_WORDS) {
- /* TODO: word selection */
+ iv2 new_p = mouse_to_cell_space(t, mouse);
+ if (sel->state != SS_WORDS) {
+ sel->range.start = sel->anchor.start;
+ sel->range.end = new_p;
} else {
- if (newp.x <= sel->start.x && newp.y <= sel->start.y) {
- if (equal_iv2(t->selection.end, INVALID_SELECTION_POINT))
- sel->end = sel->start;
- sel->start = newp;
+ Range word = get_word_around_cell(t, new_p);
+ if (sel->anchor.start.y < word.start.y) {
+ sel->range.start = sel->anchor.start;
+ sel->range.end = word.end;
+ } else if (sel->anchor.start.y > word.start.y) {
+ sel->range.start = word.start;
+ sel->range.end = sel->anchor.end;
} else {
- sel->end = newp;
+ if (word.start.x < sel->anchor.start.x) {
+ sel->range.start = word.start;
+ sel->range.end = sel->anchor.end;
+ } else {
+ sel->range.start = sel->anchor.start;
+ sel->range.end = word.end;
+ }
}
}
+ sel->range = normalize_range(sel->range);
}
KEYBIND_FN(copy)
{
- if (equal_iv2(t->selection.end, INVALID_SELECTION_POINT))
+ if (!is_valid_range(t->selection.range))
return 1;
TermView *tv = t->views + t->view_idx;
- iv2 curs = t->selection.start;
+ Range sel = t->selection.range;
+ iv2 curs = sel.start;
+ iv2 end = sel.end;
i32 buf_curs = 0;
/* NOTE: super piggy but we are only holding onto it for the function duration */
@@ -439,7 +451,7 @@ KEYBIND_FN(copy)
/* NOTE: do full rows first */
u32 last_non_space_idx = 0;
- for (; curs.y < t->selection.end.y; curs.y++) {
+ for (; curs.y < end.y; curs.y++) {
for (; curs.x < t->size.w && buf_curs != buf_size; curs.x++) {
/* TODO: handle the utf8 case */
u32 cp = tv->fb.rows[curs.y][curs.x].cp;
@@ -452,7 +464,7 @@ KEYBIND_FN(copy)
}
/* NOTE: do the last row */
- for (; curs.x < t->selection.end.x && buf_curs != buf_size; curs.x++)
+ for (; curs.x <= end.x && buf_curs != buf_size; curs.x++)
buf[buf_curs++] = (char)tv->fb.rows[curs.y][curs.x].cp;
CLAMP(buf_curs, 0, buf_size - 1);
@@ -623,7 +635,7 @@ mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod)
f64 xpos, ypos;
glfwGetCursorPos(win, &xpos, &ypos);
- t->selection.end = (iv2){.x = -1, .y = -1};
+ t->selection.range.end = (iv2){.x = -1, .y = -1};
t->selection.mouse_start = (v2){.x = xpos, .y = ypos};
t->selection.click_param = DOUBLE_CLICK_TIME;
@@ -631,7 +643,13 @@ mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod)
t->selection.state++;
iv2 cell = mouse_to_cell_space(t, t->selection.mouse_start);
- t->selection.start = cell;
+
+ if (t->selection.state == SS_WORDS) {
+ t->selection.anchor = get_word_around_cell(t, cell);
+ } else {
+ t->selection.anchor = (Range){.start = cell, .end = cell};
+ t->selection.range.end = INVALID_RANGE_END;
+ }
}
static void
@@ -642,7 +660,7 @@ char_callback(GLFWwindow *win, u32 codepoint)
t->scroll_offset = 0;
t->gl.flags |= NEEDS_FULL_BLIT;
}
- t->selection.end = INVALID_SELECTION_POINT;
+ t->selection.range.end = INVALID_RANGE_END;
os_child_put_char(t->child, codepoint);
}