Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/editor.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ void editor_free(editor_t *ed)
clear_edit_stack(&ed->redo_stack, &ed->redo_count);
free(ed->undo_stack);
free(ed->redo_stack);
free(ed->clipboard);
buffer_free(&ed->buf);
}

Expand Down Expand Up @@ -748,3 +749,76 @@ void editor_redo(editor_t *ed)
rec.old_text, rec.old_len, rec.new_text, rec.new_len);
free_edit_record(&rec);
}

void editor_copy(editor_t *ed)
{
if (!editor_has_selection(ed)) {
return;
}
size_t r1, c1, r2, c2;
editor_selection_range(ed, &r1, &c1, &r2, &c2);

free(ed->clipboard);
ed->clipboard = buffer_extract_text(&ed->buf, r1, c1, r2, c2, &ed->clipboard_len);
}

void editor_cut(editor_t *ed)
{
if (!editor_has_selection(ed)) {
return;
}
editor_copy(ed);

size_t r1, c1, r2, c2;
editor_selection_range(ed, &r1, &c1, &r2, &c2);

edit_capture_t cap;
capture_before(ed, r1, c1, r2, c2, &cap);

buffer_delete_range(&ed->buf, r1, c1, r2, c2);
ed->cy = r1;
ed->cx = c1;
ed->sel.active = false;
ed->goal_rx = editor_cx_to_rx(&ed->buf.lines[ed->cy], ed->cx);

commit_edit(ed, &cap, ed->cy, ed->cx);
}

static void insert_text_replacing_selection(editor_t *ed, const char *text, size_t len)
{
size_t r1, c1, r2, c2;
if (editor_has_selection(ed)) {
editor_selection_range(ed, &r1, &c1, &r2, &c2);
} else {
r1 = r2 = ed->cy;
c1 = c2 = ed->cx;
}

edit_capture_t cap;
capture_before(ed, r1, c1, r2, c2, &cap);

if (editor_has_selection(ed)) {
delete_selection(ed);
}
buffer_insert_text(&ed->buf, ed->cy, ed->cx, text, len);
text_end_position(ed->cy, ed->cx, text, len, &ed->cy, &ed->cx);
ed->goal_rx = editor_cx_to_rx(&ed->buf.lines[ed->cy], ed->cx);

commit_edit(ed, &cap, ed->cy, ed->cx);
}

void editor_paste(editor_t *ed)
{
if (ed->clipboard == NULL || ed->clipboard_len == 0) {
return;
}
insert_text_replacing_selection(ed, ed->clipboard, ed->clipboard_len);
}

void editor_paste_text(editor_t *ed, const char *text, size_t len)
{
if (len == 0) {
return;
}
insert_text_replacing_selection(ed, text, len);
}
8 changes: 8 additions & 0 deletions src/editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ typedef struct {
size_t redo_count;
size_t redo_cap;

char *clipboard;
size_t clipboard_len;

bool quit_confirm_pending;
bool should_quit;
} editor_t;
Expand Down Expand Up @@ -107,4 +110,9 @@ void editor_scroll_view(editor_t *ed, int delta_lines);
void editor_undo(editor_t *ed);
void editor_redo(editor_t *ed);

void editor_copy(editor_t *ed);
void editor_cut(editor_t *ed);
void editor_paste(editor_t *ed);
void editor_paste_text(editor_t *ed, const char *text, size_t len);

#endif /* DAF_EDITOR_H */
27 changes: 27 additions & 0 deletions src/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ void input_process_key(editor_t *ed, key_event_t key)
return;
}

if (key.type == KEY_PASTE) {
editor_paste_text(ed, key.paste_text, key.paste_len);
editor_set_status(ed, "Pasted from terminal");
return;
}

bool is_ctrl_key = key.type == KEY_CHAR && (key.mods & MOD_CTRL) != 0;
if (!(is_ctrl_key && key.codepoint == CTRL_KEY('q'))) {
ed->quit_confirm_pending = false;
Expand All @@ -219,6 +225,27 @@ void input_process_key(editor_t *ed, key_event_t key)
editor_undo(ed);
} else if (key.codepoint == CTRL_KEY('y')) {
editor_redo(ed);
} else if (key.codepoint == CTRL_KEY('c')) {
if (editor_has_selection(ed)) {
editor_copy(ed);
terminal_set_clipboard(ed->clipboard, ed->clipboard_len);
editor_set_status(ed, "Copied");
} else {
editor_set_status(ed, "Nothing selected");
}
} else if (key.codepoint == CTRL_KEY('x')) {
if (editor_has_selection(ed)) {
editor_cut(ed);
terminal_set_clipboard(ed->clipboard, ed->clipboard_len);
editor_set_status(ed, "Cut");
} else {
editor_set_status(ed, "Nothing selected");
}
} else if (key.codepoint == CTRL_KEY('v')) {
if (ed->clipboard != NULL) {
editor_paste(ed);
editor_set_status(ed, "Pasted");
}
}
return;
}
Expand Down
109 changes: 107 additions & 2 deletions src/terminal.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@
#include <termios.h>
#include <unistd.h>

#include "common.h"

static struct termios g_orig_termios;
static int g_raw_mode_active = 0;
static volatile sig_atomic_t g_resized = 0;

static char *g_paste_buf = NULL;
static size_t g_paste_len = 0;
static size_t g_paste_cap = 0;

