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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,26 @@ The variable `vterm-use-vterm-prompt-detection-method` determines whether to use
the vterm prompt tracking, if false it use the regexp in
`vterm-copy-prompt-regexp` to search for the prompt.

## `vterm-enable-manipulate-selection-data-by-osc52`

Vterm support copy text to emacs kill ring and system clipboard by using OSC 52.
See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html for more info about OSC 52.
For example: send 'blabla' to kill ring: printf "\033]52;c;$(printf "%s" "blabla" | base64)\a"

tmux can share its copy buffer to terminals bysupporting osc52(like iterm2 xterm),
you can enable this feature for tmux by :
set -g set-clipboard on #osc 52 copy paste share with iterm
set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007'
set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007'

The clipboard querying/clearing functionality offered by OSC 52 is not implemented here,
And for security reason, this feature is disabled by default."

This feature need the new way of handling strings with a struct `VTermStringFragment`
in libvterm. You'd better compile emacs-libvterm with `cmake -DUSE_SYSTEM_LIBVTERM=no ..`.
If you don't do that, when the content you want to copied is too long, it would be truncated
by bug of libvterm.

## `vterm-buffer-name-string`

When `vterm-buffer-name-string` is not nil, vterm renames automatically its own
Expand Down
7 changes: 7 additions & 0 deletions elisp.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ emacs_value Fvterm_invalidate;
emacs_value Feq;
emacs_value Fvterm_get_color;
emacs_value Fvterm_eval;
emacs_value Fvterm_selection;

/* Set the function cell of the symbol named NAME to SFUN using
the 'fset' function. */
Expand Down Expand Up @@ -194,3 +195,9 @@ void vterm_invalidate(emacs_env *env) {
emacs_value vterm_eval(emacs_env *env, emacs_value string) {
return env->funcall(env, Fvterm_eval, 1, (emacs_value[]){string});
}

emacs_value vterm_selection(emacs_env *env, emacs_value selection_target,
emacs_value selection_data) {
return env->funcall(env, Fvterm_selection, 2,
(emacs_value[]){selection_target, selection_data});
}
3 changes: 3 additions & 0 deletions elisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ extern emacs_value Fvterm_invalidate;
extern emacs_value Feq;
extern emacs_value Fvterm_get_color;
extern emacs_value Fvterm_eval;
extern emacs_value Fvterm_selection;

// Utils
void bind_function(emacs_env *env, const char *name, emacs_value Sfun);
Expand Down Expand Up @@ -90,5 +91,7 @@ void set_directory(emacs_env *env, emacs_value string);
void vterm_invalidate(emacs_env *env);
emacs_value vterm_get_color(emacs_env *env, int index);
emacs_value vterm_eval(emacs_env *env, emacs_value string);
emacs_value vterm_selection(emacs_env *env, emacs_value selection_target,
emacs_value selection_data);

#endif /* ELISP_H */
63 changes: 61 additions & 2 deletions vterm-module.c
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,15 @@ static void term_redraw(Term *term, emacs_env *env) {
free(term->elisp_code);
term->elisp_code = NULL;
}
if (term->selection_data) {
emacs_value selection_target = env->make_string(
env, &term->selection_target[0], strlen(&term->selection_target[0]));
emacs_value selection_data = env->make_string(env, term->selection_data,
strlen(term->selection_data));
vterm_selection(env, selection_target, selection_data);
free(term->selection_data);
term->selection_data = NULL;
}

term->is_invalidated = false;
}
Expand Down Expand Up @@ -938,6 +947,10 @@ void term_finalize(void *object) {
free(term->cmd_buffer);
term->cmd_buffer = NULL;
}
if (term->selection_data) {
free(term->selection_data);
term->selection_data = NULL;
}

for (int i = 0; i < term->lines_len; i++) {
if (term->lines[i] != NULL) {
Expand Down Expand Up @@ -995,14 +1008,48 @@ static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) {
}
return 0;
}
static int handle_osc_cmd_52(Term *term, char *buffer) {
/* OSC 52 ; Pc ; Pd BEL */
/* Manipulate Selection Data */
/* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */
/* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */

for (int i = 0; i < SELECTION_TARGET_MAX; i++) { /* reset Pc */
term->selection_target[i] = 0;
}
int selection_target_idx = 0;
size_t cmdlen = strlen(buffer);

for (int i = 0; i < cmdlen; i++) {
/* OSC 52 ; Pc ; Pd BEL */
if (buffer[i] == ';') { /* find the second ";" */
term->selection_data = malloc(cmdlen - i);
strcpy(term->selection_data, &buffer[i + 1]);
break;
}
if (selection_target_idx < SELECTION_TARGET_MAX) {
/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
/* for clipboard, primary, secondary, select, or cut buffers 0 through 7
* respectively */
term->selection_target[selection_target_idx] = buffer[i];
selection_target_idx++;
} else { /* len of Pc should not >12 just ignore this cmd,am I wrong? */
return 0;
}
}
return 1;
}
static int handle_osc_cmd(Term *term, int cmd, char *buffer) {
if (cmd == 51) {
char subCmd = '0';
if (strlen(buffer) == 0) {
return 0;
}
subCmd = buffer[0];
/* ++ skip the subcmd char */
return handle_osc_cmd_51(term, subCmd, ++buffer);
} else if (cmd == 52) {
return handle_osc_cmd_52(term, buffer);
}
return 0;
}
Expand All @@ -1019,10 +1066,13 @@ static int osc_callback(const char *command, size_t cmdlen, void *user) {
} else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' &&
buffer[2] == ';' && buffer[3] == 'E') {
return handle_osc_cmd_51(term, 'E', &buffer[4]);
} else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '2' &&
buffer[2] == ';') {
/* OSC 52 ; Pc ; Pd BEL */
return handle_osc_cmd_52(term, &buffer[3]);
}
return 0;
}

