Skip to content

Commit 712e067

Browse files
authored
Merge pull request #1552 from ychin/fix-writing-tools-and-services-text-replacement
Fix Writing Tools integration and fix up Services integration with texts
2 parents 252a133 + db26a3b commit 712e067

File tree

15 files changed

+286
-185
lines changed

15 files changed

+286
-185
lines changed

src/MacVim/MMBackend.m

Lines changed: 121 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,158 +1380,140 @@ - (NSString *)evaluateExpression:(in bycopy NSString *)expr
13801380
return eval;
13811381
}
13821382

1383-
/// Extracts the text currently selected in visual mode, and returns it.
1384-
///
1385-
/// @return the string representing the selected text, or NULL if failure.
1386-
static char_u *extractSelectedText(void)
1387-
{
1388-
// Note: Most of the functionality in Vim that allows for extracting useful
1389-
// text from a selection are in the register & clipboard utility functions.
1390-
// Unfortunately, most of those functions would actually send the text to
1391-
// the system clipboard, which we don't want (since we just want to extract
1392-
// the text instead of polluting the system clipboard). We don't want to
1393-
// refactor upstream Vim code too much to avoid merge pains later, so we
1394-
// duplicate a fair bit of the code from the functions below.
1395-
1396-
if (!(VIsual_active && (State & MODE_NORMAL))) {
1397-
// This only works when we are in visual mode and have stuff to select.
1398-
return NULL;
1399-
}
1400-
1401-
// Step 1: Find a register to yank the selection to. If we don't do this we
1402-
// have to duplicate a lot of code in op_yank(). We save it off to a backup
1403-
// first so we can restore it later to avoid polluting the registers.
1404-
1405-
// Just use the yank / '0' register as it makes sense, but it doesn't
1406-
// really matter.
1407-
yankreg_T *target_reg = get_y_register(0);
1408-
1409-
// Move the contents to the backup without doing memory allocs.
1410-
yankreg_T backup_reg = *target_reg;
1411-
target_reg->y_array = NULL;
1412-
target_reg->y_size = 0;
1413-
1414-
// Step 2: Preserve the local states, and then invoke yank.
1415-
// Note: These were copied from clip_get_selection() in clipboard.c
1416-
yankreg_T *old_y_previous, *old_y_current;
1417-
pos_T old_cursor;
1418-
pos_T old_visual;
1419-
int old_visual_mode;
1420-
colnr_T old_curswant;
1421-
int old_set_curswant;
1422-
pos_T old_op_start, old_op_end;
1423-
oparg_T oa;
1424-
cmdarg_T ca;
1425-
1426-
// Avoid triggering autocmds such as TextYankPost.
1427-
block_autocmds();
1428-
1429-
// Yank the selected text into the target register.
1430-
old_y_previous = get_y_previous();
1431-
old_y_current = get_y_current();
1432-
old_cursor = curwin->w_cursor;
1433-
old_curswant = curwin->w_curswant;
1434-
old_set_curswant = curwin->w_set_curswant;
1435-
old_op_start = curbuf->b_op_start;
1436-
old_op_end = curbuf->b_op_end;
1437-
old_visual = VIsual;
1438-
old_visual_mode = VIsual_mode;
1439-
clear_oparg(&oa);
1440-
oa.regname = '0'; // Use the '0' (yank) register. We will restore it later to avoid pollution.
1441-
oa.op_type = OP_YANK;
1442-
CLEAR_FIELD(ca);
1443-
ca.oap = &oa;
1444-
ca.cmdchar = 'y';
1445-
ca.count1 = 1;
1446-
ca.retval = CA_NO_ADJ_OP_END;
1447-
do_pending_operator(&ca, 0, TRUE);
1448-
1449-
// Step 3: Extract the text from the yank ('0') register.
1450-
char_u *str = get_reg_contents(0, 0);
1451-
1452-
// Step 4: Clean up the yank register, and restore it back.
1453-
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.
1454-
free_yank_all();
1455-
*target_reg = backup_reg;
1456-
1457-
// Step 5: Restore all the loose states that were modified during yank.
1458-
// Note: These were copied from clip_get_selection() in clipboard.c
1459-
set_y_previous(old_y_previous);
1460-
set_y_current(old_y_current);
1461-
curwin->w_cursor = old_cursor;
1462-
changed_cline_bef_curs(); // need to update w_virtcol et al
1463-
curwin->w_curswant = old_curswant;
1464-
curwin->w_set_curswant = old_set_curswant;
1465-
curbuf->b_op_start = old_op_start;
1466-
curbuf->b_op_end = old_op_end;
1467-
VIsual = old_visual;
1468-
VIsual_mode = old_visual_mode;
1469-
1470-
unblock_autocmds();
1471-
1472-
return str;
1473-
}
1474-
1475-
/// Extract the currently selected text (in visual mode) and send that to the
1476-
/// provided pasteboard.
1477-
- (BOOL)selectedTextToPasteboard:(byref NSPasteboard *)pboard
1383+
- (BOOL)hasSelectedText
14781384
{
1479-
if (VIsual_active && (State & MODE_NORMAL)) {
1480-
// If there is no pasteboard, just return YES to indicate that there is
1481-
// text to copy.
1482-
if (!pboard)
1483-
return YES;
1385+
return (VIsual_active && (State & MODE_NORMAL));
1386+
}
14841387

1485-
char_u *str = extractSelectedText();
1486-
if (!str)
1487-
return NO;
1388+
/// Returns the currently selected text.
1389+
- (NSString *)selectedText
1390+
{
1391+
if (VIsual_active && (State & MODE_NORMAL)) {
1392+
// This is basically doing the following:
1393+
// - join(getregion(getpos("."), getpos("v"), { type: visualmode() }),"\n")
1394+
// - Add extra "\n" if we have a linewise selection
14881395

1489-
if (output_conv.vc_type != CONV_NONE) {
1490-
char_u *conv_str = string_convert(&output_conv, str, NULL);
1491-
if (conv_str) {
1492-
vim_free(str);
1493-
str = conv_str;
1396+
// Call getpos()
1397+
typval_T pos1, pos2;
1398+
{
1399+
typval_T arg_posmark;
1400+
init_tv(&arg_posmark);
1401+
arg_posmark.v_type = VAR_STRING;
1402+
1403+
arg_posmark.vval.v_string = (char_u*)".";
1404+
typval_T args1[1] = { arg_posmark };
1405+
f_getpos(args1, &pos1);
1406+
if (pos1.v_type != VAR_LIST)
1407+
return nil;
1408+
1409+
arg_posmark.vval.v_string = (char_u*)"v";
1410+
typval_T args2[1] = { arg_posmark };
1411+
f_getpos(args2, &pos2);
1412+
if (pos2.v_type != VAR_LIST) {
1413+
list_unref(pos1.vval.v_list);
1414+
return nil;
14941415
}
14951416
}
14961417

1497-
NSString *string = [[NSString alloc] initWithUTF8String:(char*)str];
1498-
1499-
NSArray *types = [NSArray arrayWithObject:NSPasteboardTypeString];
1500-
[pboard declareTypes:types owner:nil];
1501-
BOOL ok = [pboard setString:string forType:NSPasteboardTypeString];
1502-
1503-
[string release];
1504-
vim_free(str);
1418+
// Call getregion()
1419+
typval_T arg_opts;
1420+
init_tv(&arg_opts);
1421+
arg_opts.v_type = VAR_DICT;
1422+
arg_opts.vval.v_dict = dict_alloc();
1423+
arg_opts.vval.v_dict->dv_refcount += 1;
1424+
1425+
char_u visualmode[2] = { VIsual_mode, '\0' };
1426+
dict_add_string(arg_opts.vval.v_dict, "type", visualmode);
1427+
1428+
typval_T args[3] = { pos1, pos2, arg_opts };
1429+
typval_T regionLines;
1430+
f_getregion(args, &regionLines);
1431+
1432+
// Join the results
1433+
NSMutableArray *returnLines = [NSMutableArray array];
1434+
if (regionLines.v_type == VAR_LIST) {
1435+
list_T *lines = regionLines.vval.v_list;
1436+
for (listitem_T *item = lines->lv_first; item != NULL; item = item->li_next) {
1437+
if (item->li_tv.v_type == VAR_STRING) {
1438+
char_u *str = item->li_tv.vval.v_string;
1439+
if (output_conv.vc_type != CONV_NONE) {
1440+
char_u *conv_str = string_convert(&output_conv, str, NULL);
1441+
if (conv_str) {
1442+
[returnLines addObject:[NSString stringWithUTF8String:(char*)conv_str]];
1443+
vim_free(conv_str);
1444+
}
1445+
} else {
1446+
[returnLines addObject:[NSString stringWithUTF8String:(char*)str]];
1447+
}
1448+
}
1449+
}
1450+
list_unref(lines);
1451+
}
1452+
dict_unref(arg_opts.vval.v_dict);
1453+
list_unref(pos1.vval.v_list);
1454+
list_unref(pos2.vval.v_list);
15051455

1506-
return ok;
1456+
if (VIsual_mode == 'V')
1457+
[returnLines addObject:@""]; // need trailing endline for linewise
1458+
return [returnLines componentsJoinedByString:@"\n"];
15071459
}
1508-
1509-
return NO;
1460+
return nil;
15101461
}
15111462

1512-
/// Returns the currently selected text. We should consolidate this with
1513-
/// selectedTextToPasteboard: above when we have time. (That function has a
1514-
/// fast path just to query whether selected text exists)
1515-
- (NSString *)selectedText
1463+
/// Replace the selected text in visual mode with the new suppiled one.
1464+
- (oneway void)replaceSelectedText:(in bycopy NSString *)text
15161465
{
15171466
if (VIsual_active && (State & MODE_NORMAL)) {
1518-
char_u *str = extractSelectedText();
1519-
if (!str)
1520-
return nil;
1521-
1522-
if (output_conv.vc_type != CONV_NONE) {
1523-
char_u *conv_str = string_convert(&output_conv, str, NULL);
1524-
if (conv_str) {
1525-
vim_free(str);
1526-
str = conv_str;
1527-
}
1528-
}
1529-
1530-
NSString *string = [[NSString alloc] initWithUTF8String:(char*)str];
1531-
vim_free(str);
1532-
return [string autorelease];
1467+
// The only real way Vim has in doing this consistently is to use the
1468+
// register put functionality as there is no generic API for this.
1469+
// We find an arbitrary register ('0'), back it up, replace it with our
1470+
// own content, paste it in, then restore the register to old value.
1471+
yankreg_T *target_reg = get_y_register(0);
1472+
yankreg_T backup_reg = *target_reg;
1473+
target_reg->y_array = NULL;
1474+
target_reg->y_size = 0;
1475+
1476+
// If selection is blockwise, we try to match it. Only do it if input
1477+
// and selected text have same number of lines, as otherwise it could
1478+
// be awkward.
1479+
int yank_type = MAUTO;
1480+
char_u *vimtext = [text vimStringSave];
1481+
if (VIsual_mode == Ctrl_V) {
1482+
long text_lines = string_count(vimtext, (char_u*)"\n", FALSE) + 1;
1483+
1484+
linenr_T v1 = VIsual.lnum;
1485+
linenr_T v2 = curwin->w_cursor.lnum;
1486+
long num_lines = v1 > v2 ? v1 - v2 + 1 : v2 - v1 + 1;
1487+
1488+
if (text_lines == num_lines)
1489+
yank_type = MBLOCK;
1490+
}
1491+
write_reg_contents_ex('0', vimtext, -1, FALSE, yank_type, -1);
1492+
vim_free(vimtext);
1493+
1494+
oparg_T oap;
1495+
CLEAR_FIELD(oap);
1496+
oap.regname = '0';
1497+
1498+
cmdarg_T cap;
1499+
CLEAR_FIELD(cap);
1500+
cap.oap = &oap;
1501+
cap.cmdchar = 'P';
1502+
cap.count1 = 1;
1503+
1504+
nv_put(&cap);
1505+
1506+
// Clean up the temporary register, and restore the old state.
1507+
yankreg_T *old_y_current = get_y_current();
1508+
set_y_current(target_reg);
1509+
free_yank_all();
1510+
set_y_current(old_y_current);
1511+
*target_reg = backup_reg;
1512+
1513+
// nv_put does not trigger a redraw command as it's done on a higher
1514+
// level, so just do a manual one here to make sure it's done.
1515+
[self redrawScreen];
15331516
}
1534-
return nil;
15351517
}
15361518

15371519
/// Returns whether the provided mouse screen position is on a visually

src/MacVim/MMCoreTextView.m

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,7 @@ - (void)keyDown:(NSEvent *)event
648648

649649
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
650650
{
651-
// We are not currently replacementRange right now.
652-
[helper insertText:string];
651+
[helper insertText:string replacementRange:replacementRange];
653652
}
654653

655654
- (void)doCommandBySelector:(SEL)selector
@@ -1992,7 +1991,7 @@ - (void)quickLookWithEvent:(NSEvent *)event
19921991
// top of said selection and if so, show definition of that instead.
19931992
MMVimController *vc = [self vimController];
19941993
id<MMBackendProtocol> backendProxy = [vc backendProxy];
1995-
if ([backendProxy selectedTextToPasteboard:nil]) {
1994+
if ([backendProxy hasSelectedText]) {
19961995
int selRow = 0, selCol = 0;
19971996
const BOOL isMouseInSelection = [backendProxy mouseScreenposIsSelection:row column:col selRow:&selRow selCol:&selCol];
19981997

src/MacVim/MMTextView.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ - (void)keyDown:(NSEvent *)event
724724

725725
- (void)insertText:(id)string
726726
{
727-
[helper insertText:string];
727+
[helper insertText:string replacementRange:NSMakeRange(0, 0)];
728728
}
729729

730730
- (void)doCommandBySelector:(SEL)selector

src/MacVim/MMTextViewHelper.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
- (NSColor *)insertionPointColor;
6666

6767
- (void)keyDown:(NSEvent *)event;
68-
- (void)insertText:(id)string;
68+
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange;
6969
- (void)doCommandBySelector:(SEL)selector;
7070
- (void)scrollWheel:(NSEvent *)event;
7171
- (void)mouseDown:(NSEvent *)event;

src/MacVim/MMTextViewHelper.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ - (void)keyDown:(NSEvent *)event
221221
currentEvent = nil;
222222
}
223223

224-
- (void)insertText:(id)string
224+
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
225225
{
226226
if ([self hasMarkedText]) {
227227
[self sendMarkedText:nil position:0];
@@ -241,6 +241,20 @@ - (void)insertText:(id)string
241241
if ([string isKindOfClass:[NSAttributedString class]])
242242
string = [string string];
243243

244+
if (replacementRange.length > 0)
245+
{
246+
// Replacement range is a concept we don't really have a way to fulfill
247+
// as we don't have proper access to the underlying text storage. This
248+
// should usually be triggered when we have selected text though, and
249+
// so we simply ask Vim to replace the current selection with the new
250+
// text, and it should hopefully work.
251+
// Only known way of this being called is Apple Intelligence Writing
252+
// Tools.
253+
MMVimController *vc = [self vimController];
254+
[vc replaceSelectedText:string];
255+
return;
256+
}
257+
244258
//int len = [string length];
245259
//ASLogDebug(@"len=%d char[0]=%#x char[1]=%#x string='%@'", [string length],
246260
// [string characterAtIndex:0],

src/MacVim/MMVimController.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@
9292
- (NSString *)evaluateVimExpression:(NSString *)expr;
9393
- (id)evaluateVimExpressionCocoa:(NSString *)expr
9494
errorString:(NSString **)errstr;
95+
- (BOOL)hasSelectedText;
96+
- (NSString *)selectedText;
97+
- (void)replaceSelectedText:(NSString *)text;
9598
- (void)processInputQueue:(NSArray *)queue;
9699
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12_2
97100
- (NSTouchBar *)makeTouchBar;

0 commit comments

Comments
 (0)