diff --git a/README.md b/README.md index c1b7a70..3a5caa9 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/include/editor.h b/include/editor.h index 67c8cc3..f53b3b4 100644 --- a/include/editor.h +++ b/include/editor.h @@ -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 &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 diff --git a/include/utils.h b/include/utils.h index fb6d027..b471211 100644 --- a/include/utils.h +++ b/include/utils.h @@ -34,9 +34,21 @@ template 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 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 diff --git a/src/editor.cc b/src/editor.cc index c299f23..3bb2f18 100644 --- a/src/editor.cc +++ b/src/editor.cc @@ -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; diff --git a/src/editor_ctrl.cc b/src/editor_ctrl.cc index 0aba091..85a2de9 100644 --- a/src/editor_ctrl.cc +++ b/src/editor_ctrl.cc @@ -1,3 +1,4 @@ +#include extern "C" { #include "../libs/libgrapheme/grapheme.h" } @@ -7,6 +8,9 @@ extern "C" { #include 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( + 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 &spans, uint32_t x, int64_t y) { Span key{.start = x, .end = 0, .hl = nullptr}; auto it = std::lower_bound( diff --git a/src/editor_scroll.cc b/src/editor_scroll.cc index cde7331..af9785a 100644 --- a/src/editor_scroll.cc +++ b/src/editor_scroll.cc @@ -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(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; diff --git a/src/utils.cc b/src/utils.cc index b8d61f9..3858564 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -7,6 +7,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -16,6 +17,55 @@ extern "C" { #include #include +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;