Add selection + clipboard support (cut/copy/paste)
This commit is contained in:
@@ -6,8 +6,6 @@ A TUI IDE.
|
||||
|
||||
# TODO
|
||||
|
||||
- [ ] Make function to get selected text. (selection itself is done)
|
||||
- [ ] Add support for copy/cut/paste.
|
||||
- [ ] Add support for ctrl + arrow key for moving words.
|
||||
- [ ] Add support for ctrl + backspace / delete for deleting words.
|
||||
- [ ] Add underline highlight for current word and all occurences.
|
||||
|
||||
@@ -109,6 +109,8 @@ void render_editor(Editor *editor);
|
||||
void fold(Editor *editor, uint32_t start_line, uint32_t end_line);
|
||||
void cursor_up(Editor *editor, uint32_t number);
|
||||
void cursor_down(Editor *editor, uint32_t number);
|
||||
Coord move_left(Editor *editor, Coord cursor, uint32_t number);
|
||||
Coord move_right(Editor *editor, Coord cursor, uint32_t number);
|
||||
void cursor_left(Editor *editor, uint32_t number);
|
||||
void cursor_right(Editor *editor, uint32_t number);
|
||||
void scroll_up(Editor *editor, uint32_t number);
|
||||
@@ -120,5 +122,6 @@ void apply_edit(std::vector<Span> &spans, uint32_t x, int64_t y);
|
||||
void edit_erase(Editor *editor, Coord pos, int64_t len);
|
||||
void edit_insert(Editor *editor, Coord pos, char *data, uint32_t len);
|
||||
Coord editor_hit_test(Editor *editor, uint32_t x, uint32_t y);
|
||||
char *get_selection(Editor *editor, uint32_t *out_len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,9 +34,21 @@ template <typename T> struct Queue {
|
||||
struct Coord {
|
||||
uint32_t row;
|
||||
uint32_t col;
|
||||
|
||||
bool operator<(const Coord &other) const {
|
||||
return row < other.row || (row == other.row && col < other.col);
|
||||
}
|
||||
bool operator<=(const Coord &other) const {
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
bool operator==(const Coord &other) const {
|
||||
return row == other.row && col == other.col;
|
||||
}
|
||||
bool operator!=(const Coord &other) const { return !(*this == other); }
|
||||
bool operator>(const Coord &other) const { return other < *this; }
|
||||
bool operator>=(const Coord &other) const { return !(*this < other); }
|
||||
};
|
||||
|
||||
// std::vector<uint32_t> visual_width(const char *s, uint32_t max_width);
|
||||
int display_width(const char *str, size_t len);
|
||||
uint32_t get_visual_col_from_bytes(const char *line, uint32_t len,
|
||||
uint32_t byte_limit);
|
||||
@@ -47,5 +59,7 @@ std::string get_exe_dir();
|
||||
char *load_file(const char *path, uint32_t *out_len);
|
||||
char *detect_file_type(const char *filename);
|
||||
Language language_for_file(const char *filename);
|
||||
void copy_to_clipboard(const char *text, size_t len);
|
||||
char *get_from_clipboard(uint32_t *out_len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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