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, ®name, 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, ®type, 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 }