commit f2df98e849b7cea2c2c0579cba70dd9e8d07a656
parent 9da316fcc45c34ec47e95f50e28865c3ec3ba15b
Author: Jan Edmund Lazo <jan.lazo@mail.utoronto.ca>
Date: Mon, 29 Sep 2025 10:02:01 -0400
vim-patch:8.2.0443: clipboard code is spread out #35949
Problem: Clipboard code is spread out.
Solution: Move clipboard code to its own file. (Yegappan Lakshmanan,
closes vim/vim#5827)
https://github.com/vim/vim/commit/45fffdf10b7cb6e59794e76e9b8a2930fcb4b192
Co-authored-by: Bram Moolenaar <Bram@vim.org>
Diffstat:
6 files changed, 303 insertions(+), 274 deletions(-)
diff --git a/src/nvim/clipboard.c b/src/nvim/clipboard.c
@@ -0,0 +1,285 @@
+// clipboard.c: Functions to handle the clipboard
+
+#include <assert.h>
+
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/clipboard.h"
+#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
+#include "nvim/option_vars.h"
+#include "nvim/register.h"
+
+#include "clipboard.c.generated.h"
+
+// for behavior between start_batch_changes() and end_batch_changes())
+static int batch_change_count = 0; // inside a script
+static bool clipboard_delay_update = false; // delay clipboard update
+static bool clipboard_needs_update = false; // clipboard was updated
+static bool clipboard_didwarn = false;
+
+/// Determine if register `*name` should be used as a clipboard.
+/// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if
+/// `clipboard=unnamed[plus]` is set.
+///
+/// @param name The name of register, or `NUL` if unnamed.
+/// @param quiet Suppress error messages
+/// @param writing if we're setting the contents of the clipboard
+///
+/// @returns the yankreg that should be written into, or `NULL`
+/// if the register isn't a clipboard or provider isn't available.
+yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
+{
+#define MSG_NO_CLIP "clipboard: No provider. " \
+ "Try \":checkhealth\" or \":h clipboard\"."
+
+ yankreg_T *target = NULL;
+ bool explicit_cb_reg = (*name == '*' || *name == '+');
+ bool implicit_cb_reg = (*name == NUL) && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus));
+ if (!explicit_cb_reg && !implicit_cb_reg) {
+ goto end;
+ }
+
+ if (!eval_has_provider("clipboard", false)) {
+ if (batch_change_count <= 1 && !quiet
+ && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) {
+ clipboard_didwarn = true;
+ // Do NOT error (emsg()) here--if it interrupts :redir we get into
+ // a weird state, stuck in "redirect mode".
+ msg(MSG_NO_CLIP, 0);
+ }
+ // ... else, be silent (don't flood during :while, :redir, etc.).
+ goto end;
+ }
+
+ if (explicit_cb_reg) {
+ target = get_y_register(*name == '*' ? STAR_REGISTER : PLUS_REGISTER);
+ if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) {
+ clipboard_needs_update = false;
+ }
+ goto end;
+ } else { // unnamed register: "implicit" clipboard
+ if (writing && clipboard_delay_update) {
+ // For "set" (copy), defer the clipboard call.
+ clipboard_needs_update = true;
+ goto end;
+ } else if (!writing && clipboard_needs_update) {
+ // For "get" (paste), use the internal value.
+ goto end;
+ }
+
+ if (cb_flags & kOptCbFlagUnnamedplus) {
+ *name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+';
+ target = get_y_register(PLUS_REGISTER);
+ } else {
+ *name = '*';
+ target = get_y_register(STAR_REGISTER);
+ }
+ goto end;
+ }
+
+end:
+ return target;
+}
+
+bool get_clipboard(int name, yankreg_T **target, bool quiet)
+{
+ // show message on error
+ bool errmsg = true;
+
+ yankreg_T *reg = adjust_clipboard_name(&name, quiet, false);
+ if (reg == NULL) {
+ return false;
+ }
+ free_register(reg);
+
+ list_T *const args = tv_list_alloc(1);
+ const char regname = (char)name;
+ tv_list_append_string(args, ®name, 1);
+
+ typval_T result = eval_call_provider("clipboard", "get", args, false);
+
+ if (result.v_type != VAR_LIST) {
+ if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
+ // failure has already been indicated by provider
+ errmsg = false;
+ }
+ goto err;
+ }
+
+ list_T *res = result.vval.v_list;
+ list_T *lines = NULL;
+ if (tv_list_len(res) == 2
+ && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) {
+ lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list;
+ if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) {
+ goto err;
+ }
+ char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string;
+ if (regtype == NULL || strlen(regtype) > 1) {
+ goto err;
+ }
+ switch (regtype[0]) {
+ case 0:
+ reg->y_type = kMTUnknown;
+ break;
+ case 'v':
+ case 'c':
+ reg->y_type = kMTCharWise;
+ break;
+ case 'V':
+ case 'l':
+ reg->y_type = kMTLineWise;
+ break;
+ case 'b':
+ case Ctrl_V:
+ reg->y_type = kMTBlockWise;
+ break;
+ default:
+ goto err;
+ }
+ } else {
+ lines = res;
+ // provider did not specify regtype, calculate it below
+ reg->y_type = kMTUnknown;
+ }
+
+ reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(String));
+ reg->y_size = (size_t)tv_list_len(lines);
+ reg->additional_data = NULL;
+ reg->timestamp = 0;
+ // Timestamp is not saved for clipboard registers because clipboard registers
+ // are not saved in the ShaDa file.
+
+ size_t tv_idx = 0;
+ TV_LIST_ITER_CONST(lines, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
+ goto err;
+ }
+ const char *s = TV_LIST_ITEM_TV(li)->vval.v_string;
+ reg->y_array[tv_idx++] = cstr_to_string(s != NULL ? s : "");
+ });
+
+ if (reg->y_size > 0 && reg->y_array[reg->y_size - 1].size == 0) {
+ // a known-to-be charwise yank might have a final linebreak
+ // but otherwise there is no line after the final newline
+ if (reg->y_type != kMTCharWise) {
+ xfree(reg->y_array[reg->y_size - 1].data);
+ reg->y_size--;
+ if (reg->y_type == kMTUnknown) {
+ reg->y_type = kMTLineWise;
+ }
+ }
+ } else {
+ if (reg->y_type == kMTUnknown) {
+ reg->y_type = kMTCharWise;
+ }
+ }
+
+ update_yankreg_width(reg);
+
+ *target = reg;
+ return true;
+
+err:
+ if (reg->y_array) {
+ for (size_t i = 0; i < reg->y_size; i++) {
+ xfree(reg->y_array[i].data);
+ }
+ xfree(reg->y_array);
+ }
+ reg->y_array = NULL;
+ reg->y_size = 0;
+ reg->additional_data = NULL;
+ reg->timestamp = 0;
+ if (errmsg) {
+ emsg("clipboard: provider returned invalid data");
+ }
+ *target = reg;
+ return false;
+}
+
+void set_clipboard(int name, yankreg_T *reg)
+{
+ if (!adjust_clipboard_name(&name, false, true)) {
+ return;
+ }
+
+ list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
+
+ for (size_t i = 0; i < reg->y_size; i++) {
+ tv_list_append_string(lines, reg->y_array[i].data, -1);
+ }
+
+ char regtype;
+ switch (reg->y_type) {
+ case kMTLineWise:
+ regtype = 'V';
+ tv_list_append_string(lines, NULL, 0);
+ break;
+ case kMTCharWise:
+ regtype = 'v';
+ break;
+ case kMTBlockWise:
+ regtype = 'b';
+ tv_list_append_string(lines, NULL, 0);
+ break;
+ case kMTUnknown:
+ abort();
+ }
+
+ list_T *args = tv_list_alloc(3);
+ tv_list_append_list(args, lines);
+ tv_list_append_string(args, ®type, 1);
+ tv_list_append_string(args, ((char[]) { (char)name }), 1);
+
+ eval_call_provider("clipboard", "set", args, true);
+}
+
+/// Avoid slow things (clipboard) during batch operations (while/for-loops).
+void start_batch_changes(void)
+{
+ if (++batch_change_count > 1) {
+ return;
+ }
+ clipboard_delay_update = true;
+}
+
+/// Counterpart to start_batch_changes().
+void end_batch_changes(void)
+{
+ if (--batch_change_count > 0) {
+ // recursive
+ return;
+ }
+ clipboard_delay_update = false;
+ if (clipboard_needs_update) {
+ // must be before, as set_clipboard will invoke
+ // start/end_batch_changes recursively
+ clipboard_needs_update = false;
+ // unnamed ("implicit" clipboard)
+ set_clipboard(NUL, get_y_previous());
+ }
+}
+
+int save_batch_count(void)
+{
+ int save_count = batch_change_count;
+ batch_change_count = 0;
+ clipboard_delay_update = false;
+ if (clipboard_needs_update) {
+ clipboard_needs_update = false;
+ // unnamed ("implicit" clipboard)
+ set_clipboard(NUL, get_y_previous());
+ }
+ return save_count;
+}
+
+void restore_batch_count(int save_count)
+{
+ assert(batch_change_count == 0);
+ batch_change_count = save_count;
+ if (batch_change_count > 0) {
+ clipboard_delay_update = true;
+ }
+}
diff --git a/src/nvim/clipboard.h b/src/nvim/clipboard.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/register_defs.h"
+
+#include "clipboard.h.generated.h"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
@@ -25,6 +25,7 @@
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/clipboard.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
@@ -89,7 +90,6 @@
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
-#include "nvim/register.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
#include "nvim/search.h"
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
@@ -20,6 +20,7 @@
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/clipboard.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/cmdhist.h"
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
@@ -22,6 +22,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/clipboard.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
diff --git a/src/nvim/register.c b/src/nvim/register.c
@@ -7,6 +7,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/clipboard.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
@@ -54,12 +55,6 @@ static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
static yankreg_T *y_previous = NULL; // ptr to last written yankreg
-// for behavior between start_batch_changes() and end_batch_changes())
-static int batch_change_count = 0; // inside a script
-static bool clipboard_delay_update = false; // delay clipboard update
-static bool clipboard_needs_update = false; // clipboard was updated
-static bool clipboard_didwarn = false;
-
static const char e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines[]
= N_("E883: Search pattern and expression register may not contain two or more lines");
@@ -74,6 +69,12 @@ yankreg_T *get_y_register(int reg)
return &y_regs[reg];
}
+yankreg_T *get_y_previous(void)
+ FUNC_ATTR_PURE
+{
+ return y_previous;
+}
+
/// Get an expression for the "\"=expr1" or "CTRL-R =expr1"
///
/// @return '=' when OK, NUL otherwise.
@@ -170,91 +171,6 @@ int get_default_register_name(void)
return name;
}
-void set_clipboard(int name, yankreg_T *reg)
-{
- if (!adjust_clipboard_name(&name, false, true)) {
- return;
- }
-
- list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
-
- for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(lines, reg->y_array[i].data, -1);
- }
-
- char regtype;
- switch (reg->y_type) {
- case kMTLineWise:
- regtype = 'V';
- tv_list_append_string(lines, NULL, 0);
- break;
- case kMTCharWise:
- regtype = 'v';
- break;
- case kMTBlockWise:
- regtype = 'b';
- tv_list_append_string(lines, NULL, 0);
- break;
- case kMTUnknown:
- abort();
- }
-
- list_T *args = tv_list_alloc(3);
- tv_list_append_list(args, lines);
- tv_list_append_string(args, ®type, 1);
- tv_list_append_string(args, ((char[]) { (char)name }), 1);
-
- eval_call_provider("clipboard", "set", args, true);
-}
-
-/// Avoid slow things (clipboard) during batch operations (while/for-loops).
-void start_batch_changes(void)
-{
- if (++batch_change_count > 1) {
- return;
- }
- clipboard_delay_update = true;
-}
-
-/// Counterpart to start_batch_changes().
-void end_batch_changes(void)
-{
- if (--batch_change_count > 0) {
- // recursive
- return;
- }
- clipboard_delay_update = false;
- if (clipboard_needs_update) {
- // must be before, as set_clipboard will invoke
- // start/end_batch_changes recursively
- clipboard_needs_update = false;
- // unnamed ("implicit" clipboard)
- set_clipboard(NUL, y_previous);
- }
-}
-
-int save_batch_count(void)
-{
- int save_count = batch_change_count;
- batch_change_count = 0;
- clipboard_delay_update = false;
- if (clipboard_needs_update) {
- clipboard_needs_update = false;
- // unnamed ("implicit" clipboard)
- set_clipboard(NUL, y_previous);
- }
- return save_count;
-}
-
-void restore_batch_count(int save_count)
-{
- assert(batch_change_count == 0);
- batch_change_count = save_count;
- if (batch_change_count > 0) {
- clipboard_delay_update = true;
- }
-}
-
/// Iterate over registers `regs`.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
@@ -365,73 +281,9 @@ bool op_reg_set_previous(const char name)
return true;
}
-/// Determine if register `*name` should be used as a clipboard.
-/// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if
-/// `clipboard=unnamed[plus]` is set.
-///
-/// @param name The name of register, or `NUL` if unnamed.
-/// @param quiet Suppress error messages
-/// @param writing if we're setting the contents of the clipboard
-///
-/// @returns the yankreg that should be written into, or `NULL`
-/// if the register isn't a clipboard or provider isn't available.
-static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
-{
-#define MSG_NO_CLIP "clipboard: No provider. " \
- "Try \":checkhealth\" or \":h clipboard\"."
-
- yankreg_T *target = NULL;
- bool explicit_cb_reg = (*name == '*' || *name == '+');
- bool implicit_cb_reg = (*name == NUL) && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus));
- if (!explicit_cb_reg && !implicit_cb_reg) {
- goto end;
- }
-
- if (!eval_has_provider("clipboard", false)) {
- if (batch_change_count <= 1 && !quiet
- && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) {
- clipboard_didwarn = true;
- // Do NOT error (emsg()) here--if it interrupts :redir we get into
- // a weird state, stuck in "redirect mode".
- msg(MSG_NO_CLIP, 0);
- }
- // ... else, be silent (don't flood during :while, :redir, etc.).
- goto end;
- }
-
- if (explicit_cb_reg) {
- target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
- if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) {
- clipboard_needs_update = false;
- }
- goto end;
- } else { // unnamed register: "implicit" clipboard
- if (writing && clipboard_delay_update) {
- // For "set" (copy), defer the clipboard call.
- clipboard_needs_update = true;
- goto end;
- } else if (!writing && clipboard_needs_update) {
- // For "get" (paste), use the internal value.
- goto end;
- }
-
- if (cb_flags & kOptCbFlagUnnamedplus) {
- *name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+';
- target = &y_regs[PLUS_REGISTER];
- } else {
- *name = '*';
- target = &y_regs[STAR_REGISTER];
- }
- goto end;
- }
-
-end:
- return target;
-}
-
/// Updates the "y_width" of a blockwise register based on its contents.
/// Do nothing on a non-blockwise register.
-static void update_yankreg_width(yankreg_T *reg)
+void update_yankreg_width(yankreg_T *reg)
{
if (reg->y_type == kMTBlockWise) {
size_t maxlen = 0;
@@ -444,123 +296,6 @@ static void update_yankreg_width(yankreg_T *reg)
}
}
-static bool get_clipboard(int name, yankreg_T **target, bool quiet)
-{
- // show message on error
- bool errmsg = true;
-
- yankreg_T *reg = adjust_clipboard_name(&name, quiet, false);
- if (reg == NULL) {
- return false;
- }
- free_register(reg);
-
- list_T *const args = tv_list_alloc(1);
- const char regname = (char)name;
- tv_list_append_string(args, ®name, 1);
-
- typval_T result = eval_call_provider("clipboard", "get", args, false);
-
- if (result.v_type != VAR_LIST) {
- if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
- // failure has already been indicated by provider
- errmsg = false;
- }
- goto err;
- }
-
- list_T *res = result.vval.v_list;
- list_T *lines = NULL;
- if (tv_list_len(res) == 2
- && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) {
- lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list;
- if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) {
- goto err;
- }
- char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string;
- if (regtype == NULL || strlen(regtype) > 1) {
- goto err;
- }
- switch (regtype[0]) {
- case 0:
- reg->y_type = kMTUnknown;
- break;
- case 'v':
- case 'c':
- reg->y_type = kMTCharWise;
- break;
- case 'V':
- case 'l':
- reg->y_type = kMTLineWise;
- break;
- case 'b':
- case Ctrl_V:
- reg->y_type = kMTBlockWise;
- break;
- default:
- goto err;
- }
- } else {
- lines = res;
- // provider did not specify regtype, calculate it below
- reg->y_type = kMTUnknown;
- }
-
- reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(String));
- reg->y_size = (size_t)tv_list_len(lines);
- reg->additional_data = NULL;
- reg->timestamp = 0;
- // Timestamp is not saved for clipboard registers because clipboard registers
- // are not saved in the ShaDa file.
-
- size_t tv_idx = 0;
- TV_LIST_ITER_CONST(lines, li, {
- if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
- goto err;
- }
- const char *s = TV_LIST_ITEM_TV(li)->vval.v_string;
- reg->y_array[tv_idx++] = cstr_to_string(s != NULL ? s : "");
- });
-
- if (reg->y_size > 0 && reg->y_array[reg->y_size - 1].size == 0) {
- // a known-to-be charwise yank might have a final linebreak
- // but otherwise there is no line after the final newline
- if (reg->y_type != kMTCharWise) {
- xfree(reg->y_array[reg->y_size - 1].data);
- reg->y_size--;
- if (reg->y_type == kMTUnknown) {
- reg->y_type = kMTLineWise;
- }
- }
- } else {
- if (reg->y_type == kMTUnknown) {
- reg->y_type = kMTCharWise;
- }
- }
-
- update_yankreg_width(reg);
-
- *target = reg;
- return true;
-
-err:
- if (reg->y_array) {
- for (size_t i = 0; i < reg->y_size; i++) {
- xfree(reg->y_array[i].data);
- }
- xfree(reg->y_array);
- }
- reg->y_array = NULL;
- reg->y_size = 0;
- reg->additional_data = NULL;
- reg->timestamp = 0;
- if (errmsg) {
- emsg("clipboard: provider returned invalid data");
- }
- *target = reg;
- return false;
-}
-
/// @return yankreg_T to use, according to the value of `regname`.
/// Cannot handle the '_' (black hole) register.
/// Must only be called with a valid register name!