Skip to content

Commit 7cb3ab5

Browse files
committed
Fix macOS services no longer able to insert texts in non-Visual modes
Interactions with OS services that insert texts were improved in #1552 to make it work in a more integrated fashion instead of a hacky injection of 's' followed by the text. However, it only accounted for services that replaces selected texts in visual mode. In other modes, MacVim would simply ignore the service. This was a regression as previously it would work everywhere (albeit often times in a non-intuitive manner since if used in insert mode the user would see an 's' in the beginning). This also affects Shortcuts that a user may have made that could be invoked from the Services menu or bound to a hotkey. Fix this properly by allowing this to be used in Normal / Insert / Cmdline modes in addition to Visual mode. Even with this fix, there is still a slight difference between the new behavior and the old hacky solution - the old method would leave the user in Insert mode, whereas the new method would stay in Normal mode. I think this is an improvement so no need to fix. Fix #1569
1 parent b2c10a6 commit 7cb3ab5

File tree

7 files changed

+161
-30
lines changed

7 files changed

+161
-30
lines changed

src/MacVim/MMBackend.m

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,10 +1443,11 @@ - (NSString *)selectedText
14431443
return nil;
14441444
}
14451445

1446-
/// Replace the selected text in visual mode with the new suppiled one.
1447-
- (oneway void)replaceSelectedText:(in bycopy NSString *)text
1446+
/// Insert or replace text with the supplied text. Works in Normal / Visual /
1447+
/// Insert / Cmdline modes.
1448+
- (oneway void)insertOrReplaceSelectedText:(in bycopy NSString *)text
14481449
{
1449-
if (VIsual_active && (State & MODE_NORMAL)) {
1450+
if (State & MODE_NORMAL || State & MODE_INSERT) {
14501451
// The only real way Vim has in doing this consistently is to use the
14511452
// register put functionality as there is no generic API for this.
14521453
// We find an arbitrary register ('0'), back it up, replace it with our
@@ -1474,17 +1475,27 @@ - (oneway void)replaceSelectedText:(in bycopy NSString *)text
14741475
write_reg_contents_ex('0', vimtext, -1, FALSE, yank_type, -1);
14751476
vim_free(vimtext);
14761477

1477-
oparg_T oap;
1478-
CLEAR_FIELD(oap);
1479-
oap.regname = '0';
1480-
1481-
cmdarg_T cap;
1482-
CLEAR_FIELD(cap);
1483-
cap.oap = &oap;
1484-
cap.cmdchar = 'P';
1485-
cap.count1 = 1;
1478+
if (State & MODE_NORMAL || State & MODE_INSERT) {
1479+
oparg_T oap;
1480+
CLEAR_FIELD(oap);
1481+
oap.regname = '0';
1482+
1483+
cmdarg_T cap;
1484+
CLEAR_FIELD(cap);
1485+
cap.oap = &oap;
1486+
if (State & MODE_NORMAL) {
1487+
// Do 'P' or 'v_P' depending if we are in visual mode. They both do
1488+
// the correct behaviors, so no need to check for VIsual_active.
1489+
cap.cmdchar = 'P';
1490+
} else {
1491+
// Need 'gP' to leave the cursor at the right location.
1492+
cap.cmdchar = 'g';
1493+
cap.nchar = 'P';
1494+
}
1495+
cap.count1 = 1;
14861496

1487-
nv_put(&cap);
1497+
nv_put(&cap);
1498+
}
14881499

14891500
// Clean up the temporary register, and restore the old state.
14901501
yankreg_T *old_y_current = get_y_current();
@@ -1496,6 +1507,73 @@ - (oneway void)replaceSelectedText:(in bycopy NSString *)text
14961507
// nv_put does not trigger a redraw command as it's done on a higher
14971508
// level, so just do a manual one here to make sure it's done.
14981509
[self redrawScreen];
1510+
} else if (State & MODE_CMDLINE) {
1511+
// This is basically doing the following:
1512+
// - let cmdline_str = getcmdline()
1513+
// - let cmdline_pos = getcmdpos() - 1
1514+
// - setcmdline(cmdline_str[0:cmdline_pos] .. text .. cmdline_str[cmdline_pos:], cmdline_pos + len(text) + 1)
1515+
1516+
typval_T cmdline_str;
1517+
f_getcmdline(NULL, &cmdline_str);
1518+
1519+
typval_T cmdline_pos;
1520+
f_getcmdpos(NULL, &cmdline_pos);
1521+
1522+
char_u *vimtext = [text vimStringSave];
1523+
for (char_u *c = vimtext; *c != NUL; c++) {
1524+
// Perform NL conversion due to Vim's internal usage
1525+
if (*c == '\n')
1526+
*c = '\r';
1527+
}
1528+
1529+
size_t new_size = STRLEN(vimtext);
1530+
varnumber_T pos = new_size + 1;
1531+
1532+
if (cmdline_str.vval.v_string != NULL && cmdline_pos.vval.v_number != 0) {
1533+
// Combine original string with new one
1534+
char_u *orig_str = cmdline_str.vval.v_string;
1535+
size_t pos_index = cmdline_pos.vval.v_number - 1;
1536+
1537+
size_t orig_size = STRLEN(cmdline_str.vval.v_string);
1538+
1539+
if (pos_index > orig_size)
1540+
pos_index = orig_size; // shouldn't really happen
1541+
1542+
char_u *newtext = alloc(orig_size + new_size + 1);
1543+
if (pos_index > 0)
1544+
memcpy(newtext, orig_str, pos_index);
1545+
memcpy(newtext + pos_index, vimtext, new_size);
1546+
if (pos_index < orig_size)
1547+
memcpy(newtext + pos_index + new_size, orig_str + pos_index, orig_size - pos_index);
1548+
newtext[orig_size + new_size] = '\0';
1549+
1550+
vim_free(vimtext);
1551+
vimtext = newtext;
1552+
1553+
pos += pos_index;
1554+
}
1555+
1556+
{
1557+
typval_T arg_cmdline_str_new;
1558+
init_tv(&arg_cmdline_str_new);
1559+
arg_cmdline_str_new.v_type = VAR_STRING;
1560+
arg_cmdline_str_new.vval.v_string = vimtext;
1561+
1562+
typval_T arg_cmdline_pos;
1563+
init_tv(&arg_cmdline_pos);
1564+
arg_cmdline_pos.v_type = VAR_NUMBER;
1565+
arg_cmdline_pos.vval.v_number = pos;
1566+
1567+
typval_T args[2] = { arg_cmdline_str_new, arg_cmdline_pos };
1568+
1569+
typval_T ret;
1570+
f_setcmdline(args, &ret);
1571+
1572+
vim_free(vimtext);
1573+
}
1574+
1575+
if (cmdline_str.vval.v_string != NULL)
1576+
vim_free(cmdline_str.vval.v_string);
14991577
}
15001578
}
15011579

src/MacVim/MMTextViewHelper.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ - (void)insertText:(id)string replacementRange:(NSRange)replacementRange
251251
// Only known way of this being called is Apple Intelligence Writing
252252
// Tools.
253253
MMVimController *vc = [self vimController];
254-
[vc replaceSelectedText:string];
254+
[vc insertOrReplaceSelectedText:string];
255255
return;
256256
}
257257

