Add selection + clipboard support (cut/copy/paste)
This commit is contained in:
@@ -75,12 +75,16 @@ void render_editor(Editor *editor) {
|
||||
uint32_t render_width = editor->size.col - numlen;
|
||||
uint32_t render_x = editor->position.col + numlen;
|
||||
if (editor->selection_active) {
|
||||
uint32_t sel1 = line_to_byte(editor->root, editor->cursor.row, nullptr) +
|
||||
editor->cursor.col;
|
||||
uint32_t sel2 = line_to_byte(editor->root, editor->selection.row, nullptr) +
|
||||
editor->selection.col;
|
||||
sel_start = MIN(sel1, sel2);
|
||||
sel_end = MAX(sel1, sel2);
|
||||
Coord start, end;
|
||||
if (editor->cursor >= editor->selection) {
|
||||
start = editor->selection;
|
||||
end = move_right(editor, editor->cursor, 1);
|
||||
} else {
|
||||
start = editor->cursor;
|
||||
end = move_right(editor, editor->selection, 1);
|
||||
}
|
||||
sel_start = line_to_byte(editor->root, start.row, nullptr) + start.col;
|
||||
sel_end = line_to_byte(editor->root, end.row, nullptr) + end.col;
|
||||
}
|
||||
Coord cursor = {UINT32_MAX, UINT32_MAX};
|
||||
uint32_t line_index = editor->scroll.row;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#include "../libs/libgrapheme/grapheme.h"
|
||||
}
|
||||
@@ -7,6 +8,9 @@ extern "C" {
|
||||
#include <cmath>
|
||||
|
||||
void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
static std::chrono::steady_clock::time_point last_click_time =
|
||||
std::chrono::steady_clock::now();
|
||||
static Coord last_click_pos = {UINT32_MAX, UINT32_MAX};
|
||||
if (event.key_type == KEY_SPECIAL) {
|
||||
switch (event.special_key) {
|
||||
case KEY_DOWN:
|
||||
@@ -24,6 +28,10 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
}
|
||||
}
|
||||
if (event.key_type == KEY_MOUSE) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_click_time)
|
||||
.count();
|
||||
switch (event.mouse_state) {
|
||||
case SCROLL:
|
||||
switch (event.mouse_direction) {
|
||||
@@ -35,17 +43,31 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
scroll_down(editor, 10);
|
||||
ensure_cursor(editor);
|
||||
break;
|
||||
case SCROLL_LEFT:
|
||||
cursor_left(editor, 10);
|
||||
break;
|
||||
case SCROLL_RIGHT:
|
||||
cursor_right(editor, 10);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PRESS:
|
||||
if (event.mouse_button == LEFT_BTN) {
|
||||
Coord p = editor_hit_test(editor, event.mouse_x, event.mouse_y);
|
||||
editor->cursor = p;
|
||||
editor->cursor_preffered = UINT32_MAX;
|
||||
editor->selection = p;
|
||||
if (mode == SELECT) {
|
||||
mode = NORMAL;
|
||||
editor->selection_active = false;
|
||||
if (duration < 250 &&
|
||||
last_click_pos == (Coord){event.mouse_x, event.mouse_y}) {
|
||||
mode = SELECT;
|
||||
editor->selection_active = true;
|
||||
} else {
|
||||
Coord p = editor_hit_test(editor, event.mouse_x, event.mouse_y);
|
||||
editor->cursor = p;
|
||||
editor->cursor_preffered = UINT32_MAX;
|
||||
editor->selection = p;
|
||||
if (mode == SELECT) {
|
||||
mode = NORMAL;
|
||||
editor->selection_active = false;
|
||||
}
|
||||
last_click_pos = (Coord){event.mouse_x, event.mouse_y};
|
||||
last_click_time = now;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -143,6 +165,8 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
break;
|
||||
case SELECT:
|
||||
if (event.key_type == KEY_CHAR && event.len == 1) {
|
||||
uint32_t len;
|
||||
char *text;
|
||||
switch (event.c[0]) {
|
||||
case 0x1B:
|
||||
case 's':
|
||||
@@ -150,6 +174,43 @@ void handle_editor_event(Editor *editor, KeyEvent event) {
|
||||
editor->selection_active = false;
|
||||
mode = NORMAL;
|
||||
break;
|
||||
case 'y':
|
||||
text = get_selection(editor, &len);
|
||||
copy_to_clipboard(text, len);
|
||||
free(text);
|
||||
editor->selection_active = false;
|
||||
mode = NORMAL;
|
||||
break;
|
||||
case 'x':
|
||||
text = get_selection(editor, &len);
|
||||
copy_to_clipboard(text, len);
|
||||
edit_erase(editor, MIN(editor->cursor, editor->selection), len);
|
||||
free(text);
|
||||
editor->selection_active = false;
|
||||
mode = NORMAL;
|
||||
break;
|
||||
case 'p':
|
||||
text = get_from_clipboard(&len);
|
||||
if (text) {
|
||||
Coord start, end;
|
||||
if (editor->cursor >= editor->selection) {
|
||||
start = editor->selection;
|
||||
end = move_right(editor, editor->cursor, 1);
|
||||
} else {
|
||||
start = editor->cursor;
|
||||
end = move_right(editor, editor->selection, 1);
|
||||
}
|
||||
uint32_t start_byte =
|
||||
line_to_byte(editor->root, start.row, nullptr) + start.col;
|
||||
uint32_t end_byte =
|
||||
line_to_byte(editor->root, end.row, nullptr) + end.col;
|
||||
edit_erase(editor, start, end_byte - start_byte);
|
||||
edit_insert(editor, editor->cursor, text, len);
|
||||
free(text);
|
||||
}
|
||||
editor->selection_active = false;
|
||||
mode = NORMAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -562,6 +623,25 @@ void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len) {
|
||||
editor->spans.edits.push({byte_pos, len});
|
||||
}
|
||||
|
||||
char *get_selection(Editor *editor, uint32_t *out_len) {
|
||||
std::shared_lock lock(editor->knot_mtx);
|
||||
Coord start, end;
|
||||
if (editor->cursor >= editor->selection) {
|
||||
start = editor->selection;
|
||||
end = move_right(editor, editor->cursor, 1);
|
||||
} else {
|
||||
start = editor->cursor;
|
||||
end = move_right(editor, editor->selection, 1);
|
||||
}
|
||||
uint32_t start_byte =
|
||||
line_to_byte(editor->root, start.row, nullptr) + start.col;
|
||||
uint32_t end_byte = line_to_byte(editor->root, end.row, nullptr) + end.col;
|
||||
char *text = read(editor->root, start_byte, end_byte - start_byte);
|
||||
if (out_len)
|
||||
*out_len = end_byte - start_byte;
|
||||
return text;
|
||||
}
|
||||
|
||||
void apply_edit(std::vector<Span> &spans, uint32_t x, int64_t y) {
|
||||
Span key{.start = x, .end = 0, .hl = nullptr};
|
||||
auto it = std::lower_bound(
|
||||
|
||||
@@ -184,9 +184,7 @@ void scroll_down(Editor *editor, uint32_t number) {
|
||||
|
||||
void ensure_cursor(Editor *editor) {
|
||||
std::shared_lock knot_lock(editor->knot_mtx);
|
||||
if (editor->cursor.row < editor->scroll.row ||
|
||||
(editor->cursor.row == editor->scroll.row &&
|
||||
editor->cursor.col < editor->scroll.col)) {
|
||||
if (editor->cursor < editor->scroll) {
|
||||
editor->cursor = editor->scroll;
|
||||
return;
|
||||
}
|
||||
@@ -275,9 +273,7 @@ void ensure_scroll(Editor *editor) {
|
||||
uint32_t numlen =
|
||||
2 + static_cast<int>(std::log10(editor->root->line_count + 1));
|
||||
uint32_t render_width = editor->size.col - numlen;
|
||||
if (editor->cursor.row < editor->scroll.row ||
|
||||
(editor->cursor.row == editor->scroll.row &&
|
||||
editor->cursor.col < editor->scroll.col)) {
|
||||
if (editor->cursor < editor->scroll) {
|
||||
LineIterator *it = begin_l_iter(editor->root, editor->cursor.row);
|
||||
if (!it)
|
||||
return;
|
||||
|
||||
50
src/utils.cc
50
src/utils.cc
@@ -7,6 +7,7 @@ extern "C" {
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <limits.h>
|
||||
@@ -16,6 +17,55 @@ extern "C" {
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
|
||||
char *get_from_clipboard(uint32_t *out_len) {
|
||||
FILE *pipe = popen("xclip -selection clipboard -o", "r");
|
||||
if (!pipe) {
|
||||
*out_len = 0;
|
||||
return nullptr;
|
||||
}
|
||||
size_t capacity = 4096;
|
||||
size_t length = 0;
|
||||
char *buffer = (char *)malloc(capacity);
|
||||
if (!buffer) {
|
||||
pclose(pipe);
|
||||
*out_len = 0;
|
||||
return nullptr;
|
||||
}
|
||||
size_t n;
|
||||
while ((n = fread(buffer + length, 1, capacity - length, pipe)) > 0) {
|
||||
length += n;
|
||||
if (length == capacity) {
|
||||
capacity *= 2;
|
||||
char *tmp = (char *)realloc(buffer, capacity);
|
||||
if (!tmp) {
|
||||
free(buffer);
|
||||
pclose(pipe);
|
||||
*out_len = 0;
|
||||
return nullptr;
|
||||
}
|
||||
buffer = tmp;
|
||||
}
|
||||
}
|
||||
pclose(pipe);
|
||||
char *result = (char *)realloc(buffer, length + 1);
|
||||
if (result) {
|
||||
result[length] = '\0';
|
||||
buffer = result;
|
||||
} else {
|
||||
buffer[length] = '\0';
|
||||
}
|
||||
*out_len = length;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void copy_to_clipboard(const char *text, size_t len) {
|
||||
FILE *pipe = popen("xclip -selection clipboard", "w");
|
||||
if (!pipe)
|
||||
return;
|
||||
fwrite(text, sizeof(char), len, pipe);
|
||||
pclose(pipe);
|
||||
}
|
||||
|
||||
int display_width(const char *str, size_t len) {
|
||||
if (!str || !*str)
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user