neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

clipboard.c (7923B)


      1 // clipboard.c: Functions to handle the clipboard
      2 
      3 #include <assert.h>
      4 
      5 #include "nvim/api/private/helpers.h"
      6 #include "nvim/ascii_defs.h"
      7 #include "nvim/clipboard.h"
      8 #include "nvim/eval.h"
      9 #include "nvim/eval/typval.h"
     10 #include "nvim/option_vars.h"
     11 #include "nvim/register.h"
     12 
     13 #include "clipboard.c.generated.h"
     14 
     15 // for behavior between start_batch_changes() and end_batch_changes())
     16 static int batch_change_count = 0;           // inside a script
     17 static bool clipboard_delay_update = false;  // delay clipboard update
     18 static bool clipboard_needs_update = false;  // clipboard was updated
     19 static bool clipboard_didwarn = false;
     20 
     21 /// Determine if register `*name` should be used as a clipboard.
     22 /// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if
     23 /// `clipboard=unnamed[plus]` is set.
     24 ///
     25 /// @param name The name of register, or `NUL` if unnamed.
     26 /// @param quiet Suppress error messages
     27 /// @param writing if we're setting the contents of the clipboard
     28 ///
     29 /// @returns the yankreg that should be written into, or `NULL`
     30 /// if the register isn't a clipboard or provider isn't available.
     31 yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
     32 {
     33 #define MSG_NO_CLIP "clipboard: No provider. " \
     34  "Try \":checkhealth\" or \":h clipboard\"."
     35 
     36  yankreg_T *target = NULL;
     37  bool explicit_cb_reg = (*name == '*' || *name == '+');
     38  bool implicit_cb_reg = (*name == NUL) && (cb_flags & (kOptCbFlagUnnamed | kOptCbFlagUnnamedplus));
     39  if (!explicit_cb_reg && !implicit_cb_reg) {
     40    goto end;
     41  }
     42 
     43  if (!eval_has_provider("clipboard", false)) {
     44    if (batch_change_count <= 1 && !quiet
     45        && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) {
     46      clipboard_didwarn = true;
     47      // Do NOT error (emsg()) here--if it interrupts :redir we get into
     48      // a weird state, stuck in "redirect mode".
     49      msg(MSG_NO_CLIP, 0);
     50    }
     51    // ... else, be silent (don't flood during :while, :redir, etc.).
     52    goto end;
     53  }
     54 
     55  if (explicit_cb_reg) {
     56    target = get_y_register(*name == '*' ? STAR_REGISTER : PLUS_REGISTER);
     57    if (writing && (cb_flags & (*name == '*' ? kOptCbFlagUnnamed : kOptCbFlagUnnamedplus))) {
     58      clipboard_needs_update = false;
     59    }
     60    goto end;
     61  } else {  // unnamed register: "implicit" clipboard
     62    if (writing && clipboard_delay_update) {
     63      // For "set" (copy), defer the clipboard call.
     64      clipboard_needs_update = true;
     65      goto end;
     66    } else if (!writing && clipboard_needs_update) {
     67      // For "get" (paste), use the internal value.
     68      goto end;
     69    }
     70 
     71    if (cb_flags & kOptCbFlagUnnamedplus) {
     72      *name = (cb_flags & kOptCbFlagUnnamed && writing) ? '"' : '+';
     73      target = get_y_register(PLUS_REGISTER);
     74    } else {
     75      *name = '*';
     76      target = get_y_register(STAR_REGISTER);
     77    }
     78    goto end;
     79  }
     80 
     81 end:
     82  return target;
     83 }
     84 
     85 bool get_clipboard(int name, yankreg_T **target, bool quiet)
     86 {
     87  // show message on error
     88  bool errmsg = true;
     89 
     90  yankreg_T *reg = adjust_clipboard_name(&name, quiet, false);
     91  if (reg == NULL) {
     92    return false;
     93  }
     94  free_register(reg);
     95 
     96  list_T *const args = tv_list_alloc(1);
     97  const char regname = (char)name;
     98  tv_list_append_string(args, &regname, 1);
     99 
    100  typval_T result = eval_call_provider("clipboard", "get", args, false);
    101 
    102  if (result.v_type != VAR_LIST) {
    103    if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
    104      // failure has already been indicated by provider
    105      errmsg = false;
    106    }
    107    goto err;
    108  }
    109 
    110  list_T *res = result.vval.v_list;
    111  list_T *lines = NULL;
    112  if (tv_list_len(res) == 2
    113      && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) {
    114    lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list;
    115    if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) {
    116      goto err;
    117    }
    118    char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string;
    119    if (regtype == NULL || strlen(regtype) > 1) {
    120      goto err;
    121    }
    122    switch (regtype[0]) {
    123    case 0:
    124      reg->y_type = kMTUnknown;
    125      break;
    126    case 'v':
    127    case 'c':
    128      reg->y_type = kMTCharWise;
    129      break;
    130    case 'V':
    131    case 'l':
    132      reg->y_type = kMTLineWise;
    133      break;
    134    case 'b':
    135    case Ctrl_V:
    136      reg->y_type = kMTBlockWise;
    137      break;
    138    default:
    139      goto err;
    140    }
    141  } else {
    142    lines = res;
    143    // provider did not specify regtype, calculate it below
    144    reg->y_type = kMTUnknown;
    145  }
    146 
    147  reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(String));
    148  reg->y_size = (size_t)tv_list_len(lines);
    149  reg->y_width = 0;  // Will be updated by update_yankreg_width() below.
    150  reg->additional_data = NULL;
    151  reg->timestamp = 0;
    152  // Timestamp is not saved for clipboard registers because clipboard registers
    153  // are not saved in the ShaDa file.
    154 
    155  size_t tv_idx = 0;
    156  TV_LIST_ITER_CONST(lines, li, {
    157    if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
    158      goto err;
    159    }
    160    const char *s = TV_LIST_ITEM_TV(li)->vval.v_string;
    161    reg->y_array[tv_idx++] = cstr_to_string(s != NULL ? s : "");
    162  });
    163 
    164  if (reg->y_size > 0 && reg->y_array[reg->y_size - 1].size == 0) {
    165    // a known-to-be charwise yank might have a final linebreak
    166    // but otherwise there is no line after the final newline
    167    if (reg->y_type != kMTCharWise) {
    168      xfree(reg->y_array[reg->y_size - 1].data);
    169      reg->y_size--;
    170      if (reg->y_type == kMTUnknown) {
    171        reg->y_type = kMTLineWise;
    172      }
    173    }
    174  } else {
    175    if (reg->y_type == kMTUnknown) {
    176      reg->y_type = kMTCharWise;
    177    }
    178  }
    179 
    180  update_yankreg_width(reg);
    181 
    182  *target = reg;
    183  return true;
    184 
    185 err:
    186  if (reg->y_array) {
    187    for (size_t i = 0; i < reg->y_size; i++) {
    188      xfree(reg->y_array[i].data);
    189    }
    190    xfree(reg->y_array);
    191  }
    192  reg->y_array = NULL;
    193  reg->y_size = 0;
    194  reg->additional_data = NULL;
    195  reg->timestamp = 0;
    196  if (errmsg) {
    197    emsg("clipboard: provider returned invalid data");
    198  }
    199  *target = reg;
    200  return false;
    201 }
    202 
    203 void set_clipboard(int name, yankreg_T *reg)
    204 {
    205  if (!adjust_clipboard_name(&name, false, true)) {
    206    return;
    207  }
    208 
    209  list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
    210 
    211  for (size_t i = 0; i < reg->y_size; i++) {
    212    tv_list_append_string(lines, reg->y_array[i].data, (int)reg->y_array[i].size);
    213  }
    214 
    215  char regtype;
    216  switch (reg->y_type) {
    217  case kMTLineWise:
    218    regtype = 'V';
    219    tv_list_append_string(lines, NULL, 0);
    220    break;
    221  case kMTCharWise:
    222    regtype = 'v';
    223    break;
    224  case kMTBlockWise:
    225    regtype = 'b';
    226    tv_list_append_string(lines, NULL, 0);
    227    break;
    228  case kMTUnknown:
    229    abort();
    230  }
    231 
    232  list_T *args = tv_list_alloc(3);
    233  tv_list_append_list(args, lines);
    234  tv_list_append_string(args, &regtype, 1);
    235  tv_list_append_string(args, ((char[]) { (char)name }), 1);
    236 
    237  eval_call_provider("clipboard", "set", args, true);
    238 }
    239 
    240 /// Avoid slow things (clipboard) during batch operations (while/for-loops).
    241 void start_batch_changes(void)
    242 {
    243  if (++batch_change_count > 1) {
    244    return;
    245  }
    246  clipboard_delay_update = true;
    247 }
    248 
    249 /// Counterpart to start_batch_changes().
    250 void end_batch_changes(void)
    251 {
    252  if (--batch_change_count > 0) {
    253    // recursive
    254    return;
    255  }
    256  clipboard_delay_update = false;
    257  if (clipboard_needs_update) {
    258    // must be before, as set_clipboard will invoke
    259    // start/end_batch_changes recursively
    260    clipboard_needs_update = false;
    261    // unnamed ("implicit" clipboard)
    262    set_clipboard(NUL, get_y_previous());
    263  }
    264 }
    265 
    266 int save_batch_count(void)
    267 {
    268  int save_count = batch_change_count;
    269  batch_change_count = 0;
    270  clipboard_delay_update = false;
    271  if (clipboard_needs_update) {
    272    clipboard_needs_update = false;
    273    // unnamed ("implicit" clipboard)
    274    set_clipboard(NUL, get_y_previous());
    275  }
    276  return save_count;
    277 }
    278 
    279 void restore_batch_count(int save_count)
    280 {
    281  assert(batch_change_count == 0);
    282  batch_change_count = save_count;
    283  if (batch_change_count > 0) {
    284    clipboard_delay_update = true;
    285  }
    286 }