Skip to content

Commit 08f49ae

Browse files
authored
Merge pull request #1571 from ychin/fix-shortcut-services-text-input
Fix macOS services no longer able to insert texts in non-Visual modes
2 parents b2c10a6 + 7cb3ab5 commit 08f49ae

File tree

7 files changed

+161
-30
lines changed

7 files changed

+161
-30
lines changed

src/MacVim/MMBackend.m

+91-13
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

+1-1
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

+1-1
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

+3-3
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

+1-1
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

+1-1
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

+63-10
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)