Skip to content

Commit 9e21327

Browse files
authored
Merge pull request #1300 from ychin/fix-services-menu-copy-to-clipboard
Fix bug where clipboard would be polluted when showing Services menu
2 parents bacfaff + f45f939 commit 9e21327

File tree

3 files changed

+120
-31
lines changed

3 files changed

+120
-31
lines changed

src/MacVim/MMBackend.m

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,41 +1357,121 @@ - (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 returns it.
1361+
///
1362+
/// @return the string representing the selected text, or NULL if failure.
1363+
static char_u *extractSelectedText()
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 NULL;
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: Extract the text from the yank ('0') register.
1427+
char_u *str = get_reg_contents(0, 0);
1428+
1429+
// Step 4: Clean up the yank register, and restore it back.
1430+
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.
1431+
free_yank_all();
1432+
*target_reg = backup_reg;
1433+
1434+
// Step 5: Restore all the loose states that were modified during yank.
1435+
// Note: These were copied from clip_get_selection() in clipboard.c
1436+
set_y_previous(old_y_previous);
1437+
set_y_current(old_y_current);
1438+
curwin->w_cursor = old_cursor;
1439+
changed_cline_bef_curs(); // need to update w_virtcol et al
1440+
curwin->w_curswant = old_curswant;
1441+
curwin->w_set_curswant = old_set_curswant;
1442+
curbuf->b_op_start = old_op_start;
1443+
curbuf->b_op_end = old_op_end;
1444+
VIsual = old_visual;
1445+
VIsual_mode = old_visual_mode;
1446+
1447+
unblock_autocmds();
1448+
1449+
return str;
1450+
}
1451+
1452+
/// Extract the currently selected text (in visual mode) and send that to the
1453+
/// provided pasteboard.
1454+
- (BOOL)selectedTextToPasteboard:(byref NSPasteboard *)pboard
13611455
{
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.
1456+
if (VIsual_active && (State & MODE_NORMAL)) {
1457+
// If there is no pasteboard, just return YES to indicate that there is
1458+
// text to copy.
13671459
if (!pboard)
13681460
return YES;
13691461

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.
1378-
long_u llen = 0; char_u *str = 0;
1379-
int type = clip_convert_selection(&str, &llen, &clip_star);
1380-
if (type < 0)
1462+
char_u *str = extractSelectedText();
1463+
if (!str)
13811464
return NO;
13821465

1383-
// TODO: Avoid overflow.
1384-
int len = (int)llen;
13851466
if (output_conv.vc_type != CONV_NONE) {
1386-
char_u *conv_str = string_convert(&output_conv, str, &len);
1467+
char_u *conv_str = string_convert(&output_conv, str, NULL);
13871468
if (conv_str) {
13881469
vim_free(str);
13891470
str = conv_str;
13901471
}
13911472
}
13921473

1393-
NSString *string = [[NSString alloc]
1394-
initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1474+
NSString *string = [[NSString alloc] initWithUTF8String:(char*)str];
13951475

13961476
NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
13971477
[pboard declareTypes:types owner:nil];

src/MacVim/MMWindowController.m

Lines changed: 17 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;
@@ -1342,21 +1342,25 @@ - (id)validRequestorForSendType:(NSString *)sendType
13421342
returnType:(NSString *)returnType
13431343
{
13441344
if ([sendType isEqual:NSStringPboardType]
1345-
&& [self askBackendForStarRegister:nil])
1345+
&& [self askBackendForSelectedText:nil])
13461346
return self;
13471347

13481348
return [super validRequestorForSendType:sendType returnType:returnType];
13491349
}
13501350

1351+
/// Called by OS when it tries to show a "Services" menu. We ask Vim for the
1352+
/// currently selected text and write that to the provided pasteboard.
13511353
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
13521354
types:(NSArray *)types
13531355
{
13541356
if (![types containsObject:NSStringPboardType])
13551357
return NO;
13561358

1357-
return [self askBackendForStarRegister:pboard];
1359+
return [self askBackendForSelectedText:pboard];
13581360
}
13591361

1362+
/// Called by the OS when it tries to update the selection. This could happen
1363+
/// if you selected "Convert text to full width" in the Services menu, for example.
13601364
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
13611365
{
13621366
// Replace the current selection with the text on the pasteboard.
@@ -1684,18 +1688,23 @@ - (NSTabViewItem *)addNewTabViewItem
16841688
return [vimView addNewTabViewItem];
16851689
}
16861690

1687-
- (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
1688-
{
1689-
// TODO: Can this be done with evaluateExpression: instead?
1691+
/// Ask Vim to fill in the pasteboard with the currently selected text in visual mode.
1692+
- (BOOL)askBackendForSelectedText:(NSPasteboard *)pb
1693+
{
1694+
// This could potentially be done via evaluateExpression by yanking the
1695+
// selection, then returning the results via getreg('@') and restoring the
1696+
// register. Using a dedicated API is probably a little safer (e.g. it
1697+
// prevents TextYankPost autocmd's from triggering) and efficient
1698+
// and hence this is what we use for now.
16901699
BOOL reply = NO;
16911700
id backendProxy = [vimController backendProxy];
16921701

16931702
if (backendProxy) {
16941703
@try {
1695-
reply = [backendProxy starRegisterToPasteboard:pb];
1704+
reply = [backendProxy selectedTextToPasteboard:pb];
16961705
}
16971706
@catch (NSException *ex) {
1698-
ASLogDebug(@"starRegisterToPasteboard: failed: pid=%d reason=%@",
1707+
ASLogDebug(@"selectedTextToPasteboard: failed: pid=%d reason=%@",
16991708
[vimController pid], ex);
17001709
}
17011710
}

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

0 commit comments

Comments
 (0)