static VTermParserCallbacks parser_callbacks = {
.text = NULL,
.control = NULL,
Expand All @@ -1042,10 +1092,16 @@ static int osc_callback(int cmd, VTermStringFragment frag, void *user) {
/* "51;E" executes elisp code */
/* The elisp code is executed in term_redraw */

/* "52;[cpqs01234567];data" Manipulate Selection Data */
/* I think libvterm has bug ,sometimes when the data is long enough ,the final
* fragment is missed */
/* printf "\033]52;c;$(printf "%s" $(ruby -e 'print "x"*999999')|base64)\a"
*/

Term *term = (Term *)user;

if (frag.initial) {
/* drop old fragment,because this is a initial fragment */
/* drop old fragment,because this is a initial fragment */
if (term->cmd_buffer) {
free(term->cmd_buffer);
term->cmd_buffer = NULL;
Expand Down Expand Up @@ -1128,6 +1184,7 @@ emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
term->directory_changed = false;
term->elisp_code = NULL;
term->elisp_code_changed = false;
term->selection_data = NULL;

term->cmd_buffer = NULL;

Expand Down Expand Up @@ -1337,6 +1394,8 @@ int emacs_module_init(struct emacs_runtime *ert) {
Fvterm_get_color =
env->make_global_ref(env, env->intern(env, "vterm--get-color"));
Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval"));
Fvterm_selection =
env->make_global_ref(env, env->intern(env, "vterm--selection"));

// Exported functions
emacs_value fun;
Expand Down
9 changes: 9 additions & 0 deletions vterm-module.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ enum {
VTERM_PROP_CURSOR_NOT_VISIBLE = 5,
};

/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
#define SELECTION_TARGET_MAX 12

typedef struct Cursor {
int row, col;
int cursor_type;
Expand Down Expand Up @@ -75,6 +79,11 @@ typedef struct Term {
char *elisp_code;
bool elisp_code_changed;

/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
char selection_target[SELECTION_TARGET_MAX];
char *selection_data;

/* the size of dirs almost = window height, value = directory of that line */
LineInfo **lines;
int lines_len;
Expand Down
41 changes: 41 additions & 0 deletions vterm.el
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,22 @@ information on the how to configure the shell."
:type 'string
:group 'vterm)

(defcustom vterm-enable-manipulate-selection-data-by-osc52 nil
"Support OSC 52 MANIPULATE SELECTION DATA.

Support copy text to emacs kill ring and system clipboard by using OSC 52.
For example: send base64 encoded 'foo' to kill ring: echo -en '\e]52;c;Zm9v\a',
tmux can share its copy buffer to terminals by supporting osc52(like iterm2 xterm),
you can enable this feature for tmux by :
set -g set-clipboard on #osc 52 copy paste share with iterm
set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007'
set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007'

The clipboard querying/clearing functionality offered by OSC 52 is not implemented here,
And for security reason, this feature is disabled by default."
:type 'boolean
:group 'vterm)

;; TODO: Improve doc string, it should not point to the readme but it should
;; be self-contained.
(defcustom vterm-eval-cmds '(("find-file" find-file)
Expand Down Expand Up @@ -922,6 +938,31 @@ Argument BUFFER the terminal buffer."
(when (cl-member (selected-window) windows :test #'eq)
(set-window-hscroll (selected-window) 0))))))))

(defun vterm--selection (targets data)
"OSC 52 Manipulate Selection Data.
Search Manipulate Selection Data in
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ."
(when vterm-enable-manipulate-selection-data-by-osc52
(unless (or (string-equal data "?")
(string-empty-p data))
(let ((decoded-data (decode-coding-string
(base64-decode-string data) locale-coding-system))
(select-enable-clipboard select-enable-clipboard)
(select-enable-primary select-enable-primary))
;; https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
;; c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7
;; clipboard, primary, secondary, select, or cut buffers 0 through 7
(unless (string-empty-p targets)
(setq select-enable-clipboard nil)
(setq select-enable-primary nil))
(when (cl-find ?c targets)
(setq select-enable-clipboard t))
(when (cl-find ?p targets)
(setq select-enable-primary t))

(kill-new decoded-data)
(message "kill-ring is updated by vterm OSC 52(Manipulate Selection Data)")))))

;;; Entry Points

;;;###autoload
Expand Down