-
Notifications
You must be signed in to change notification settings - Fork 26
/
ex.c
3568 lines (3229 loc) · 98 KB
/
ex.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
/* ex.c */
/* Copyright 1995 by Steve Kirkendall */
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_ex[] = "$Id: ex.c,v 2.244 2004/03/26 21:33:27 steve Exp $";
#endif
#if USE_PROTOTYPES
static void skipwhitespace(CHAR **refp);
static ELVBOOL parsewindowid(CHAR **refp, EXINFO *xinf);
static ELVBOOL parsebuffername(CHAR **refp, EXINFO *xinf);
static ELVBOOL parseregexp(CHAR **refp, EXINFO *xinf, long flags);
static ELVBOOL parsecommandname(CHAR **refp, EXINFO *xinf);
static void parsemulti(CHAR **refp, EXINFO *xinf);
static void parsebang(CHAR **refp, EXINFO *xinf, long flags);
static void parseplus(CHAR **refp, EXINFO *xinf, long flags);
static void parseprintflag(CHAR **refp, EXINFO *xinf, long flags, long quirks);
static void parselhs(CHAR **refp, EXINFO *xinf, long flags, long quirks);
static void parserhs(CHAR **refp, EXINFO *xinf, long flags, long quirks);
static void parsecutbuffer(CHAR **refp, EXINFO *xinf, long flags);
static void parsecount(CHAR **refp, EXINFO *xinf, long flags);
static RESULT parsenested(CHAR **refp, EXINFO *xinf);
static RESULT parsecmds(CHAR **refp, EXINFO *xinf, long flags);
static ELVBOOL parsefileargs(CHAR **refp, EXINFO *xinf, long flags);
static RESULT parse(WINDOW win, CHAR **refp, EXINFO *xinf);
static RESULT execute(EXINFO *xinf);
# ifdef FEATURE_ALIAS
static char *cname(int i);
# endif
#endif
/* Minimum number of filename pointers to allocate at a time. */
#define FILEGRAN 8
/* These are the possible arguments for a command. These may be combined
* via the bitwise OR operator to describe arbitrary combinations of these.
*/
#define a_None 0x00000000L /* no arguments */
#define a_Line 0x00000001L /* single line specifier */
#define a_Range 0x00000002L /* range of lines */
#define a_Multi 0x00000004L /* command character may be stuttered */
#define a_Bang 0x00000008L /* '!' after the command name */
#define a_Target 0x00000010L /* extra line specifier after the command name */
#define a_Lhs 0x00000020L /* single word after the command */
#define a_Rhs 0x00000040L /* extra words (after Lhs, if Lhs allowed) */
#define a_Buffer 0x00000080L /* cut buffer name */
#define a_Count 0x00000100L /* number of lines affected */
#define a_Pflag 0x00000200L /* print flag (p, l, or #) */
#define a_Plus 0x00000400L /* line offset (as for ":e +20 foo") */
#define a_Append 0x00000800L /* ">>" indicating append mode */
#define a_Filter 0x00001000L /* "!cmd", where cmd may include '|' characters */
#define a_File 0x00002000L /* a single file name */
#define a_Files 0x00004000L /* Zero or more filenames */
#define a_Cmds 0x00008000L /* text which may contain '|' characters */
#define a_RegExp 0x00010000L /* regular expression */
#define a_RegSub 0x00020000L /* substitution text (requires RegExp) */
#define a_Text 0x00040000L /* If no Rhs, following lines are part of cmd */
/* The following values indicate the default values of arguments */
#define d_All 0x00080000L /* default range is all lines */
#define d_None 0x00100000L /* default range is no lines (else current line) */
#define d_LnMask 0x00180000L /* mask value for extracting line defaults */
#define d_File 0x00200000L /* default file is current file (else no default) */
/* The following indicate other quirks of commands */
#define q_None 0x0000L /* no quirks */
#define q_Unsafe 0x0001L /* command can't be executed if "safer" */
#define q_FileUnsafe 0x0002L /* command can't take filename if "safer" */
#define q_Restricted 0x0004L /* command can't be executed if "restricted" */
#define q_FileRestr 0x0008L /* command can't take filename if "restricted"*/
#define q_Autop 0x0010L /* autoprint current line */
#define q_Zero 0x0020L /* allow Target or Line to be 0 */
#define q_Exrc 0x0040L /* command may be run in a .exrc file */
#define q_Undo 0x0080L /* save an "undo" version before this command */
#define q_Custom 0x0100L /* save options/maps after command */
#define q_Ex 0x0200L /* only allowed in ex mode, not vi's <:> cmd */
#define q_CtrlV 0x0400L /* use ^V for quote char, instead of \ */
#define q_MayQuit 0x0800L /* may cause window to disappear */
#define q_SwitchB 0x1000L /* may move cursor to a different buffer */
/* This array maps ex command names to command codes. The order in which
* command names are listed below is significant -- ambiguous abbreviations
* are always resolved to be the first possible match. (e.g. "r" is taken
* to mean "read", not "rewind", because "read" comes before "rewind")
*
* Also, commands which share the same first letter are grouped together.
* Similarly, within each one-letter group, the commands are ordered so that
* the commands with the same two letters are grouped together, and those
* groups are then divided into 3-letter groups, and so on. This allows
* the command list to be searched faster.
*
* The comment at the start of each line below gives the shortest abbreviation.
* HOWEVER, YOU CAN'T SIMPLY SORT THOSE ABBREVIATIONS to produce the correct
* order for the commands. Consider the change/chdir/calculate commands, for
* an example of why that wouldn't work.
*/
static struct
{
char *name; /* name of the command */
EXCMD code; /* enum code of the command */
RESULT (*fn) P_((EXINFO *));
/* function which executes the command */
long flags; /* command line arguments, defaults */
long quirks; /* command line quirks */
}
cmdnames[] =
{ /* cmd name cmd code function flags (describing arguments) quirks */
/*! */{"!", EX_BANG, ex_bang, a_Range | a_Cmds | d_None, q_Exrc | q_Unsafe | q_Restricted | q_Undo },
/*# */{"#", EX_NUMBER, ex_print, a_Range | a_Count | a_Pflag, q_None },
/*& */{"&", EX_SUBAGAIN, ex_substitute, a_Range | a_Rhs, q_Undo },
/*< */{"<", EX_SHIFTL, ex_shift, a_Range | a_Multi | a_Bang | a_Count, q_Autop | q_Undo },
/*= */{"=", EX_EQUAL, ex_file, a_Range },
/*> */{">", EX_SHIFTR, ex_shift, a_Range | a_Multi | a_Bang | a_Count, q_Autop | q_Undo },
#ifdef FEATURE_MISC
/*@ */{"@", EX_AT, ex_at, a_Buffer, q_None },
#endif
/*N */{"Next", EX_PREVIOUS, ex_next, a_Bang, q_SwitchB },
/*" */{"\"", EX_COMMENT, ex_comment, a_Cmds, q_Exrc },
/*a */{"append", EX_APPEND, ex_append, a_Line | a_Bang | a_Cmds | a_Text, q_Zero | q_Undo | q_Ex },
/*ab */{"abbreviate", EX_ABBR, ex_map, a_Bang | a_Lhs | a_Rhs, q_Exrc | q_Custom | q_Unsafe | q_Restricted | q_CtrlV},
#ifdef FEATURE_MISC
/*al */{"all", EX_ALL, ex_all, a_Bang | a_Cmds, q_None },
#endif
#ifdef FEATURE_ALIAS
/*ali */{"alias", EX_ALIAS, ex_alias, a_Bang | a_Lhs | a_Cmds, q_Exrc | q_Unsafe | q_Restricted | q_Custom },
#endif
/*ar */{"args", EX_ARGS, ex_args, a_Files, q_Exrc | q_FileRestr },
#ifdef FEATURE_AUTOCMD
/*au */{"autocmd", EX_AUTOCMD, ex_autocmd, a_Bang | a_Cmds, q_Exrc | q_Unsafe | q_Restricted | q_Custom },
/*aue */{"auevent", EX_AUEVENT, ex_auevent, a_Bang | a_Rhs, q_Exrc | q_Custom },
/*aug */{"augroup", EX_AUGROUP, ex_augroup, a_Bang | a_Lhs, q_Exrc },
#endif
#ifdef FEATURE_MISC
/*b */{"buffer", EX_BUFFER, ex_buffer, a_Bang | a_Rhs, q_SwitchB },
/*bb */{"bbrowse", EX_BBROWSE, ex_buffer, a_Bang, q_SwitchB },
#endif
#ifdef FEATURE_BROWSE
/*br */{"browse", EX_BROWSE, ex_browse, a_Bang|a_Rhs, q_SwitchB },
#endif
#ifdef FEATURE_MAPDB
/*bre */{"break", EX_BREAK, ex_map, a_Bang | a_Rhs, q_CtrlV },
#endif
/*c */{"change", EX_CHANGE, ex_append, a_Range | a_Bang | a_Count | a_Text, q_Undo | q_Ex },
/*cha */{"chdir", EX_CD, ex_cd, a_Bang | a_File, q_Exrc | q_Unsafe | q_Restricted},
#ifdef FEATURE_SPELL
/*che */{"check", EX_CHECK, ex_check, a_Bang | a_Rhs, q_Exrc | q_Custom },
#endif
#ifdef FEATURE_REGION
/*chr */{"chregion", EX_CHREGION, ex_region, a_Range | a_Rhs | a_Lhs, q_None },
#endif
#ifdef FEATURE_CALC
/*ca */{"calculate", EX_CALC, ex_comment, a_Cmds, q_Exrc },
/*cas */{"case", EX_CASE, ex_case, a_Lhs | a_Cmds, q_Exrc },
#endif
#ifdef FEATURE_MAKE
/*cc */{"cc", EX_CC, ex_make, a_Bang | a_Rhs, q_Unsafe | q_Restricted | q_SwitchB},
#endif
/*cd */{"cd", EX_CD, ex_cd, a_Bang | a_File, q_Exrc | q_Unsafe | q_Restricted},
/*cl */{"close", EX_CLOSE, ex_xit, a_Bang, q_MayQuit },
/*co */{"copy", EX_COPY, ex_move, a_Range | a_Target | a_Pflag, q_Autop | q_Undo },
/*col */{"color", EX_COLOR, ex_color, a_Bang | a_Lhs | a_Rhs, q_Exrc | q_Custom },
/*d */{"delete", EX_DELETE, ex_delete, a_Range | a_Buffer | a_Count | a_Pflag, q_Undo | q_Autop },
#ifdef FEATURE_CALC
/*de */{"default", EX_DEFAULT, ex_case, a_Cmds, q_Exrc },
#endif
/*di */{"display", EX_DISPLAY, ex_display, a_Rhs },
/*dig */{"digraph", EX_DIGRAPH, ex_digraph, a_Bang | a_Rhs, q_Exrc | q_Custom },
#ifdef FEATURE_CALC
/*do */{"doloop", EX_DO, ex_do, a_Cmds, q_Exrc },
#endif
#ifdef FEATURE_AUTOCMD
/*doa */{"doautocmd", EX_DOAUTOCMD, ex_doautocmd, a_Rhs, q_Exrc },
#endif
/*e */{"edit", EX_EDIT, ex_edit, a_Bang | a_Plus | a_File | d_File, q_FileRestr | q_SwitchB },
/*ec */{"echo", EX_ECHO, ex_comment, a_Rhs, q_Exrc },
/*el */{"else", EX_ELSE, ex_then, a_Cmds, q_Exrc },
#ifdef FEATURE_MAKE
/*er */{"errlist", EX_ERRLIST, ex_errlist, a_Bang | a_File | a_Filter, q_SwitchB | q_Restricted },
#endif
/*erro*/{"error", EX_ERROR, ex_message, a_Rhs, q_Exrc },
#ifdef FEATURE_CALC
/*ev */{"eval", EX_EVAL, ex_if, a_Cmds, q_Exrc },
#endif
/*ex */{"ex", EX_EDIT, ex_edit, a_Bang | a_Plus | a_File | d_File, q_FileRestr | q_SwitchB },
/*f */{"file", EX_FILE, ex_file, a_Bang | a_File, q_FileUnsafe | q_FileRestr },
#ifdef FEATURE_FOLD
/*fo */{"fold", EX_FOLD, ex_fold, a_Range | a_Bang | a_Rhs },
#endif
#ifdef FEATURE_CALC
/*for */{"foreach", EX_FOR, ex_while, a_Lhs | a_Cmds, q_Exrc },
#endif
/*g */{"global", EX_GLOBAL, ex_global, a_Range|a_Bang|a_RegExp|a_Cmds|d_All, q_Undo },
/*go */{"goto", EX_GOTO, ex_comment, a_Line, q_Zero | q_Autop | q_SwitchB },
/*gu */{"gui", EX_GUI, ex_gui, a_Cmds, q_Exrc },
#ifdef FEATURE_MISC
/*h */{"help", EX_HELP, ex_help, a_Lhs | a_Rhs, q_SwitchB },
#endif
/*i */{"insert", EX_INSERT, ex_append, a_Line | a_Bang | a_Cmds | a_Text, q_Undo | q_Ex },
#ifdef FEATURE_CALC
/*if */{"if", EX_IF, ex_if, a_Cmds, q_Exrc },
#endif
/*j */{"join", EX_JOIN, ex_join, a_Range | a_Bang | a_Count, q_Autop | q_Undo },
/*k */{"k", EX_MARK, ex_mark, a_Line | a_Lhs },
/*l */{"list", EX_LIST, ex_print, a_Range | a_Count | a_Pflag },
/*la */{"last", EX_LAST, ex_next, 0, q_SwitchB },
#ifdef FEATURE_CALC
/*le */{"let", EX_LET, ex_set, a_Bang | a_Cmds, q_Exrc | q_Custom },
#endif
#ifdef FEATURE_MISC
/*se */{"local", EX_LOCAL, ex_set, a_Rhs, q_Exrc },
#endif
#ifdef FEATURE_LPR
/*lp */{"lpr", EX_LPR, ex_lpr, a_Range|a_Bang|a_Append|a_File|a_Filter|d_All, q_Unsafe | q_Restricted },
#endif
/*m */{"move", EX_MOVE, ex_move, a_Range | a_Target, q_Autop | q_Undo },
/*ma */{"mark", EX_MARK, ex_mark, a_Line | a_Lhs },
#ifdef FEATURE_MAKE
/*mak */{"make", EX_MAKE, ex_make, a_Bang | a_Rhs, q_Unsafe | q_Restricted | q_SwitchB},
#endif
/*map */{"map", EX_MAP, ex_map, a_Bang | a_Rhs, q_Exrc | q_Custom | q_Unsafe | q_Restricted | q_CtrlV},
/*me */{"message", EX_MESSAGE, ex_message, a_Rhs, q_Exrc },
#ifdef FEATURE_MKEXRC
/*mk */{"mkexrc", EX_MKEXRC, ex_mkexrc, a_Bang | a_File, q_Unsafe | q_Restricted },
#endif
/*n */{"next", EX_NEXT, ex_next, a_Bang | a_Files, q_FileRestr | q_SwitchB },
#ifdef FEATURE_SPLIT
/*ne */{"new", EX_SNEW, ex_split, a_None, q_None },
#endif
/*no */{"normal", EX_NORMAL, ex_display, a_Range | a_Bang | a_Cmds | d_None, q_Undo },
#ifdef FEATURE_FOLD
/*nof */{"nofold", EX_NOFOLD, ex_fold, a_Range | a_Bang },
#endif
#ifdef FEATURE_HLSEARCH
/*noh */{"nohlsearch", EX_NOHLSEARCH, ex_nohlsearch, a_None, q_None },
#endif
/*nu */{"number", EX_NUMBER, ex_print, a_Range | a_Count | a_Pflag, q_None },
/*o */{"open", EX_OPEN, ex_edit, a_Bang | a_Plus | a_File, q_SwitchB | q_FileRestr },
/*on */{"only", EX_ONLY, ex_qall, a_Bang | d_None, q_None },
/*p */{"print", EX_PRINT, ex_print, a_Range | a_Count | a_Pflag, q_None },
/*pre */{"previous", EX_PREVIOUS, ex_next, a_Bang, q_SwitchB },
/*pres*/{"preserve", EX_PRESERVE, ex_qall, d_None, q_MayQuit },
/*ph */{"phelp", EX_PHELP, ex_help, a_Lhs | a_Rhs, q_SwitchB },
/*po */{"pop", EX_POP, ex_pop, a_Bang, q_SwitchB },
/*pu */{"put", EX_PUT, ex_put, a_Line | a_Buffer, q_Zero | a_Buffer | q_Undo },
/*pus */{"push", EX_PUSH, ex_edit, a_Bang | a_Plus | a_File, q_FileRestr | q_SwitchB },
/*q */{"quit", EX_QUIT, ex_xit, a_Bang, q_MayQuit },
/*qa */{"qall", EX_QALL, ex_qall, a_Bang, q_MayQuit },
/*r */{"read", EX_READ, ex_read, a_Line | a_File | a_Filter, q_Zero | q_Undo | q_Restricted },
/*red */{"redo", EX_REDO, ex_undo, a_Lhs, q_Autop },
#ifdef FEATURE_REGION
/*reg */{"region", EX_REGION, ex_region, a_Range | a_Lhs | a_Rhs, q_None },
#endif
/*rew */{"rewind", EX_REWIND, ex_next, a_Bang, q_SwitchB },
/*s */{"s", EX_SUBSTITUTE, ex_substitute, a_Range|a_RegExp|a_RegSub|a_Rhs|a_Count|a_Pflag, q_Autop|q_Undo|q_Exrc },
/*su */{"suspend", EX_SUSPEND, ex_suspend, a_Bang, q_Unsafe | q_Restricted },
/*sub */{"substitute", EX_SUBSTITUTE, ex_substitute, a_Range|a_RegExp|a_RegSub|a_Rhs|a_Count|a_Pflag, q_Autop|q_Undo|q_Exrc },
#ifdef FEATURE_SPLIT
/*sN */{"sNext", EX_SPREVIOUS, ex_next, 0, q_None },
/*sa */{"sall", EX_SALL, ex_sall, 0, q_None },
#endif
/*saf */{"safely", EX_SAFELY, ex_then, a_Cmds, q_Exrc },
#if defined(FEATURE_SPLIT) && defined(FEATURE_MISC)
/*sbb */{"sbbrowse", EX_SBBROWSE, ex_buffer, a_Bang, q_None },
#endif
#ifdef FEATURE_BROWSE
/*sbr */{"sbrowse", EX_SBROWSE, ex_browse, a_Bang|a_Rhs },
#endif
/*se */{"set", EX_SET, ex_set, a_Bang | a_Rhs, q_Exrc | q_Custom },
/*sh */{"shell", EX_SHELL, ex_suspend, 0, q_Unsafe | q_Restricted },
#ifdef FEATURE_SPLIT
/*sl */{"slast", EX_SLAST, ex_next, 0, q_None },
/*sn */{"snext", EX_SNEXT, ex_next, a_Files, q_FileRestr },
/*snew*/{"snew", EX_SNEW, ex_split, 0, q_None },
#endif
/*so */{"source", EX_SOURCE, ex_source, a_Bang | a_File | a_Filter, q_Exrc | q_Restricted },
#ifdef FEATURE_SPLIT
/*sp */{"split", EX_SPLIT, ex_split, a_Line | a_Plus | a_File | a_Filter, q_FileRestr },
/*sr */{"srewind", EX_SREWIND, ex_next, a_Bang, q_None },
#endif
/*st */{"stop", EX_STOP, ex_suspend, a_Bang, q_Unsafe | q_Restricted },
#ifdef FEATURE_SPLIT
/*sta */{"stag", EX_STAG, ex_tag, a_Rhs },
#endif
#ifdef FEATURE_MISC
/*stac*/{"stack", EX_STACK, ex_stack, d_None },
#endif
#ifdef FEATURE_CALC
/*sw */{"switch", EX_SWITCH, ex_switch, a_Cmds, q_Exrc },
#endif
/*t */{"to", EX_COPY, ex_move, a_Range | a_Target | a_Pflag, q_Autop | q_Undo },
/*ta */{"tag", EX_TAG, ex_tag, a_Bang | a_Rhs, q_SwitchB },
/*th */{"then", EX_THEN, ex_then, a_Cmds, q_Exrc },
/*try */{"try", EX_TRY, ex_then, a_Cmds, q_Exrc },
/*u */{"undo", EX_UNDO, ex_undo, a_Lhs, q_Autop },
/*una */{"unabbreviate",EX_UNABBR, ex_map, a_Bang | a_Lhs, q_Exrc | q_Custom | q_CtrlV },
#ifdef FEATURE_ALIAS
/*unal*/{"unalias", EX_UNALIAS, ex_alias, a_Lhs, q_Exrc | q_Custom },
#endif
#ifdef FEATURE_MAPDB
/*unb */{"unbreak", EX_UNBREAK, ex_map, a_Bang | a_Rhs, q_CtrlV },
#endif
#ifdef FEATURE_FOLD
/*unf */{"unfold", EX_UNFOLD, ex_fold, a_Range | a_Bang | a_Rhs },
#endif
/*unm */{"unmap", EX_UNMAP, ex_map, a_Bang | a_Rhs, q_Exrc | q_Custom | q_CtrlV },
#ifdef FEATURE_REGION
/*unr */{"unregion", EX_UNREGION, ex_region, a_Range | a_Lhs },
#endif
/*v */{"vglobal", EX_VGLOBAL, ex_global, a_Range|a_Bang|a_RegExp|a_Cmds|d_All, q_Undo },
/*ve */{"version", EX_VERSION, ex_version, 0, q_Exrc },
/*vi */{"visual", EX_VISUAL, ex_edit, a_Bang | a_Plus | a_File, q_FileRestr | q_SwitchB },
/*w */{"write", EX_WRITE, ex_write, a_Range|a_Bang|a_Append|a_File|a_Filter|d_All|d_File, q_Unsafe | q_FileRestr },
/*wa */{"warning", EX_WARNING, ex_message, a_Rhs, q_Exrc },
#ifdef FEATURE_CALC
/*wh */{"while", EX_WHILE, ex_while, a_Cmds, q_Exrc },
#endif
#ifdef FEATURE_MISC
/*wi */{"window", EX_WINDOW, ex_window, a_Lhs },
#endif
/*wn */{"wnext", EX_WNEXT, ex_next, a_Bang, q_SwitchB },
#ifdef FEATURE_SPELL
/*wo */{"words", EX_WORDS, ex_check, a_Bang | a_Rhs, q_Exrc | q_Custom },
/*wordf*/{"wordfile", EX_WORDFILE, ex_wordfile, a_Bang | a_Files, q_Exrc | q_Custom | q_FileRestr },
#endif
/*wq */{"wquit", EX_WQUIT, ex_xit, a_Bang | a_File | d_File, q_Unsafe | q_FileRestr | q_MayQuit },
/*x */{"xit", EX_XIT, ex_xit, a_Bang | a_File , q_Unsafe | q_FileRestr | q_MayQuit },
/*y */{"yank", EX_YANK, ex_delete, a_Range | a_Buffer | a_Count },
#ifdef FEATURE_MISC
/*z */{"z", EX_Z, ex_z, a_Line | a_Rhs },
#endif
/*~ */{"~", EX_SUBRECENT, ex_substitute, a_Range | a_Rhs, q_Undo },
#ifdef FEATURE_ALIAS
/*~ */{" "/*dummy name*/,EX_DOALIAS, ex_doalias, a_Range | a_Bang | a_Rhs, q_Exrc },
#endif
};
/* This variable is used for detecting nested global statements */
static int globaldepth;
/* This function discards info from an EXINFO struct. The struct itself
* is not freed, since it is usually just a local variable in some function.
*/
void exfree(xinf)
EXINFO *xinf; /* the command to be freed */
{
int i;
if (xinf->fromaddr) markfree(xinf->fromaddr);
if (xinf->toaddr) markfree(xinf->toaddr);
if (xinf->destaddr) markfree(xinf->destaddr);
if (xinf->re) safefree(xinf->re);
if (xinf->lhs) safefree(xinf->lhs);
if (xinf->rhs) safefree(xinf->rhs);
for (i = 0; i < xinf->nfiles; i++)
{
assert(xinf->file && xinf->file[i]);
safefree(xinf->file[i]);
}
if (xinf->file) safefree(xinf->file);
}
/* This function skips over blanks and tabs */
static void skipwhitespace(refp)
CHAR **refp; /* pointer to scan variable */
{
while (*refp && (**refp == ' ' || **refp == '\t'))
{
scannext(refp);
}
}
/* This function attempts to parse a window ID. If there is no window ID,
* then it leaves xinf->win unchanged; else it sets xinf->win to the given
* window. Returns ElvTrue unless there was an error.
*
* This function also sets the default buffer to the default window's state
* buffer, or the specified window's main buffer. It is assumed that
* xinf->window has already been initialized to the default window.
*/
static ELVBOOL parsewindowid(refp, xinf)
CHAR **refp; /* pointer to the (CHAR *) used for scanning command */
EXINFO *xinf; /* info about the command being parsed */
{
long num;
MARK start;
WINDOW win;
/* set default buffer, assuming default window. */
if (xinf->window->state && xinf->window->state->pop)
xinf->defaddr = *xinf->window->state->pop->cursor;
else
xinf->defaddr = *xinf->window->cursor;
if (markbuffer(&xinf->defaddr) != bufdefault)
{
xinf->defaddr.buffer = bufdefault;
xinf->defaddr.offset = 0L;
}
/* if doesn't start with a digit, ignore it */
if (!*refp || !elvdigit(**refp))
{
return ElvTrue;
}
/* convert the number */
start = scanmark(refp);
for (num = 0; *refp && elvdigit(**refp); scannext(refp))
{
num = num * 10 + **refp - '0';
}
/* if doesn't end with a ':', then it's not meant to be a window id */
if (!*refp || **refp != ':' || num <= 0)
{
scanseek(refp, start);
return ElvTrue;
}
/* eat the ':' character */
scannext(refp);
/* convert the number to a WINDOW */
for (win = winofbuf((WINDOW)0, (BUFFER)0);
win && o_windowid(win) != num;
win = winofbuf(win, (BUFFER)0))
{
}
if (!win)
{
msg(MSG_ERROR, "bad window");
return ElvFalse;
}
/* use the named window */
xinf->window = win;
xinf->defaddr = *win->cursor;
return ElvTrue;
}
/* This function attempts to parse a single buffer name. It returns True
* unless an invalid buffer is specified.
*
* If an explicit buffer is named, then it sets the default address to that
* buffer's undo point. (Else it is left set window's cursor, as set by
* the parsewindowid() function.)
*/
static ELVBOOL parsebuffername(refp, xinf)
CHAR **refp; /* pointer to the (CHAR *) used for scanning command */
EXINFO *xinf; /* info about the command being parsed */
{
CHAR *tmp;
CHAR bufname[200];/* buffer name, as a string */
int len, nest;
BUFFER buf;
/* if the first character isn't '(' then this isn't a buffer name */
if (!*refp || **refp != '(')
{
return ElvTrue;
}
/* collect the characters into buffer */
for (len = 0, nest = 1; scannext(refp) && nest > 0; )
{
/* Treat some characters specially */
if (**refp == '(')
nest++;
else if (**refp == ')')
nest--;
else if (**refp == '|' && nest == 1)
nest = 0;
else if (**refp == '\n')
break; /* and don't do scannext() in for-loop */
/* add this character, unless we hit end */
if (nest > 0)
bufname[len++] = **refp;
}
bufname[len] = '\0';
#ifdef FEATURE_CALC
/* if the name begins with a '=' then evaluate it */
if (*bufname == '=')
{
tmp = calculate(bufname + 1, NULL, CALC_ALL);
if (!tmp)
return ElvFalse;
}
else
#endif
tmp = bufname;
/* try to find the buffer */
buf = buffind(tmp);
if (!buf)
{
msg(MSG_ERROR, "[S]no buffer named $1", tmp);
return ElvFalse;
}
xinf->defaddr.buffer = buf;
marksetoffset(&xinf->defaddr, buf->docursor);
return ElvTrue;
}
/* If the command supports a regular expression, then this function parses
* that regular expression from the command line and compiles it. If the
* command also supports replacement text, then that is parsed too.
*/
static ELVBOOL parseregexp(refp, xinf, flags)
CHAR **refp; /* pointer to the (CHAR *) used for scanning command */
EXINFO *xinf; /* info about the command being parsed */
long flags; /* bitmap of command attributes */
{
CHAR delim; /* delimiter character */
CHAR *retext;/* source code of the regular expression */
/* If this command doesn't use regular expressions, then do nothing.
* (By the way, not using regexps implies that it doesn't use
* replacement text either.)
*/
if ((flags & a_RegExp) == 0)
{
return ElvTrue;
}
/* get the delimiter character. Can be any punctuation character
* except '|'. If no delimiter is given, then use an empty string.
*/
if (*refp && elvpunct(**refp) && **refp != '|')
{
/* remember the delimiter */
delim = **refp;
scannext(refp);
/* collect the characters of the regexp source code */
retext = (*refp && **refp) ? regbuild(delim, refp, ElvTrue) : NULL;
/* if an empty regexp was explicitly given (":s//" rather than
* ":s"), then remember that. We need to distinguish between
* an explicit empty regexp and an implicit one, because if
* there is no explicit regexp then there can't be any
* replacement text either.
*/
if (!retext)
{
retext = empty;
empty[0] = (CHAR)0;
}
}
else
{
retext = NULL;
delim = '\n'; /* illegal delimiter, affects a_RegSub text below */
if (xinf->command == EX_SUBSTITUTE)
xinf->command = EX_SUBAGAIN;
}
/* compile the regular expression */
xinf->re = regcomp(retext ? retext : empty,
xinf->window ? xinf->window->state->cursor : NULL);
/* We don't need the source to the regexp anymore. If the source was
* anything other than the empty string, then free its memory.
*/
if (retext && retext != empty)
safefree(retext);
/* detect errors in the regexp */
if (!xinf->re)
{
/* error message already written out by regcomp() */
return ElvFalse;
}
/* if no substitution text is needed, then we're done. */
if ((flags & a_RegSub) == 0)
{
return ElvTrue;
}
/* We do use replacement text. If there was no regexp, then the
* replacement text is implied to be "~" -- so :s with no args will
* repeat the previous replacement command.
*/
if (!retext)
{
xinf->lhs = CHARdup(toCHAR(o_magic ? "~" : "\\~"));
return ElvTrue;
}
/* Collect characters up to the next delimiter to be the replacement
* text. Same rules as the regular expression. The first delimiter
* has already been comsumed.
*/
if (delim == '\n')
{
/* no delimiter implies that there is no replacement text */
xinf->lhs = (CHAR *)safealloc(1, sizeof(CHAR));
}
else
{
xinf->lhs = regbuild(delim, refp, ElvFalse);
if (!xinf->lhs)
{
/* zero-length string, if nothing else */
xinf->lhs = (CHAR *)safealloc(1, sizeof(CHAR));
}
/* consume the closing delimiter */
if (*refp && **refp == delim)
{
scannext(refp);
}
}
return ElvTrue;
}
/* This function parses an address, and places the results in xinf->to.
* Returns ElvTrue unless there is an error.
*/
ELVBOOL exparseaddress(refp, xinf)
CHAR **refp; /* pointer to the (CHAR *) used for scanning command */
EXINFO *xinf; /* info about the command being parsed */
{
BLKNO bi; /* bufinfo block of the buffer being addressed */
long lnum; /* line number */
long delta; /* movement amount */
CHAR sign; /* '+' or '-' for delta */
long start; /* where search stared, so we can detect wrap */
long buflines;/* number of lines in buffer */
MARKBUF m; /* start of a line */
MARK mark;
CHAR ch;
regexp *re;
/* if nothing, do nothing */
if (!*refp)
return ElvTrue;
/* find the default line number */
bi = bufbufinfo(markbuffer(&xinf->defaddr));
lnum = markline(&xinf->defaddr);
xinf->fromoffset = 0;
/* parse the address */
switch (**refp)
{
case '.':
/* use the line containing the default address */
scannext(refp);
break;
case '/':
case '?':
/* if buffer is empty then fail */
if (o_bufchars(markbuffer(&xinf->defaddr)) <= 1)
{
msg(MSG_ERROR, "no match");
return ElvFalse;
}
/* parse & compile the regular expression */
delta = (**refp == '?') ? -1 : 1;
if (!parseregexp(refp, xinf, a_RegExp))
{
return ElvFalse;
}
/* allow the visual 'n' and 'N' commands to search for the
* same regexp, unless the "saveregexp" option is turned off.
*/
re = xinf->re;
if (o_saveregexp)
{
if (searchre)
{
safefree(searchre);
}
searchre = xinf->re;
searchforward = (ELVBOOL)(delta > 0);
searchhasdelta = ElvFalse;
xinf->re = NULL;
}
/* search for the regular expression */
start = lnum;
buflines = o_buflines(markbuffer(&xinf->defaddr));
do
{
/* find next line */
lnum += delta;
if (o_wrapscan)
{
if (lnum == 0)
lnum = buflines;
else if (lnum > buflines)
lnum = 1;
}
else if (lnum == 0)
{
msg(MSG_ERROR, "no match above");
return ElvFalse;
}
else if (lnum > buflines)
{
msg(MSG_ERROR, "no match below");
return ElvFalse;
}
/* see if this line contains the regexp */
if (regexec(re, marktmp(m, markbuffer(&xinf->defaddr), lowline(bi, lnum)), ElvTrue))
{
delta = 0;
}
/* if we've wrapped back to our starting point,
* then we've failed.
*/
if (lnum == start)
{
msg(MSG_ERROR, "no match");
return ElvFalse;
}
/* did the user cancel the search? */
if (guipoll(ElvFalse))
{
return ElvFalse;
}
} while (delta != 0);
/* If we get here, then we've found a match, and lnum is set
* to its line number. Good!
*/
xinf->fromoffset = (re->leavep >= 0 ? re->leavep : re->startp[0]);
break;
case '\'':
case '`':
ch = **refp;
if (!scannext(refp))
{
msg(MSG_ERROR, "incomplete mark name");
return ElvFalse;
}
#ifdef FEATURE_AUTOCMD
if (**refp == '[')
mark = autop;
else if (**refp == ']')
mark = aubottom;
else
#endif
if (**refp >= 'a' && **refp <= 'z')
mark = namedmark[**refp - 'a'];
else
{
msg(MSG_ERROR, "bad mark name");
return ElvFalse;
}
/* sanity checks */
if (!mark)
{
msg(MSG_ERROR, "[C]'$1 unset", **refp);
return ElvFalse;
}
if (markbuffer(&xinf->defaddr) != markbuffer(mark))
{
if (xinf->anyaddr)
{
msg(MSG_ERROR, "would span buffers");
return ElvFalse;
}
xinf->defaddr = *mark;
}
/* use it */
lnum = markline(mark);
if (ch == '`')
xinf->fromoffset = markoffset(mark);
scannext(refp);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
for (lnum = 0; *refp && elvdigit(**refp); scannext(refp))
{
lnum = lnum * 10 + **refp - '0';
}
if (lnum < 0 || lnum > o_buflines(markbuffer(&xinf->defaddr)))
{
msg(MSG_ERROR, "bad line number");
return ElvFalse;
}
break;
case '$':
scannext(refp);
lnum = o_buflines(markbuffer(&xinf->defaddr));
break;
#ifdef FEATURE_MISC
case '=':
/* if followed immediately by a letter or '(' then it is used
* as a computed address.
*/
if (!scannext(refp))
return ElvFalse;
if (**refp == '(' || elvalpha(**refp))
{
CHAR *expr, *value;
int nest;
CHAR string;
/* collect chars of the expression */
expr = NULL;
if (**refp == '(')
{
/* collect to matching ')' */
for (nest = 1, string = '\0';
nest > 0 && **refp && **refp != '\n';
(*refp)++)
{
/* append char to expression */
buildCHAR(&expr, **refp);
/* recognize nested () */
if (!string)
{
switch (**refp)
{
case '\'':
case '"':
string = **refp;
break;
case '(':
nest++;
break;
case ')':
nest--;
break;
}
}
else if (**refp == string)
string = '\0';
else if (**refp == '\\' && (*refp)[1] == string)
(*refp)++;
}
}
else
{
/* collect chars of option name */
while (elvalnum(**refp))
{
buildCHAR(&expr, **refp);
(*refp)++;
}
}
/* evaluate the expression */
value = calculate(expr, NULL, CALC_ALL);
safefree(expr);
if (!value)
/* error msg already given */
return ElvFalse;
/* sanity checks */
if (!calcnumber(value)
|| (lnum = CHAR2long(value)) < 0
|| lnum > o_buflines(markbuffer(&xinf->defaddr)))
{
msg(MSG_ERROR, "bad calculated line number");
return ElvFalse;
}
break;
}
/* else fall through so '=' is parsed later as a command... */
scanprev(refp);
#endif /* FEATURE_MISC */
default:
/* use the default address, but don't consume any chars */
lnum = markline(&xinf->defaddr);
}
/* followed by a delta? */
skipwhitespace(refp);
if (*refp && (**refp == '+' || **refp == '-' || elvdigit(**refp)))
{
/* delta implies whole-line addressing */
xinf->fromoffset = 0;
/* find the sign & magnitude of the delta */
sign = **refp;
if (elvdigit(sign))
sign = '+';
else
scannext(refp);
for (delta = 1; **refp == sign; scannext(refp), delta++)
{
}
if (delta == 1 && *refp && elvdigit(**refp))
{
for (delta = **refp - '0';
scannext(refp) && elvdigit(**refp);
delta = delta * 10 + **refp - '0')
{
}
}
/* add the delta to the line number */
if (sign == '+')
lnum += delta;
else
lnum -= delta;
/* if sum is invalid, complain */
if (lnum < 1 || lnum > o_buflines(markbuffer(&xinf->defaddr)))
{
msg(MSG_ERROR, "bad delta");
}
}
xinf->to = lnum;
return ElvTrue;
}
/* This parses a command name, and sets xinf->command accordingly. Returns
* ElvTrue if everything is okay, or ElvFalse if the command is unrecognized or
* was given addresses but doesn't allow addresses.
*/
static ELVBOOL parsecommandname(refp, xinf)
CHAR **refp; /* pointer to the (CHAR *) used for scanning command */
EXINFO *xinf; /* info about the command being parsed */
{
ELVBOOL anymatch;
int matches; /* number of commands that match so far */
int firstmatch; /* first command that matched */
char cmdname[20]; /* command name */
int len; /* number of characters in name so far */
MARK start; /* where the command name started */
int i;
CHAR *cp;
/* if no command, then assume either "goto" or comment */
if (!*refp || **refp == '\n' || **refp == '|')
{
strcpy(cmdname, (xinf->anyaddr || (xinf->window && markbuffer(xinf->window->cursor) != xinf->defaddr.buffer)) ? "goto" : "\"");
for (firstmatch = QTY(cmdnames) - 1;
firstmatch >= 0 && strcmp(cmdnames[firstmatch].name, cmdname);
firstmatch--)
{
}
goto Found;
}
/* begin by checking for aliases */
start = scanmark(refp);
#ifdef FEATURE_ALIAS
for (len = 0; len < QTY(cmdname) - 1 && *refp && elvalnum(**refp); scannext(refp))
{
cmdname[len++] = **refp;
}
cmdname[len] = '\0';
xinf->cmdname = exisalias(cmdname, ElvFalse);
if (xinf->cmdname)
{
xinf->cmdidx = QTY(cmdnames) - 1;
xinf->command = EX_DOALIAS;
return ElvTrue;
}
scanseek(refp, start);