src/MacVim/MMVimController.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
errorString:(NSString **)errstr;
9595
- (BOOL)hasSelectedText;
9696
- (NSString *)selectedText;
97-
- (void)replaceSelectedText:(NSString *)text;
97+
- (void)insertOrReplaceSelectedText:(NSString *)text;
9898
- (void)processInputQueue:(NSArray *)queue;
9999
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12_2
100100
- (NSTouchBar *)makeTouchBar;

src/MacVim/MMVimController.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,14 +563,14 @@ - (NSString *)selectedText
563563
return selectedText;
564564
}
565565

566-
- (void)replaceSelectedText:(NSString *)text
566+
- (void)insertOrReplaceSelectedText:(NSString *)text
567567
{
568568
if (backendProxy) {
569569
@try {
570-
[backendProxy replaceSelectedText:text];
570+
[backendProxy insertOrReplaceSelectedText:text];
571571
}
572572
@catch (NSException *ex) {
573-
ASLogDebug(@"replaceSelectedText: failed: pid=%d reason=%@",
573+
ASLogDebug(@"insertOrReplaceSelectedText: failed: pid=%d reason=%@",
574574
pid, ex);
575575
}
576576
}

src/MacVim/MMWindowController.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1679,7 +1679,7 @@ - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
16791679
NSArray *types = [pboard types];
16801680
if ([types containsObject:NSPasteboardTypeString]) {
16811681
NSString *input = [pboard stringForType:NSPasteboardTypeString];
1682-
[vimController replaceSelectedText:input];
1682+
[vimController insertOrReplaceSelectedText:input];
16831683
return YES;
16841684
}
16851685

src/MacVim/MacVim.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ typedef NSString* NSAttributedStringKey;
194194
errorString:(out bycopy NSString **)errstr;
195195
- (BOOL)hasSelectedText;
196196
- (NSString *)selectedText;
197-
- (oneway void)replaceSelectedText:(in bycopy NSString *)text;
197+
- (oneway void)insertOrReplaceSelectedText:(in bycopy NSString *)text;
198198
- (BOOL)mouseScreenposIsSelection:(int)row column:(int)column selRow:(byref int *)startRow selCol:(byref int *)startCol;
199199
- (oneway void)acknowledgeConnection;
200200
@end

src/MacVim/MacVimTests/MacVimTests.m

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,6 +1470,10 @@ - (void)testIPCSelectedText {
14701470
NSString *regcontents = [vc evaluateVimExpression:@"getreg()"];
14711471
XCTAssertEqualObjects(regcontents, @"abcd\n");
14721472

1473+
// Visual mode
1474+
1475+
NSString *changedtick1 = [vc evaluateVimExpression:@"b:changedtick"];
1476+
14731477
// Get selected texts in visual mode
14741478
XCTAssertFalse([vc hasSelectedText]);
14751479
XCTAssertNil([vc selectedText]);
@@ -1491,30 +1495,79 @@ - (void)testIPCSelectedText {
14911495
XCTAssertEqualObjects([vc selectedText], @"bc\nfg");
14921496

14931497
// Set selected texts in visual block mode
1494-
NSString *changedtick = [vc evaluateVimExpression:@"b:changedtick"];
1495-
[vc replaceSelectedText:@"xyz\n1234"];
1496-
NSString *changedtick2 = [vc evaluateVimExpression:@"b:changedtick"];
1498+
[vc insertOrReplaceSelectedText:@"xyz\n1234"];
14971499
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(1)"], @"axyz d");
14981500
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(2)"], @"e1234h");
14991501
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(3)"], @"ijkl");
1500-
XCTAssertNotEqualObjects(changedtick, changedtick2);
1501-
1502-
// Make sure replacing texts when nothing is selected won't set anything
1503-
[vc replaceSelectedText:@"foobar"];
1504-
NSString *changedtick3 = [vc evaluateVimExpression:@"b:changedtick"];
1505-
XCTAssertEqualObjects(changedtick2, changedtick3);
15061502

15071503
// Select in visual block again but send a different number of lines, make sure we intentionaly won't treat it as block text
15081504
[self sendStringToVim:@"ggjjvll" withMods:0];
15091505
[self sendKeyToVim:@"v" withMods:NSEventModifierFlagControl];
15101506
[self waitForEventHandlingAndVimProcess];
1511-
[vc replaceSelectedText:@"xyz\n1234\n"]; // ending in newline means it gets interpreted as line-wise
1507+
[vc insertOrReplaceSelectedText:@"xyz\n1234\n"]; // ending in newline means it gets interpreted as line-wise
15121508
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(1)"], @"axyz d");
15131509
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(2)"], @"e1234h");
15141510
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(3)"], @"xyz");
15151511
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(4)"], @"1234");
15161512
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(5)"], @"l");
15171513

1514+
// Normal mode
1515+
1516+
// When nothing is selected this will simply insert the text and not replace anything
1517+
[self sendStringToVim:@"ggll" withMods:0];
1518+
[self waitForEventHandlingAndVimProcess];
1519+
[vc insertOrReplaceSelectedText:@"_normtext_"];
1520+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(1)"], @"ax_normtext_yz d");
1521+
1522+
// Insert mode
1523+
1524+
[self sendStringToVim:@"ggjja" withMods:0];
1525+
[self waitForEventHandlingAndVimProcess];
1526+
// Should insert the text at the cursor
1527+
[vc insertOrReplaceSelectedText:@"_inserttext_"];
1528+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(3)"], @"x_inserttext_yz");
1529+
// Should leave the cursor past the inserted text
1530+
[self sendStringToVim:@"additional_text" withMods:0];
1531+
[self waitForEventHandlingAndVimProcess];
1532+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getline(3)"], @"x_inserttext_additional_textyz");
1533+
[self sendKeyToVim:@"[" withMods:NSEventModifierFlagControl]; // escape insert mode
1534+
[self waitForEventHandlingAndVimProcess];
1535+
1536+
// Cmdline mode
1537+
1538+
NSString *changedtick2 = [vc evaluateVimExpression:@"b:changedtick"];
1539+
XCTAssertNotEqualObjects(changedtick1, changedtick2);
1540+
1541+
[self sendStringToVim:@":cnoremap z <Left>\n" withMods:0];
1542+
[self sendStringToVim:@":123" withMods:0];
1543+
[self waitForEventHandlingAndVimProcess];
1544+
[vc insertOrReplaceSelectedText:@"a\nb\n"];
1545+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getcmdline()"], @"123a\rb\r"); // Vim does internal \n to \r conversion
1546+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getcmdpos()"], @"8");
1547+
[self sendKeyToVim:@"[" withMods:NSEventModifierFlagControl]; // escape cmdline
1548+
[self waitForEventHandlingAndVimProcess];
1549+
1550+
[self sendStringToVim:@":123zzz" withMods:0];
1551+
[self waitForEventHandlingAndVimProcess];
1552+
[vc insertOrReplaceSelectedText:@"foobar"];
1553+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getcmdline()"], @"foobar123");
1554+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getcmdpos()"], @"7");
1555+
[self sendKeyToVim:@"[" withMods:NSEventModifierFlagControl]; // escape cmdline
1556+
1557+
[self waitForEventHandlingAndVimProcess];
1558+
[self sendStringToVim:@":123z" withMods:0];
1559+
[self waitForEventHandlingAndVimProcess];
1560+
[vc insertOrReplaceSelectedText:@"foobar"];
1561+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getcmdline()"], @"12foobar3");
1562+
XCTAssertEqualObjects([vc evaluateVimExpression:@"getcmdpos()"], @"9");
1563+
[self sendKeyToVim:@"[" withMods:NSEventModifierFlagControl]; // escape cmdline
1564+
[self waitForEventHandlingAndVimProcess];
1565+
1566+
// Make sure that the actual buffer wasn't changed at all during these insertions as they all
1567+
// went to the cmdline.
1568+
NSString *changedtick3 = [vc evaluateVimExpression:@"b:changedtick"];
1569+
XCTAssertEqualObjects(changedtick2, changedtick3);
1570+
15181571
// Make sure registers didn't get stomped (internally the implementation uses register and manually restores it)
15191572
regcontents = [[app keyVimController] evaluateVimExpression:@"getreg()"];
15201573
XCTAssertEqualObjects(regcontents, @"abcd\n");

0 commit comments

Comments
 (0)