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)

45fffdf10b

Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
Jan Edmund Lazo
2025-09-29 10:02:01 -04:00
committed by GitHub
parent 9da316fcc4
commit f2df98e849
6 changed files with 303 additions and 274 deletions

285
src/nvim/clipboard.c Normal file
View File

@@ -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, &regname, 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, &regtype, 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;
}
}

7
src/nvim/clipboard.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include <stdbool.h>
#include "nvim/register_defs.h"
#include "clipboard.h.generated.h"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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, &regtype, 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, &regname, 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!