/
ops.c
4339 lines (3971 loc) · 111 KB
/
ops.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
* op_change, op_yank, do_join
*/
#include "vim.h"
static void shift_block(oparg_T *oap, int amount);
static void mb_adjust_opend(oparg_T *oap);
static int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1);
// Flags for third item in "opchars".
#define OPF_LINES 1 // operator always works on lines
#define OPF_CHANGE 2 // operator changes text
/*
* The names of operators.
* IMPORTANT: Index must correspond with defines in vim.h!!!
* The third field holds OPF_ flags.
*/
static char opchars[][3] =
{
{NUL, NUL, 0}, // OP_NOP
{'d', NUL, OPF_CHANGE}, // OP_DELETE
{'y', NUL, 0}, // OP_YANK
{'c', NUL, OPF_CHANGE}, // OP_CHANGE
{'<', NUL, OPF_LINES | OPF_CHANGE}, // OP_LSHIFT
{'>', NUL, OPF_LINES | OPF_CHANGE}, // OP_RSHIFT
{'!', NUL, OPF_LINES | OPF_CHANGE}, // OP_FILTER
{'g', '~', OPF_CHANGE}, // OP_TILDE
{'=', NUL, OPF_LINES | OPF_CHANGE}, // OP_INDENT
{'g', 'q', OPF_LINES | OPF_CHANGE}, // OP_FORMAT
{':', NUL, OPF_LINES}, // OP_COLON
{'g', 'U', OPF_CHANGE}, // OP_UPPER
{'g', 'u', OPF_CHANGE}, // OP_LOWER
{'J', NUL, OPF_LINES | OPF_CHANGE}, // DO_JOIN
{'g', 'J', OPF_LINES | OPF_CHANGE}, // DO_JOIN_NS
{'g', '?', OPF_CHANGE}, // OP_ROT13
{'r', NUL, OPF_CHANGE}, // OP_REPLACE
{'I', NUL, OPF_CHANGE}, // OP_INSERT
{'A', NUL, OPF_CHANGE}, // OP_APPEND
{'z', 'f', OPF_LINES}, // OP_FOLD
{'z', 'o', OPF_LINES}, // OP_FOLDOPEN
{'z', 'O', OPF_LINES}, // OP_FOLDOPENREC
{'z', 'c', OPF_LINES}, // OP_FOLDCLOSE
{'z', 'C', OPF_LINES}, // OP_FOLDCLOSEREC
{'z', 'd', OPF_LINES}, // OP_FOLDDEL
{'z', 'D', OPF_LINES}, // OP_FOLDDELREC
{'g', 'w', OPF_LINES | OPF_CHANGE}, // OP_FORMAT2
{'g', '@', OPF_CHANGE}, // OP_FUNCTION
{Ctrl_A, NUL, OPF_CHANGE}, // OP_NR_ADD
{Ctrl_X, NUL, OPF_CHANGE}, // OP_NR_SUB
};
/*
* Translate a command name into an operator type.
* Must only be called with a valid operator name!
*/
int
get_op_type(int char1, int char2)
{
int i;
if (char1 == 'r') // ignore second character
return OP_REPLACE;
if (char1 == '~') // when tilde is an operator
return OP_TILDE;
if (char1 == 'g' && char2 == Ctrl_A) // add
return OP_NR_ADD;
if (char1 == 'g' && char2 == Ctrl_X) // subtract
return OP_NR_SUB;
if (char1 == 'z' && char2 == 'y') // OP_YANK
return OP_YANK;
for (i = 0; ; ++i)
{
if (opchars[i][0] == char1 && opchars[i][1] == char2)
break;
if (i == (int)ARRAY_LENGTH(opchars) - 1)
{
internal_error("get_op_type()");
break;
}
}
return i;
}
/*
* Return TRUE if operator "op" always works on whole lines.
*/
static int
op_on_lines(int op)
{
return opchars[op][2] & OPF_LINES;
}
#if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
/*
* Return TRUE if operator "op" changes text.
*/
int
op_is_change(int op)
{
return opchars[op][2] & OPF_CHANGE;
}
#endif
/*
* Get first operator command character.
* Returns 'g' or 'z' if there is another command character.
*/
int
get_op_char(int optype)
{
return opchars[optype][0];
}
/*
* Get second operator command character.
*/
int
get_extra_op_char(int optype)
{
return opchars[optype][1];
}
/*
* op_shift - handle a shift operation
*/
void
op_shift(oparg_T *oap, int curs_top, int amount)
{
long i;
int first_char;
int block_col = 0;
if (u_save((linenr_T)(oap->start.lnum - 1),
(linenr_T)(oap->end.lnum + 1)) == FAIL)
return;
if (oap->block_mode)
block_col = curwin->w_cursor.col;
for (i = oap->line_count; --i >= 0; )
{
first_char = *ml_get_curline();
if (first_char == NUL) // empty line
curwin->w_cursor.col = 0;
else if (oap->block_mode)
shift_block(oap, amount);
else
// Move the line right if it doesn't start with '#', 'smartindent'
// isn't set or 'cindent' isn't set or '#' isn't in 'cino'.
if (first_char != '#' || !preprocs_left())
shift_line(oap->op_type == OP_LSHIFT, p_sr, amount, FALSE);
++curwin->w_cursor.lnum;
}
changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L);
if (oap->block_mode)
{
curwin->w_cursor.lnum = oap->start.lnum;
curwin->w_cursor.col = block_col;
}
else if (curs_top) // put cursor on first line, for ">>"
{
curwin->w_cursor.lnum = oap->start.lnum;
beginline(BL_SOL | BL_FIX); // shift_line() may have set cursor.col
}
else
--curwin->w_cursor.lnum; // put cursor on last line, for ":>"
#ifdef FEAT_FOLDING
// The cursor line is not in a closed fold
foldOpenCursor();
#endif
if (oap->line_count > p_report)
{
char *op;
char *msg_line_single;
char *msg_line_plural;
if (oap->op_type == OP_RSHIFT)
op = ">";
else
op = "<";
msg_line_single = NGETTEXT("%ld line %sed %d time",
"%ld line %sed %d times", amount);
msg_line_plural = NGETTEXT("%ld lines %sed %d time",
"%ld lines %sed %d times", amount);
vim_snprintf((char *)IObuff, IOSIZE,
NGETTEXT(msg_line_single, msg_line_plural, oap->line_count),
oap->line_count, op, amount);
msg_attr_keep((char *)IObuff, 0, TRUE);
}
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
{
// Set "'[" and "']" marks.
curbuf->b_op_start = oap->start;
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
if (curbuf->b_op_end.col > 0)
--curbuf->b_op_end.col;
}
}
/*
* Shift the current line one shiftwidth left (if left != 0) or right
* leaves cursor on first blank in the line.
*/
void
shift_line(
int left,
int round,
int amount,
int call_changed_bytes) // call changed_bytes()
{
int count;
int i, j;
int sw_val = (int)get_sw_value_indent(curbuf);
count = get_indent(); // get current indent
if (round) // round off indent
{
i = count / sw_val; // number of 'shiftwidth' rounded down
j = count % sw_val; // extra spaces
if (j && left) // first remove extra spaces
--amount;
if (left)
{
i -= amount;
if (i < 0)
i = 0;
}
else
i += amount;
count = i * sw_val;
}
else // original vi indent
{
if (left)
{
count -= sw_val * amount;
if (count < 0)
count = 0;
}
else
count += sw_val * amount;
}
// Set new indent
if (State & VREPLACE_FLAG)
change_indent(INDENT_SET, count, FALSE, NUL, call_changed_bytes);
else
(void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
}
/*
* Shift one line of the current block one shiftwidth right or left.
* Leaves cursor on first character in block.
*/
static void
shift_block(oparg_T *oap, int amount)
{
int left = (oap->op_type == OP_LSHIFT);
int oldstate = State;
int total;
char_u *newp, *oldp;
int oldcol = curwin->w_cursor.col;
int sw_val = (int)get_sw_value_indent(curbuf);
int ts_val = (int)curbuf->b_p_ts;
struct block_def bd;
int incr;
colnr_T ws_vcol;
int added;
unsigned new_line_len; // the length of the line after the
// block shift
#ifdef FEAT_RIGHTLEFT
int old_p_ri = p_ri;
p_ri = 0; // don't want revins in indent
#endif
State = MODE_INSERT; // don't want MODE_REPLACE for State
block_prep(oap, &bd, curwin->w_cursor.lnum, TRUE);
if (bd.is_short)
return;
// total is number of screen columns to be inserted/removed
total = (int)((unsigned)amount * (unsigned)sw_val);
if ((total / sw_val) != amount)
return; // multiplication overflow
oldp = ml_get_curline();
if (!left)
{
int tabs = 0, spaces = 0;
chartabsize_T cts;
/*
* 1. Get start vcol
* 2. Total ws vcols
* 3. Divvy into TABs & spp
* 4. Construct new string
*/
total += bd.pre_whitesp; // all virtual WS up to & incl a split TAB
ws_vcol = bd.start_vcol - bd.pre_whitesp;
if (bd.startspaces)
{
if (has_mbyte)
{
if ((*mb_ptr2len)(bd.textstart) == 1)
++bd.textstart;
else
{
ws_vcol = 0;
bd.startspaces = 0;
}
}
else
++bd.textstart;
}
// TODO: is passing bd.textstart for start of the line OK?
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum,
bd.start_vcol, bd.textstart, bd.textstart);
for ( ; VIM_ISWHITE(*cts.cts_ptr); )
{
incr = lbr_chartabsize_adv(&cts);
total += incr;
cts.cts_vcol += incr;
}
bd.textstart = cts.cts_ptr;
bd.start_vcol = cts.cts_vcol;
clear_chartabsize_arg(&cts);
// OK, now total=all the VWS reqd, and textstart points at the 1st
// non-ws char in the block.
#ifdef FEAT_VARTABS
if (!curbuf->b_p_et)
tabstop_fromto(ws_vcol, ws_vcol + total,
ts_val, curbuf->b_p_vts_array, &tabs, &spaces);
else
spaces = total;
#else
if (!curbuf->b_p_et)
tabs = ((ws_vcol % ts_val) + total) / ts_val; // number of tabs
if (tabs > 0)
spaces = ((ws_vcol % ts_val) + total) % ts_val; // number of spp
else
spaces = total;
#endif
// if we're splitting a TAB, allow for it
bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0);
new_line_len = bd.textcol + tabs + spaces + (int)STRLEN(bd.textstart);
newp = alloc(new_line_len + 1);
if (newp == NULL)
return;
mch_memmove(newp, oldp, (size_t)bd.textcol);
vim_memset(newp + bd.textcol, TAB, (size_t)tabs);
vim_memset(newp + bd.textcol + tabs, ' ', (size_t)spaces);
// Note that STRMOVE() copies the trailing NUL.
STRMOVE(newp + bd.textcol + tabs + spaces, bd.textstart);
}
else // left
{
colnr_T destination_col; // column to which text in block will
// be shifted
char_u *verbatim_copy_end; // end of the part of the line which is
// copied verbatim
colnr_T verbatim_copy_width;// the (displayed) width of this part
// of line
unsigned fill; // nr of spaces that replace a TAB
size_t block_space_width;
size_t shift_amount;
char_u *non_white = bd.textstart;
colnr_T non_white_col;
chartabsize_T cts;
/*
* Firstly, let's find the first non-whitespace character that is
* displayed after the block's start column and the character's column
* number. Also, let's calculate the width of all the whitespace
* characters that are displayed in the block and precede the searched
* non-whitespace character.
*/
// If "bd.startspaces" is set, "bd.textstart" points to the character,
// the part of which is displayed at the block's beginning. Let's start
// searching from the next character.
if (bd.startspaces)
MB_PTR_ADV(non_white);
// The character's column is in "bd.start_vcol".
non_white_col = bd.start_vcol;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum,
non_white_col, bd.textstart, non_white);
while (VIM_ISWHITE(*cts.cts_ptr))
{
incr = lbr_chartabsize_adv(&cts);
cts.cts_vcol += incr;
}
non_white_col = cts.cts_vcol;
non_white = cts.cts_ptr;
clear_chartabsize_arg(&cts);
block_space_width = non_white_col - oap->start_vcol;
// We will shift by "total" or "block_space_width", whichever is less.
shift_amount = (block_space_width < (size_t)total
? block_space_width : (size_t)total);
// The column to which we will shift the text.
destination_col = (colnr_T)(non_white_col - shift_amount);
// Now let's find out how much of the beginning of the line we can
// reuse without modification.
verbatim_copy_end = bd.textstart;
verbatim_copy_width = bd.start_vcol;
// If "bd.startspaces" is set, "bd.textstart" points to the character
// preceding the block. We have to subtract its width to obtain its
// column number.
if (bd.startspaces)
verbatim_copy_width -= bd.start_char_vcols;
init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width,
bd.textstart, verbatim_copy_end);
while (cts.cts_vcol < destination_col)
{
incr = lbr_chartabsize(&cts);
if (cts.cts_vcol + incr > destination_col)
break;
cts.cts_vcol += incr;
MB_PTR_ADV(cts.cts_ptr);
}
verbatim_copy_width = cts.cts_vcol;
verbatim_copy_end = cts.cts_ptr;
clear_chartabsize_arg(&cts);
// If "destination_col" is different from the width of the initial
// part of the line that will be copied, it means we encountered a tab
// character, which we will have to partly replace with spaces.
fill = destination_col - verbatim_copy_width;
// The replacement line will consist of:
// - the beginning of the original line up to "verbatim_copy_end",
// - "fill" number of spaces,
// - the rest of the line, pointed to by non_white.
new_line_len = (unsigned)(verbatim_copy_end - oldp)
+ fill
+ (unsigned)STRLEN(non_white);
newp = alloc(new_line_len + 1);
if (newp == NULL)
return;
mch_memmove(newp, oldp, (size_t)(verbatim_copy_end - oldp));
vim_memset(newp + (verbatim_copy_end - oldp), ' ', (size_t)fill);
// Note that STRMOVE() copies the trailing NUL.
STRMOVE(newp + (verbatim_copy_end - oldp) + fill, non_white);
}
// replace the line
added = new_line_len - (int)STRLEN(oldp);
ml_replace(curwin->w_cursor.lnum, newp, FALSE);
inserted_bytes(curwin->w_cursor.lnum, bd.textcol, added);
State = oldstate;
curwin->w_cursor.col = oldcol;
#ifdef FEAT_RIGHTLEFT
p_ri = old_p_ri;
#endif
}
/*
* Insert string "s" (b_insert ? before : after) block :AKelly
* Caller must prepare for undo.
*/
static void
block_insert(
oparg_T *oap,
char_u *s,
int b_insert,
struct block_def *bdp)
{
int ts_val;
int count = 0; // extra spaces to replace a cut TAB
int spaces = 0; // non-zero if cutting a TAB
colnr_T offset; // pointer along new line
colnr_T startcol; // column where insert starts
unsigned s_len; // STRLEN(s)
char_u *newp, *oldp; // new, old lines
linenr_T lnum; // loop var
int oldstate = State;
State = MODE_INSERT; // don't want MODE_REPLACE for State
s_len = (unsigned)STRLEN(s);
for (lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++)
{
block_prep(oap, bdp, lnum, TRUE);
if (bdp->is_short && b_insert)
continue; // OP_INSERT, line ends before block start
oldp = ml_get(lnum);
if (b_insert)
{
ts_val = bdp->start_char_vcols;
spaces = bdp->startspaces;
if (spaces != 0)
count = ts_val - 1; // we're cutting a TAB
offset = bdp->textcol;
}
else // append
{
ts_val = bdp->end_char_vcols;
if (!bdp->is_short) // spaces = padding after block
{
spaces = (bdp->endspaces ? ts_val - bdp->endspaces : 0);
if (spaces != 0)
count = ts_val - 1; // we're cutting a TAB
offset = bdp->textcol + bdp->textlen - (spaces != 0);
}
else // spaces = padding to block edge
{
// if $ used, just append to EOL (ie spaces==0)
if (!bdp->is_MAX)
spaces = (oap->end_vcol - bdp->end_vcol) + 1;
count = spaces;
offset = bdp->textcol + bdp->textlen;
}
}
if (has_mbyte && spaces > 0)
// avoid copying part of a multi-byte character
offset -= (*mb_head_off)(oldp, oldp + offset);
if (spaces < 0) // can happen when the cursor was moved
spaces = 0;
// Make sure the allocated size matches what is actually copied below.
newp = alloc(STRLEN(oldp) + spaces + s_len
+ (spaces > 0 && !bdp->is_short ? ts_val - spaces : 0)
+ count + 1);
if (newp == NULL)
continue;
// copy up to shifted part
mch_memmove(newp, oldp, (size_t)offset);
oldp += offset;
// insert pre-padding
vim_memset(newp + offset, ' ', (size_t)spaces);
startcol = offset + spaces;
// copy the new text
mch_memmove(newp + startcol, s, (size_t)s_len);
offset += s_len;
if (spaces > 0 && !bdp->is_short)
{
if (*oldp == TAB)
{
// insert post-padding
vim_memset(newp + offset + spaces, ' ',
(size_t)(ts_val - spaces));
// we're splitting a TAB, don't copy it
oldp++;
// We allowed for that TAB, remember this now
count++;
}
else
// Not a TAB, no extra spaces
count = spaces;
}
if (spaces > 0)
offset += count;
STRMOVE(newp + offset, oldp);
ml_replace(lnum, newp, FALSE);
if (b_insert)
// correct any text properties
inserted_bytes(lnum, startcol, s_len);
if (lnum == oap->end.lnum)
{
// Set "']" mark to the end of the block instead of the end of
// the insert in the first line.
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = offset;
}
} // for all lnum
changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L);
State = oldstate;
}
/*
* Handle a delete operation.
*
* Return FAIL if undo failed, OK otherwise.
*/
int
op_delete(oparg_T *oap)
{
int n;
linenr_T lnum;
char_u *ptr;
char_u *newp, *oldp;
struct block_def bd;
linenr_T old_lcount = curbuf->b_ml.ml_line_count;
int did_yank = FALSE;
if (curbuf->b_ml.ml_flags & ML_EMPTY) // nothing to do
return OK;
// Nothing to delete, return here. Do prepare undo, for op_change().
if (oap->empty)
return u_save_cursor();
if (!curbuf->b_p_ma)
{
emsg(_(e_cannot_make_changes_modifiable_is_off));
return FAIL;
}
if (VIsual_select && oap->is_VIsual)
// use register given with CTRL_R, defaults to zero
oap->regname = VIsual_select_reg;
#ifdef FEAT_CLIPBOARD
adjust_clip_reg(&oap->regname);
#endif
if (has_mbyte)
mb_adjust_opend(oap);
/*
* Imitate the strange Vi behaviour: If the delete spans more than one
* line and motion_type == MCHAR and the result is a blank line, make the
* delete linewise. Don't do this for the change command or Visual mode.
*/
if ( oap->motion_type == MCHAR
&& !oap->is_VIsual
&& !oap->block_mode
&& oap->line_count > 1
&& oap->motion_force == NUL
&& oap->op_type == OP_DELETE)
{
ptr = ml_get(oap->end.lnum) + oap->end.col;
if (*ptr != NUL)
ptr += oap->inclusive;
ptr = skipwhite(ptr);
if (*ptr == NUL && inindent(0))
oap->motion_type = MLINE;
}
/*
* Check for trying to delete (e.g. "D") in an empty line.
* Note: For the change operator it is ok.
*/
if ( oap->motion_type == MCHAR
&& oap->line_count == 1
&& oap->op_type == OP_DELETE
&& *ml_get(oap->start.lnum) == NUL)
{
/*
* It's an error to operate on an empty region, when 'E' included in
* 'cpoptions' (Vi compatible).
*/
if (virtual_op)
// Virtual editing: Nothing gets deleted, but we set the '[ and ']
// marks as if it happened.
goto setmarks;
if (vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL)
beep_flush();
return OK;
}
/*
* Do a yank of whatever we're about to delete.
* If a yank register was specified, put the deleted text into that
* register. For the black hole register '_' don't yank anything.
*/
if (oap->regname != '_')
{
if (oap->regname != 0)
{
// check for read-only register
if (!valid_yank_reg(oap->regname, TRUE))
{
beep_flush();
return OK;
}
get_yank_register(oap->regname, TRUE); // yank into specif'd reg.
if (op_yank(oap, TRUE, FALSE) == OK) // yank without message
did_yank = TRUE;
}
else
reset_y_append(); // not appending to unnamed register
/*
* Put deleted text into register 1 and shift number registers if the
* delete contains a line break, or when using a specific operator (Vi
* compatible)
*/
if (oap->motion_type == MLINE || oap->line_count > 1
|| oap->use_reg_one)
{
shift_delete_registers();
if (op_yank(oap, TRUE, FALSE) == OK)
did_yank = TRUE;
}
// Yank into small delete register when no named register specified
// and the delete is within one line.
if ((
#ifdef FEAT_CLIPBOARD
((clip_unnamed & CLIP_UNNAMED) && oap->regname == '*') ||
((clip_unnamed & CLIP_UNNAMED_PLUS) && oap->regname == '+') ||
#endif
oap->regname == 0) && oap->motion_type != MLINE
&& oap->line_count == 1)
{
oap->regname = '-';
get_yank_register(oap->regname, TRUE);
if (op_yank(oap, TRUE, FALSE) == OK)
did_yank = TRUE;
oap->regname = 0;
}
/*
* If there's too much stuff to fit in the yank register, then get a
* confirmation before doing the delete. This is crude, but simple.
* And it avoids doing a delete of something we can't put back if we
* want.
*/
if (!did_yank)
{
int msg_silent_save = msg_silent;
msg_silent = 0; // must display the prompt
n = ask_yesno((char_u *)_("cannot yank; delete anyway"), TRUE);
msg_silent = msg_silent_save;
if (n != 'y')
{
emsg(_(e_command_aborted));
return FAIL;
}
}
#if defined(FEAT_EVAL)
if (did_yank && has_textyankpost())
yank_do_autocmd(oap, get_y_current());
#endif
}
/*
* block mode delete
*/
if (oap->block_mode)
{
if (u_save((linenr_T)(oap->start.lnum - 1),
(linenr_T)(oap->end.lnum + 1)) == FAIL)
return FAIL;
for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; ++lnum)
{
block_prep(oap, &bd, lnum, TRUE);
if (bd.textlen == 0) // nothing to delete
continue;
// Adjust cursor position for tab replaced by spaces and 'lbr'.
if (lnum == curwin->w_cursor.lnum)
{
curwin->w_cursor.col = bd.textcol + bd.startspaces;
curwin->w_cursor.coladd = 0;
}
// "n" == number of chars deleted
// If we delete a TAB, it may be replaced by several characters.
// Thus the number of characters may increase!
n = bd.textlen - bd.startspaces - bd.endspaces;
oldp = ml_get(lnum);
newp = alloc(STRLEN(oldp) + 1 - n);
if (newp == NULL)
continue;
// copy up to deleted part
mch_memmove(newp, oldp, (size_t)bd.textcol);
// insert spaces
vim_memset(newp + bd.textcol, ' ',
(size_t)(bd.startspaces + bd.endspaces));
// copy the part after the deleted part
oldp += bd.textcol + bd.textlen;
STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp);
// replace the line
ml_replace(lnum, newp, FALSE);
#ifdef FEAT_PROP_POPUP
if (curbuf->b_has_textprop && n != 0)
adjust_prop_columns(lnum, bd.textcol, -n, 0);
#endif
}
check_cursor_col();
changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
oap->end.lnum + 1, 0L);
oap->line_count = 0; // no lines deleted
}
else if (oap->motion_type == MLINE)
{
if (oap->op_type == OP_CHANGE)
{
// Delete the lines except the first one. Temporarily move the
// cursor to the next line. Save the current line number, if the
// last line is deleted it may be changed.
if (oap->line_count > 1)
{
lnum = curwin->w_cursor.lnum;
++curwin->w_cursor.lnum;
del_lines((long)(oap->line_count - 1), TRUE);
curwin->w_cursor.lnum = lnum;
}
if (u_save_cursor() == FAIL)
return FAIL;
if (curbuf->b_p_ai) // don't delete indent
{
beginline(BL_WHITE); // cursor on first non-white
did_ai = TRUE; // delete the indent when ESC hit
ai_col = curwin->w_cursor.col;
}
else
beginline(0); // cursor in column 0
truncate_line(FALSE); // delete the rest of the line
// leave cursor past last char in line
if (oap->line_count > 1)
u_clearline(); // "U" command not possible after "2cc"
}
else
{
del_lines(oap->line_count, TRUE);
beginline(BL_WHITE | BL_FIX);
u_clearline(); // "U" command not possible after "dd"
}
}
else
{
if (virtual_op)
{
int endcol = 0;
// For virtualedit: break the tabs that are partly included.
if (gchar_pos(&oap->start) == '\t')
{
if (u_save_cursor() == FAIL) // save first line for undo
return FAIL;
if (oap->line_count == 1)
endcol = getviscol2(oap->end.col, oap->end.coladd);
coladvance_force(getviscol2(oap->start.col, oap->start.coladd));
oap->start = curwin->w_cursor;
if (oap->line_count == 1)
{
coladvance(endcol);
oap->end.col = curwin->w_cursor.col;
oap->end.coladd = curwin->w_cursor.coladd;
curwin->w_cursor = oap->start;
}
}
// Break a tab only when it's included in the area.
if (gchar_pos(&oap->end) == '\t'
&& (int)oap->end.coladd < oap->inclusive)
{
// save last line for undo
if (u_save((linenr_T)(oap->end.lnum - 1),
(linenr_T)(oap->end.lnum + 1)) == FAIL)
return FAIL;
curwin->w_cursor = oap->end;
coladvance_force(getviscol2(oap->end.col, oap->end.coladd));
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
}
if (has_mbyte)
mb_adjust_opend(oap);
}
if (oap->line_count == 1) // delete characters within one line
{
if (u_save_cursor() == FAIL) // save line for undo
return FAIL;
// if 'cpoptions' contains '$', display '$' at end of change
if ( vim_strchr(p_cpo, CPO_DOLLAR) != NULL
&& oap->op_type == OP_CHANGE
&& oap->end.lnum == curwin->w_cursor.lnum
&& !oap->is_VIsual)
display_dollar(oap->end.col - !oap->inclusive);
n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
if (virtual_op)
{
// fix up things for virtualedit-delete:
// break the tabs which are going to get in our way
char_u *curline = ml_get_curline();
int len = (int)STRLEN(curline);
if (oap->end.coladd != 0
&& (int)oap->end.col >= len - 1
&& !(oap->start.coladd && (int)oap->end.col >= len - 1))
n++;
// Delete at least one char (e.g, when on a control char).
if (n == 0 && oap->start.coladd != oap->end.coladd)
n = 1;
// When deleted a char in the line, reset coladd.
if (gchar_cursor() != NUL)
curwin->w_cursor.coladd = 0;
}
(void)del_bytes((long)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
}
else // delete characters between lines
{
pos_T curpos;
// save deleted and changed lines for undo
if (u_save((linenr_T)(curwin->w_cursor.lnum - 1),
(linenr_T)(curwin->w_cursor.lnum + oap->line_count)) == FAIL)
return FAIL;
truncate_line(TRUE); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
++curwin->w_cursor.lnum;
del_lines((long)(oap->line_count - 2), FALSE);
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((long)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, FALSE, FALSE, FALSE, FALSE);
}
if (oap->op_type == OP_DELETE)
auto_format(FALSE, TRUE);
}
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
setmarks:
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
{
if (oap->block_mode)
{
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = oap->start.col;
}
else
curbuf->b_op_end = oap->start;
curbuf->b_op_start = oap->start;
}
return OK;
}
/*
* Adjust end of operating area for ending on a multi-byte character.
* Used for deletion.
*/
static void
mb_adjust_opend(oparg_T *oap)
{
char_u *p;
if (oap->inclusive)
{
p = ml_get(oap->end.lnum);
oap->end.col += mb_tail_off(p, p + oap->end.col);
}
}
/*
* Replace the character under the cursor with "c".