static void handle_sigwinch(int signum)
{
(void)signum;
Expand All @@ -33,7 +39,7 @@ static void write_str(const char *s)
void terminal_disable_raw_mode(void)
{
if (g_raw_mode_active) {
write_str("\x1b[?1006l\x1b[?1002l");
write_str("\x1b[?2004l\x1b[?1006l\x1b[?1002l");
tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_termios);
g_raw_mode_active = 0;
}
Expand All @@ -60,7 +66,7 @@ void terminal_enable_raw_mode(void)
exit(EXIT_FAILURE);
}
g_raw_mode_active = 1;
write_str("\x1b[?1002h\x1b[?1006h");
write_str("\x1b[?1002h\x1b[?1006h\x1b[?2004h");

struct sigaction sa;
memset(&sa, 0, sizeof(sa));
Expand Down Expand Up @@ -250,6 +256,51 @@ static key_event_t read_sgr_mouse_sequence(void)
return ev;
}

static void paste_buf_append(char c)
{
if (g_paste_len + 1 > g_paste_cap) {
size_t new_cap = g_paste_cap == 0 ? 1024 : g_paste_cap * 2;
g_paste_buf = xrealloc(g_paste_buf, new_cap);
g_paste_cap = new_cap;
}
g_paste_buf[g_paste_len++] = c;
}

static key_event_t read_bracketed_paste(void)
{
static const char terminator[5] = {'[', '2', '0', '1', '~'};
g_paste_len = 0;

for (;;) {
char c;
if (read_raw_byte(&c) != 0) {
break;
}
if ((unsigned char)c != 0x1b) {
paste_buf_append(c);
continue;
}

char seq[5];
size_t got = 0;
for (; got < sizeof(seq); got++) {
if (try_read_byte(&seq[got], 30) != 0) {
break;
}
}
if (got == sizeof(seq) && memcmp(seq, terminator, sizeof(terminator)) == 0) {
break;
}

paste_buf_append(0x1b);
for (size_t i = 0; i < got; i++) {
paste_buf_append(seq[i]);
}
}

return (key_event_t){.type = KEY_PASTE, .paste_text = g_paste_buf, .paste_len = g_paste_len};
}

static key_event_t read_csi_rest(char first_byte)
{
char body[16];
Expand All @@ -269,6 +320,10 @@ static key_event_t read_csi_rest(char first_byte)
}
body[idx] = '\0';

if (strcmp(body, "200~") == 0) {
return read_bracketed_paste();
}

return decode_csi_body(body);
}

Expand Down Expand Up @@ -352,3 +407,53 @@ key_event_t terminal_read_key(void)
}
return decode_utf8_char(uc);
}

static const char k_base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

static size_t base64_encode(const unsigned char *data, size_t len, char *out)
{
size_t out_len = 0;
size_t i = 0;

for (; i + 3 <= len; i += 3) {
unsigned int n = ((unsigned int)data[i] << 16) | ((unsigned int)data[i + 1] << 8) |
(unsigned int)data[i + 2];
out[out_len++] = k_base64_chars[(n >> 18) & 0x3F];
out[out_len++] = k_base64_chars[(n >> 12) & 0x3F];
out[out_len++] = k_base64_chars[(n >> 6) & 0x3F];
out[out_len++] = k_base64_chars[n & 0x3F];
}

size_t rem = len - i;
if (rem == 1) {
unsigned int n = (unsigned int)data[i] << 16;
out[out_len++] = k_base64_chars[(n >> 18) & 0x3F];
out[out_len++] = k_base64_chars[(n >> 12) & 0x3F];
out[out_len++] = '=';
out[out_len++] = '=';
} else if (rem == 2) {
unsigned int n = ((unsigned int)data[i] << 16) | ((unsigned int)data[i + 1] << 8);
out[out_len++] = k_base64_chars[(n >> 18) & 0x3F];
out[out_len++] = k_base64_chars[(n >> 12) & 0x3F];
out[out_len++] = k_base64_chars[(n >> 6) & 0x3F];
out[out_len++] = '=';
}

return out_len;
}

void terminal_set_clipboard(const char *text, size_t len)
{
size_t cap = ((len + 2) / 3) * 4 + 1;
char *encoded = xmalloc(cap);
size_t encoded_len = base64_encode((const unsigned char *)text, len, encoded);

write_str("\x1b]52;c;");
ssize_t written = write(STDOUT_FILENO, encoded, encoded_len);
(void)written;
write_str("\x07");

free(encoded);
}
8 changes: 8 additions & 0 deletions src/terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef DAF_TERMINAL_H
#define DAF_TERMINAL_H

#include <stddef.h>

enum key_type {
KEY_NONE = 0,
KEY_CHAR,
Expand All @@ -22,6 +24,7 @@ enum key_type {
KEY_TAB,
KEY_ESCAPE,
KEY_MOUSE,
KEY_PASTE,
};

enum key_mod {
Expand All @@ -48,6 +51,9 @@ typedef struct {
enum mouse_button mouse_button;
int mouse_row;
int mouse_col;

const char *paste_text;
size_t paste_len;
} key_event_t;

void terminal_enable_raw_mode(void);
Expand All @@ -56,4 +62,6 @@ int terminal_get_window_size(int *rows, int *cols);
int terminal_consume_resize(void);
key_event_t terminal_read_key(void);

void terminal_set_clipboard(const char *text, size_t len);

#endif /* DAF_TERMINAL_H */
2 changes: 1 addition & 1 deletion tests/meson.build
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test_names = ['buffer', 'search', 'encoding', 'filetype', 'editor', 'undo']
test_names = ['buffer', 'search', 'encoding', 'filetype', 'editor', 'undo', 'clipboard']

foreach name : test_names
test_exe = executable(
Expand Down
Loading
Loading