Skip to content

Commit 8fda035

Browse files
committed
Fix bug where clipboard would be polluted when showing Services menu
Previously, whenever the user selects some text in MacVim, and then bring up the Services menu (either by right-clicking on the selection, or go to MacVim -> Services), MacVim will copy the selected text to the system clipboard, which is quite an unexpected behavior. Fix that here. Part of the issue is that Vim has built-in ways to convert the selected range to a single copyable text, while managing complexities of dealing with blockwise/linewise modes. Previous implementation in MacVim was lazy in that it just invoked those code, but the side effect was that it would also copy the text to the system clipboard and pollute Vim's star register as well. This was quite undesirable because the user has not done anything other than opening a menu and wouldn't have expected the system clipboard or the Vim registers to have changed. In this fix, we unfortunately had to do a little bit more copy-pasting to extract the useful bits so we can copy the text to a register (but not system clipboard), invoke the code to convert the register to clipboard-happy text, restore the register, and then move on. A little annoying but better than having the unintuitive / annoying behaveior from before, and this way we don't have to do too much intrusive refactoring of upstream Vim code as well.
1 parent 130a89e commit 8fda035

File tree

4 files changed

+133
-25
lines changed

4 files changed

+133
-25
lines changed

src/MacVim/MMBackend.m

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,26 +1357,111 @@ - (NSString *)evaluateExpression:(in bycopy NSString *)expr
13571357
return eval;
13581358
}
13591359

1360-
- (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1360+
/// Extracts the text currently selected in visual mode, and return them in str/len.
1361+
///
1362+
/// @return the motion type (e.g. blockwise), or -1 for failure.
1363+
static int extractSelectedText(char_u **str, long_u *len)
1364+
{
1365+
// Note: Most of the functionality in Vim that allows for extracting useful
1366+
// text from a selection are in the register & clipboard utility functions.
1367+
// Unfortunately, most of those functions would actually send the text to
1368+
// the system clipboard, which we don't want (since we just want to extract
1369+
// the text instead of polluting the system clipboard). We don't want to
1370+
// refactor upstream Vim code too much to avoid merge pains later, so we
1371+
// duplicate a fair bit of the code from the functions below.
1372+
1373+
if (!(VIsual_active && (State & MODE_NORMAL))) {
1374+
// This only works when we are in visual mode and have stuff to select.
1375+
return -1;
1376+
}
1377+
1378+
// Step 1: Find a register to yank the selection to. If we don't do this we
1379+
// have to duplicate a lot of code in op_yank(). We save it off to a backup
1380+
// first so we can restore it later to avoid polluting the registers.
1381+
1382+
// Just use the yank / '0' register as it makes sense, but it doesn't
1383+
// really matter.
1384+
yankreg_T *target_reg = get_y_register(0);
1385+
1386+
// Move the contents to the backup without doing memory allocs.
1387+
yankreg_T backup_reg = *target_reg;
1388+
target_reg->y_array = NULL;
1389+
target_reg->y_size = 0;
1390+
1391+
// Step 2: Preserve the local states, and then invoke yank.
1392+
// Note: These were copied from clip_get_selection() in clipboard.c
1393+
yankreg_T *old_y_previous, *old_y_current;
1394+
pos_T old_cursor;
1395+
pos_T old_visual;
1396+
int old_visual_mode;
1397+
colnr_T old_curswant;
1398+
int old_set_curswant;
1399+
pos_T old_op_start, old_op_end;
1400+
oparg_T oa;
1401+
cmdarg_T ca;
1402+
1403+
// Avoid triggering autocmds such as TextYankPost.
1404+
block_autocmds();
1405+
1406+
// Yank the selected text into the target register.
1407+
old_y_previous = get_y_previous();
1408+
old_y_current = get_y_current();
1409+
old_cursor = curwin->w_cursor;
1410+
old_curswant = curwin->w_curswant;
1411+
old_set_curswant = curwin->w_set_curswant;
1412+
old_op_start = curbuf->b_op_start;
1413+
old_op_end = curbuf->b_op_end;
1414+
old_visual = VIsual;
1415+
old_visual_mode = VIsual_mode;
1416+
clear_oparg(&oa);
1417+
oa.regname = '0'; // Use the '0' (yank) register. We will restore it later to avoid pollution.
1418+
oa.op_type = OP_YANK;
1419+
CLEAR_FIELD(ca);
1420+
ca.oap = &oa;
1421+
ca.cmdchar = 'y';
1422+
ca.count1 = 1;
1423+
ca.retval = CA_NO_ADJ_OP_END;
1424+
do_pending_operator(&ca, 0, TRUE);
1425+
1426+
// Step 3: Convert the yank register to a single piece of useful text. This
1427+
// will handle all the edge cases of different modes (e.g. blockwise, etc).
1428+
const int convert_result = clip_convert_selection(str, len, NULL);
1429+
1430+
// Step 4: Clean up the yank register, and restore it back.
1431+
set_y_current(target_reg); // should not be necessary as it's done in do_pending_operator above (since regname was set to 0), but just to be safe and verbose in intention.
1432+
free_yank_all();
1433+
*target_reg = backup_reg;
1434+
1435+
// Step 5: Restore all the loose states that were modified during yank.
1436+
// Note: These were copied from clip_get_selection() in clipboard.c
1437+
set_y_previous(old_y_previous);
1438+
set_y_current(old_y_current);
1439+
curwin->w_cursor = old_cursor;
1440+
changed_cline_bef_curs(); // need to update w_virtcol et al
1441+
curwin->w_curswant = old_curswant;
1442+
curwin->w_set_curswant = old_set_curswant;
1443+
curbuf->b_op_start = old_op_start;
1444+
curbuf->b_op_end = old_op_end;
1445+
VIsual = old_visual;
1446+
VIsual_mode = old_visual_mode;
1447+
1448+
unblock_autocmds();
1449+
1450+
return convert_result;
1451+
}
1452+
1453+
/// Extract the currently selected text (in visual mode) and send that to the
1454+
/// provided pasteboard.
1455+
- (BOOL)selectedTextToPasteboard:(byref NSPasteboard *)pboard
13611456
{
1362-
// TODO: This method should share code with clip_mch_request_selection().
1363-
1364-
if (VIsual_active && (State & MODE_NORMAL) && clip_star.available) {
1365-
// If there is no pasteboard, return YES to indicate that there is text
1366-
// to copy.
1457+
if (VIsual_active && (State & MODE_NORMAL)) {
1458+
// If there is no pasteboard, just return YES to indicate that there is
1459+
// text to copy.
13671460
if (!pboard)
13681461
return YES;
13691462

1370-
// The code below used to be clip_copy_selection() but it is now
1371-
// static, so do it manually.
1372-
clip_update_selection(&clip_star);
1373-
clip_free_selection(&clip_star);
1374-
clip_get_selection(&clip_star);
1375-
clip_gen_set_selection(&clip_star);
1376-
1377-
// Get the text to put on the pasteboard.
13781463
long_u llen = 0; char_u *str = 0;
1379-
int type = clip_convert_selection(&str, &llen, &clip_star);
1464+
int type = extractSelectedText(&str, &llen);
13801465
if (type < 0)
13811466
return NO;
13821467

src/MacVim/MMWindowController.m

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ - (void)resizeWindowToFitContentSize:(NSSize)contentSize
8787
- (NSSize)constrainContentSizeToScreenSize:(NSSize)contentSize;
8888
- (NSRect)constrainFrame:(NSRect)frame;
8989
- (NSTabViewItem *)addNewTabViewItem;
90-
- (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
90+
- (BOOL)askBackendForSelectedText:(NSPasteboard *)pb;
9191
- (void)updateTablineSeparator;
9292
- (void)hideTablineSeparator:(BOOL)hide;
9393
- (void)doFindNext:(BOOL)next;
@@ -1336,21 +1336,25 @@ - (id)validRequestorForSendType:(NSString *)sendType
13361336
returnType:(NSString *)returnType
13371337
{
13381338
if ([sendType isEqual:NSStringPboardType]
1339-
&& [self askBackendForStarRegister:nil])
1339+
&& [self askBackendForSelectedText:nil])
13401340
return self;
13411341

13421342
return [super validRequestorForSendType:sendType returnType:returnType];
13431343
}
13441344

1345+
/// Called by OS when it tries to show a "Services" menu. We ask Vim for the
1346+
/// currently selected text and write that to the provided pasteboard.
13451347
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
13461348
types:(NSArray *)types
13471349
{
13481350
if (![types containsObject:NSStringPboardType])
13491351
return NO;
13501352

1351-
return [self askBackendForStarRegister:pboard];
1353+
return [self askBackendForSelectedText:pboard];
13521354
}
13531355

1356+
/// Called by the OS when it tries to update the selection. This could happen
1357+
/// if you selected "Convert text to full width" in the Services menu, for example.
13541358
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
13551359
{
13561360
// Replace the current selection with the text on the pasteboard.
@@ -1678,18 +1682,25 @@ - (NSTabViewItem *)addNewTabViewItem
16781682
return [vimView addNewTabViewItem];
16791683
}
16801684

1681-
- (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1682-
{
1683-
// TODO: Can this be done with evaluateExpression: instead?
1685+
/// Ask Vim to fill in the pasteboard with the currently selected text in visual mode.
1686+
- (BOOL)askBackendForSelectedText:(NSPasteboard *)pb
1687+
{
1688+
// We use a dedicated API for this instead of just using something like
1689+
// evaluateExpression because there's a fair bit of logic in Vim that
1690+
// figures out how to convert from a visual selection to an externally
1691+
// presentable text in the clipboard code. Part of the complexity has to do
1692+
// with the three modes (character/blockwise/linewise) that visual mode can
1693+
// be in. We would like to reuse that code, instead of hacking it via some
1694+
// expression evaluation results.
16841695
BOOL reply = NO;
16851696
id backendProxy = [vimController backendProxy];
16861697

16871698
if (backendProxy) {
16881699
@try {
1689-
reply = [backendProxy starRegisterToPasteboard:pb];
1700+
reply = [backendProxy selectedTextToPasteboard:pb];
16901701
}
16911702
@catch (NSException *ex) {
1692-
ASLogDebug(@"starRegisterToPasteboard: failed: pid=%d reason=%@",
1703+
ASLogDebug(@"selectedTextToPasteboard: failed: pid=%d reason=%@",
16931704
[vimController pid], ex);
16941705
}
16951706
}

src/MacVim/MacVim.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
- (NSString *)evaluateExpression:(in bycopy NSString *)expr;
119119
- (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
120120
errorString:(out bycopy NSString **)errstr;
121-
- (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard;
121+
- (BOOL)selectedTextToPasteboard:(byref NSPasteboard *)pboard;
122122
- (oneway void)acknowledgeConnection;
123123
@end
124124

src/clipboard.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2114,7 +2114,19 @@ clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd)
21142114
int_u eolsize;
21152115
yankreg_T *y_ptr;
21162116

2117-
if (cbd == &clip_plus)
2117+
if (!cbd)
2118+
{
2119+
// MacVim extension: This makes this function much more useful as we
2120+
// can now extract usable texts from any registers for use instead of
2121+
// being forced to go through the system clipboard. This is useful for
2122+
// features that expose selected texts (e.g. system services) without
2123+
// polluting the system clipboard.
2124+
int unname_register = get_unname_register();
2125+
if (unname_register < 0)
2126+
return -1;
2127+
y_ptr = get_y_register(unname_register);
2128+
}
2129+
else if (cbd == &clip_plus)
21182130
y_ptr = get_y_register(PLUS_REGISTER);
21192131
else
21202132
y_ptr = get_y_register(STAR_REGISTER);

0 commit comments

Comments
 (0)