1 /*******************************************************************************
2 *                                                                              *
3 * macro.c -- Macro file processing, learn/replay, and built-in macro           *
4 *            subroutines                                                       *
5 *                                                                              *
6 * Copyright (C) 1999 Mark Edel                                                 *
7 *                                                                              *
8 * This is free software; you can redistribute it and/or modify it under the    *
9 * terms of the GNU General Public License as published by the Free Software    *
10 * Foundation; either version 2 of the License, or (at your option) any later   *
11 * version. In addition, you may distribute versions of this program linked to  *
12 * Motif or Open Motif. See README for details.                                 *
13 *                                                                              *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
16 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License        *
17 * for more details.                                                            *
18 *                                                                              *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple     *
21 * Place, Suite 330, Boston, MA  02111-1307 USA                                 *
22 *                                                                              *
23 * Nirvana Text Editor                                                          *
24 * April, 1997                                                                  *
25 *                                                                              *
26 * Written by Mark Edel                                                         *
27 *                                                                              *
28 *******************************************************************************/
29 
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
33 
34 #include "macro.h"
35 #include "textBuf.h"
36 #include "text.h"
37 #include "nedit.h"
38 #include "window.h"
39 #include "preferences.h"
40 #include "interpret.h"
41 #include "parse.h"
42 #include "search.h"
43 #include "server.h"
44 #include "shell.h"
45 #include "smartIndent.h"
46 #include "userCmds.h"
47 #include "selection.h"
48 #include "../util/rbTree.h"
49 #include "tags.h"
50 #include "calltips.h"
51 #include "../util/DialogF.h"
52 #include "../util/misc.h"
53 #include "../util/fileUtils.h"
54 #include "../util/utils.h"
55 #include "../util/getfiles.h"
56 #include "highlight.h"
57 #include "highlightData.h"
58 #include "rangeset.h"
59 #include "../util/nedit_malloc.h"
60 
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <ctype.h>
65 #include <errno.h>
66 #ifdef VMS
67 #include <limits.h>
68 #include "../util/VMSparam.h"
69 #include <types.h>
70 #include <stat.h>
71 #include <unixio.h>
72 #else
73 #include <sys/types.h>
74 #include <sys/stat.h>
75 #ifndef __MVS__
76 #include <sys/param.h>
77 #endif
78 #include <fcntl.h>
79 #endif /*VMS*/
80 #include <inttypes.h>
81 
82 #include <X11/Intrinsic.h>
83 #include <X11/keysym.h>
84 #include <Xm/Xm.h>
85 #include <Xm/CutPaste.h>
86 #include <Xm/Form.h>
87 #include <Xm/RowColumn.h>
88 #include <Xm/LabelG.h>
89 #include <Xm/List.h>
90 #include <Xm/ToggleB.h>
91 #include <Xm/DialogS.h>
92 #include <Xm/MessageB.h>
93 #include <Xm/SelectioB.h>
94 #include <Xm/PushB.h>
95 #include <Xm/Text.h>
96 #include <Xm/Separator.h>
97 
98 #ifdef HAVE_DEBUG_H
99 #include "../debug.h"
100 #endif
101 
102 /* Maximum number of actions in a macro and args in
103    an action (to simplify the reader) */
104 #define MAX_MACRO_ACTIONS 1024
105 #define MAX_ACTION_ARGS 40
106 
107 /* How long to wait (msec) before putting up Macro Command banner */
108 #define BANNER_WAIT_TIME 6000
109 
110 /* The following definitions cause an exit from the macro with a message */
111 /* added if (1) to remove compiler warnings on solaris */
112 #define M_FAILURE(s)  do { *errMsg = s; if (1) return False; } while (0)
113 #define M_STR_ALLOC_ASSERT(xDV) do { if (xDV.tag == STRING_TAG && !xDV.val.str.rep) { *errMsg = "Failed to allocate value: %s"; return(False); } } while (0)
114 #define M_ARRAY_INSERT_FAILURE() M_FAILURE("array element failed to insert: %s")
115 
116 /* Data attached to window during shell command execution with
117    information for controling and communicating with the process */
118 typedef struct {
119     XtIntervalId bannerTimeoutID;
120     XtWorkProcId continueWorkProcID;
121     char bannerIsUp;
122     char closeOnCompletion;
123     Program *program;
124     RestartData *context;
125     Widget dialog;
126 } macroCmdInfo;
127 
128 /* Widgets and global data for Repeat dialog */
129 typedef struct {
130     WindowInfo *forWindow;
131     char *lastCommand;
132     Widget shell, repeatText, lastCmdToggle;
133     Widget inSelToggle, toEndToggle;
134 } repeatDialog;
135 
136 static void cancelLearn(void);
137 static void runMacro(WindowInfo *window, Program *prog);
138 static void finishMacroCmdExecution(WindowInfo *window);
139 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData);
140 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData);
141 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event);
142 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData);
143 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
144 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
145 	XEvent *event, String *params, Cardinal *numParams);
146 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
147 	XEvent *event, String *params, Cardinal *numParams);
148 static char *actionToString(Widget w, char *actionName, XEvent *event,
149 	String *params, Cardinal numParams);
150 static int isMouseAction(const char *action);
151 static int isRedundantAction(const char *action);
152 static int isIgnoredAction(const char *action);
153 static int readCheckMacroString(Widget dialogParent, char *string,
154 	WindowInfo *runWindow, const char *errIn, char **errPos);
155 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id);
156 static Boolean continueWorkProc(XtPointer clientData);
157 static int escapeStringChars(char *fromString, char *toString);
158 static int escapedStringLength(char *string);
159 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
160     	DataValue *result, char **errMsg);
161 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
162     	DataValue *result, char **errMsg);
163 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
164     	DataValue *result, char **errMsg);
165 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
166       DataValue *result, char **errMsg);
167 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
168     	DataValue *result, char **errMsg);
169 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
170     	DataValue *result, char **errMsg);
171 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
172     	DataValue *result, char **errMsg);
173 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
174     	DataValue *result, char **errMsg);
175 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
176     	DataValue *result, char **errMsg);
177 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
178     	DataValue *result, char **errMsg);
179 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
180     	DataValue *result, char **errMsg);
181 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
182     	DataValue *result, char **errMsg);
183 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
184     	DataValue *result, char **errMsg);
185 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
186     	DataValue *result, char **errMsg);
187 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
188     	DataValue *result, char **errMsg);
189 static int writeOrAppendFile(int append, WindowInfo *window,
190     	DataValue *argList, int nArgs, DataValue *result, char **errMsg);
191 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
192     	DataValue *result, char **errMsg);
193 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
194     	DataValue *result, char **errMsg);
195 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
196     	DataValue *result, char **errMsg);
197 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
198     	DataValue *result, char **errMsg);
199 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
200     	DataValue *result, char **errMsg);
201 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
202     	DataValue *result, char **errMsg);
203 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
204     	DataValue *result, char **errMsg);
205 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
206     	DataValue *result, char **errMsg);
207 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
208     	DataValue *result, char **errMsg);
209 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
210     	DataValue *result, char **errMsg);
211 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
212     	DataValue *result, char **errMsg);
213 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
214     	DataValue *result, char **errMsg);
215 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
216     	DataValue *result, char **errMsg);
217 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
218     	DataValue *result, char **errMsg);
219 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
220     	DataValue *result, char **errMsg);
221 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData);
222 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData);
223 #ifdef LESSTIF_VERSION
224 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
225     	Boolean *cont);
226 #endif /* LESSTIF_VERSION */
227 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
228     	DataValue *result, char **errMsg);
229 static void stringDialogBtnCB(Widget w, XtPointer clientData,
230     	XtPointer callData);
231 static void stringDialogCloseCB(Widget w, XtPointer clientData,
232     	XtPointer callData);
233 #ifdef LESSTIF_VERSION
234 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
235     	Boolean *cont);
236 #endif /* LESSTIF_VERSION */
237 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
238        DataValue *result, char **errMsg);
239 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
240        DataValue *result, char **errMsg);
241 /* T Balinski */
242 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
243 	DataValue *result, char **errMsg);
244 static void listDialogBtnCB(Widget w, XtPointer clientData,
245 	XtPointer callData);
246 static void listDialogCloseCB(Widget w, XtPointer clientData,
247 	XtPointer callData);
248 /* T Balinski End */
249 #ifdef LESSTIF_VERSION
250 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
251     	Boolean *cont);
252 #endif /* LESSTIF_VERSION */
253 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
254     	DataValue *result, char **errMsg);
255 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
256     	DataValue *result, char **errMsg);
257 /* DISASBLED for 5.4
258 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
259 	int nArgs, DataValue *result, char **errMsg);
260 */
261 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
262     	DataValue *result, char **errMsg);
263 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
264         DataValue *result, char **errMsg);
265 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
266         DataValue *result, char **errMsg);
267 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
268     	DataValue *result, char **errMsg);
269 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
270     	DataValue *result, char **errMsg);
271 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
272     	DataValue *result, char **errMsg);
273 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
274     	DataValue *result, char **errMsg);
275 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
276     	DataValue *result, char **errMsg);
277 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
278     	DataValue *result, char **errMsg);
279 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
280     	DataValue *result, char **errMsg);
281 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
282     DataValue *result, char **errMsg);
283 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
284     DataValue *result, char **errMsg);
285 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
286     DataValue *result, char **errMsg);
287 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
288     DataValue *result, char **errMsg);
289 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
290     DataValue *result, char **errMsg);
291 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
292     DataValue *result, char **errMsg);
293 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
294     DataValue *result, char **errMsg);
295 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
296     DataValue *result, char **errMsg);
297 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
298     DataValue *result, char **errMsg);
299 static int matchSyntaxBasedMV(WindowInfo *window, DataValue *argList, int nArgs,
300     DataValue *result, char **errMsg);
301 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
302     DataValue *result, char **errMsg);
303 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
304     DataValue *result, char **errMsg);
305 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
306     DataValue *result, char **errMsg);
307 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
308     DataValue *result, char **errMsg);
309 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
310     DataValue *result, char **errMsg);
311 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
312     DataValue *result, char **errMsg);
313 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
314     DataValue *result, char **errMsg);
315 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
316     DataValue *result, char **errMsg);
317 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
318     DataValue *result, char **errMsg);
319 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
320     DataValue *result, char **errMsg);
321 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
322     DataValue *result, char **errMsg);
323 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
324     	DataValue *result, char **errMsg);
325 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
326     DataValue *result, char **errMsg);
327 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
328     DataValue *result, char **errMsg);
329 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
330     DataValue *result, char **errMsg);
331 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
332     DataValue *result, char **errMsg);
333 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
334     DataValue *result, char **errMsg);
335 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
336     DataValue *result, char **errMsg);
337 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
338     DataValue *result, char **errMsg);
339 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
340     	DataValue *result, char **errMsg);
341 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
342     	DataValue *result, char **errMsg);
343 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
344     	DataValue *result, char **errMsg);
345 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
346     	DataValue *result, char **errMsg);
347 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
348     	DataValue *result, char **errMsg);
349 static int calltipIDMV(WindowInfo *window, DataValue *argList, int nArgs,
350     	DataValue *result, char **errMsg);
351 static int readSearchArgs(DataValue *argList, int nArgs, int*searchDirection,
352 	int *searchType, int *wrap, char **errMsg);
353 static int wrongNArgsErr(char **errMsg);
354 static int tooFewArgsErr(char **errMsg);
355 static int strCaseCmp(char *str1, char *str2);
356 static int readIntArg(DataValue dv, int *result, char **errMsg);
357 static int readStringArg(DataValue dv, char **result, char *stringStorage,
358     	char **errMsg);
359 /* DISABLED FOR 5.4
360 static int backlightStringMV(WindowInfo *window, DataValue *argList,
361 	int nArgs, DataValue *result, char **errMsg);
362 */
363 static int rangesetListMV(WindowInfo *window, DataValue *argList,
364 	int nArgs, DataValue *result, char **errMsg);
365 static int versionMV(WindowInfo* window, DataValue* argList, int nArgs,
366         DataValue* result, char** errMsg);
367 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
368       DataValue *result, char **errMsg);
369 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
370       DataValue *result, char **errMsg);
371 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
372       DataValue *result, char **errMsg);
373 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
374       DataValue *result, char **errMsg);
375 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
376       DataValue *result, char **errMsg);
377 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
378       DataValue *result, char **errMsg);
379 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
380       DataValue *result, char **errMsg);
381 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
382       DataValue *result, char **errMsg);
383 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
384       int nArgs, DataValue *result, char **errMsg);
385 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
386       int nArgs, DataValue *result, char **errMsg);
387 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
388       int nArgs, DataValue *result, char **errMsg);
389 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
390       int nArgs, DataValue *result, char **errMsg);
391 
392 static int fillPatternResult(DataValue *result, char **errMsg, WindowInfo *window,
393         char *patternName, Boolean preallocatedPatternName, Boolean includeName,
394         char *styleName, int bufferPos);
395 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
396         DataValue *result, char **errMsg);
397 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
398         DataValue *result, char **errMsg);
399 
400 static int fillStyleResult(DataValue *result, char **errMsg,
401         WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
402         Boolean includeName, int patCode, int bufferPos);
403 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
404         DataValue *result, char **errMsg);
405 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
406         DataValue *result, char **errMsg);
407 static int filenameDialogMS(WindowInfo* window, DataValue* argList, int nArgs,
408         DataValue* result, char** errMsg);
409 
410 /* Built-in subroutines and variables for the macro language */
411 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS,
412         dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS,
413         setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS,
414         searchStringMS, substringMS, replaceSubstringMS, readFileMS,
415         writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS,
416         replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS,
417         shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS,
418         tolowerMS, listDialogMS, getenvMS,
419         stringCompareMS, splitMS, calltipMS, killCalltipMS,
420 /* DISABLED for 5.4        setBacklightStringMS,*/
421         rangesetCreateMS, rangesetDestroyMS,
422         rangesetAddMS, rangesetSubtractMS, rangesetInvertMS,
423         rangesetInfoMS, rangesetRangeMS, rangesetIncludesPosMS,
424         rangesetSetColorMS, rangesetSetNameMS, rangesetSetModeMS,
425         rangesetGetByNameMS,
426         getPatternByNameMS, getPatternAtPosMS,
427         getStyleByNameMS, getStyleAtPosMS, filenameDialogMS
428     };
429 #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs)
430 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print",
431         "dialog", "string_dialog", "replace_range", "replace_selection",
432         "set_cursor_pos", "get_character", "min", "max", "search",
433         "search_string", "substring", "replace_substring", "read_file",
434         "write_file", "append_file", "beep", "get_selection", "valid_number",
435         "replace_in_string", "select", "select_rectangle", "focus_window",
436         "shell_command", "string_to_clipboard", "clipboard_to_string",
437         "toupper", "tolower", "list_dialog", "getenv",
438         "string_compare", "split", "calltip", "kill_calltip",
439 /* DISABLED for 5.4        "set_backlight_string", */
440         "rangeset_create", "rangeset_destroy",
441         "rangeset_add", "rangeset_subtract", "rangeset_invert",
442         "rangeset_info", "rangeset_range", "rangeset_includes",
443         "rangeset_set_color", "rangeset_set_name", "rangeset_set_mode",
444         "rangeset_get_by_name",
445         "get_pattern_by_name", "get_pattern_at_pos",
446         "get_style_by_name", "get_style_at_pos", "filename_dialog"
447     };
448 static BuiltInSubr SpecialVars[] = {cursorMV, lineMV, columnMV,
449         fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV,
450         selectionLeftMV, selectionRightMV, wrapMarginMV, tabDistMV,
451         emTabDistMV, useTabsMV, languageModeMV, modifiedMV,
452         statisticsLineMV, incSearchLineMV, showLineNumbersMV,
453         autoIndentMV, wrapTextMV, highlightSyntaxMV,
454         makeBackupCopyMV, incBackupMV, showMatchingMV, matchSyntaxBasedMV,
455         overTypeModeMV, readOnlyMV, lockedMV, fileFormatMV,
456         fontNameMV, fontNameItalicMV,
457         fontNameBoldMV, fontNameBoldItalicMV, subscriptSepMV,
458         minFontWidthMV, maxFontWidthMV, topLineMV, numDisplayLinesMV,
459         displayWidthMV, activePaneMV, nPanesMV, emptyArrayMV,
460         serverNameMV, calltipIDMV,
461 /* DISABLED for 5.4        backlightStringMV, */
462 	rangesetListMV, versionMV
463     };
464 #define N_SPECIAL_VARS (sizeof SpecialVars/sizeof *SpecialVars)
465 static const char *SpecialVarNames[N_SPECIAL_VARS] = {"$cursor", "$line", "$column",
466         "$file_name", "$file_path", "$text_length", "$selection_start",
467         "$selection_end", "$selection_left", "$selection_right",
468         "$wrap_margin", "$tab_dist", "$em_tab_dist", "$use_tabs",
469         "$language_mode", "$modified",
470         "$statistics_line", "$incremental_search_line", "$show_line_numbers",
471         "$auto_indent", "$wrap_text", "$highlight_syntax",
472         "$make_backup_copy", "$incremental_backup", "$show_matching", "$match_syntax_based",
473         "$overtype_mode", "$read_only", "$locked", "$file_format",
474         "$font_name", "$font_name_italic",
475         "$font_name_bold", "$font_name_bold_italic", "$sub_sep",
476         "$min_font_width", "$max_font_width", "$top_line", "$n_display_lines",
477         "$display_width", "$active_pane", "$n_panes", "$empty_array",
478         "$server_name", "$calltip_ID",
479 /* DISABLED for 5.4       "$backlight_string", */
480         "$rangeset_list", "$VERSION"
481     };
482 
483 /* Global symbols for returning values from built-in functions */
484 #define N_RETURN_GLOBALS 5
485 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS,
486 	SHELL_CMD_STATUS, LIST_DIALOG_BUTTON};
487 static const char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button",
488     	"$search_end", "$read_status", "$shell_cmd_status",
489 	"$list_dialog_button"};
490 static Symbol *ReturnGlobals[N_RETURN_GLOBALS];
491 
492 /* List of actions not useful when learning a macro sequence (also see below) */
493 static char* IgnoredActions[] = {"focusIn", "focusOut"};
494 
495 /* List of actions intended to be attached to mouse buttons, which the user
496    must be warned can't be recorded in a learn/replay sequence */
497 static const char* MouseActions[] = {"grab_focus", "extend_adjust", "extend_start",
498 	"extend_end", "secondary_or_drag_adjust", "secondary_adjust",
499 	"secondary_or_drag_start", "secondary_start", "move_destination",
500 	"move_to", "move_to_or_end_drag", "copy_to", "copy_to_or_end_drag",
501 	"exchange", "process_bdrag", "mouse_pan"};
502 
503 /* List of actions to not record because they
504    generate further actions, more suitable for recording */
505 static const char* RedundantActions[] = {"open_dialog", "save_as_dialog",
506     "revert_to_saved_dialog", "include_file_dialog", "load_macro_file_dialog",
507     "load_tags_file_dialog", "find_dialog", "replace_dialog",
508     "goto_line_number_dialog", "mark_dialog", "goto_mark_dialog",
509     "control_code_dialog", "filter_selection_dialog", "execute_command_dialog",
510     "repeat_dialog", "start_incremental_find"};
511 
512 /* The last command executed (used by the Repeat command) */
513 static char *LastCommand = NULL;
514 
515 /* The current macro to execute on Replay command */
516 static char *ReplayMacro = NULL;
517 
518 /* Buffer where macro commands are recorded in Learn mode */
519 static textBuffer *MacroRecordBuf = NULL;
520 
521 /* Action Hook id for recording actions for Learn mode */
522 static XtActionHookId MacroRecordActionHook = 0;
523 
524 /* Window where macro recording is taking place */
525 static WindowInfo *MacroRecordWindow = NULL;
526 
527 /* Arrays for translating escape characters in escapeStringChars */
528 static char ReplaceChars[] = "\\\"ntbrfav";
529 static char EscapeChars[] = "\\\"\n\t\b\r\f\a\v";
530 
531 /*
532 ** Install built-in macro subroutines and special variables for accessing
533 ** editor information
534 */
RegisterMacroSubroutines(void)535 void RegisterMacroSubroutines(void)
536 {
537     static DataValue subrPtr = {NO_TAG, {0}}, noValue = {NO_TAG, {0}};
538     unsigned i;
539 
540     /* Install symbols for built-in routines and variables, with pointers
541        to the appropriate c routines to do the work */
542     for (i=0; i<N_MACRO_SUBRS; i++) {
543     	subrPtr.val.subr = MacroSubrs[i];
544     	InstallSymbol(MacroSubrNames[i], C_FUNCTION_SYM, subrPtr);
545     }
546     for (i=0; i<N_SPECIAL_VARS; i++) {
547     	subrPtr.val.subr = SpecialVars[i];
548     	InstallSymbol(SpecialVarNames[i], PROC_VALUE_SYM, subrPtr);
549     }
550 
551     /* Define global variables used for return values, remember their
552        locations so they can be set without a LookupSymbol call */
553     for (i=0; i<N_RETURN_GLOBALS; i++)
554     	ReturnGlobals[i] = InstallSymbol(ReturnGlobalNames[i], GLOBAL_SYM,
555     	    	noValue);
556 }
557 
558 #define MAX_LEARN_MSG_LEN ((2 * MAX_ACCEL_LEN) + 60)
BeginLearn(WindowInfo * window)559 void BeginLearn(WindowInfo *window)
560 {
561     WindowInfo *win;
562     XmString s;
563     XmString xmFinish;
564     XmString xmCancel;
565     char *cFinish;
566     char *cCancel;
567     char message[MAX_LEARN_MSG_LEN];
568 
569     /* If we're already in learn mode, return */
570     if (MacroRecordActionHook != 0)
571     	return;
572 
573     /* dim the inappropriate menus and items, and undim finish and cancel */
574     for (win=WindowList; win!=NULL; win=win->next) {
575     	if (!IsTopDocument(win))
576 	    continue;
577 	XtSetSensitive(win->learnItem, False);
578     }
579     SetSensitive(window, window->finishLearnItem, True);
580     XtVaSetValues(window->cancelMacroItem, XmNlabelString,
581     	    s=XmStringCreateSimple("Cancel Learn"), NULL);
582     XmStringFree(s);
583     SetSensitive(window, window->cancelMacroItem, True);
584 
585     /* Mark the window where learn mode is happening */
586     MacroRecordWindow = window;
587 
588     /* Allocate a text buffer for accumulating the macro strings */
589     MacroRecordBuf = BufCreate();
590 
591     /* Add the action hook for recording the actions */
592     MacroRecordActionHook =
593     	    XtAppAddActionHook(XtWidgetToApplicationContext(window->shell),
594     	    learnActionHook, window);
595 
596     /* Extract accelerator texts from menu PushButtons */
597     XtVaGetValues(window->finishLearnItem, XmNacceleratorText, &xmFinish, NULL);
598     XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
599 
600     /* Translate Motif strings to char* */
601     cFinish = GetXmStringText(xmFinish);
602     cCancel = GetXmStringText(xmCancel);
603 
604     /* Free Motif Strings */
605     XmStringFree(xmFinish);
606     XmStringFree(xmCancel);
607 
608     /* Create message */
609     if (cFinish[0] == '\0') {
610         if (cCancel[0] == '\0') {
611             strncpy(message, "Learn Mode -- Use menu to finish or cancel",
612                 MAX_LEARN_MSG_LEN);
613             message[MAX_LEARN_MSG_LEN - 1] = '\0';
614         }
615         else {
616             sprintf(message,
617                 "Learn Mode -- Use menu to finish, press %s to cancel",
618                 cCancel);
619         }
620     }
621     else {
622         if (cCancel[0] == '\0') {
623             sprintf(message,
624                 "Learn Mode -- Press %s to finish, use menu to cancel",
625                 cFinish);
626 
627         }
628         else {
629             sprintf(message,
630                 "Learn Mode -- Press %s to finish, %s to cancel",
631                 cFinish,
632                 cCancel);
633         }
634     }
635 
636     /* Free C-strings */
637     NEditFree(cFinish);
638     NEditFree(cCancel);
639 
640     /* Put up the learn-mode banner */
641     SetModeMessage(window, message);
642 }
643 
AddLastCommandActionHook(XtAppContext context)644 void AddLastCommandActionHook(XtAppContext context)
645 {
646     XtAppAddActionHook(context, lastActionHook, NULL);
647 }
648 
FinishLearn(void)649 void FinishLearn(void)
650 {
651     WindowInfo *win;
652 
653     /* If we're not in learn mode, return */
654     if (MacroRecordActionHook == 0)
655     	return;
656 
657     /* Remove the action hook */
658     XtRemoveActionHook(MacroRecordActionHook);
659     MacroRecordActionHook = 0;
660 
661     /* Free the old learn/replay sequence */
662     NEditFree(ReplayMacro);
663 
664     /* Store the finished action for the replay menu item */
665     ReplayMacro = BufGetAll(MacroRecordBuf);
666 
667     /* Free the buffer used to accumulate the macro sequence */
668     BufFree(MacroRecordBuf);
669 
670     /* Undim the menu items dimmed during learn */
671     for (win=WindowList; win!=NULL; win=win->next) {
672     	if (!IsTopDocument(win))
673 	    continue;
674 	XtSetSensitive(win->learnItem, True);
675     }
676     if (IsTopDocument(MacroRecordWindow)) {
677 	XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
678 	XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
679     }
680 
681     /* Undim the replay and paste-macro buttons */
682     for (win=WindowList; win!=NULL; win=win->next) {
683     	if (!IsTopDocument(win))
684 	    continue;
685     	XtSetSensitive(win->replayItem, True);
686     }
687     DimPasteReplayBtns(True);
688 
689     /* Clear learn-mode banner */
690     ClearModeMessage(MacroRecordWindow);
691 }
692 
693 /*
694 ** Cancel Learn mode, or macro execution (they're bound to the same menu item)
695 */
CancelMacroOrLearn(WindowInfo * window)696 void CancelMacroOrLearn(WindowInfo *window)
697 {
698     if (MacroRecordActionHook != 0)
699     	cancelLearn();
700     else if (window->macroCmdData != NULL)
701     	AbortMacroCommand(window);
702 }
703 
cancelLearn(void)704 static void cancelLearn(void)
705 {
706     WindowInfo *win;
707 
708     /* If we're not in learn mode, return */
709     if (MacroRecordActionHook == 0)
710     	return;
711 
712     /* Remove the action hook */
713     XtRemoveActionHook(MacroRecordActionHook);
714     MacroRecordActionHook = 0;
715 
716     /* Free the macro under construction */
717     BufFree(MacroRecordBuf);
718 
719     /* Undim the menu items dimmed during learn */
720     for (win=WindowList; win!=NULL; win=win->next) {
721     	if (!IsTopDocument(win))
722 	    continue;
723 	XtSetSensitive(win->learnItem, True);
724     }
725     if (IsTopDocument(MacroRecordWindow)) {
726 	XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
727 	XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
728     }
729 
730     /* Clear learn-mode banner */
731     ClearModeMessage(MacroRecordWindow);
732 }
733 
734 /*
735 ** Execute the learn/replay sequence stored in "window"
736 */
Replay(WindowInfo * window)737 void Replay(WindowInfo *window)
738 {
739     Program *prog;
740     char *errMsg, *stoppedAt;
741 
742     /* Verify that a replay macro exists and it's not empty and that */
743     /* we're not already running a macro */
744     if (ReplayMacro != NULL &&
745             ReplayMacro[0] != 0 &&
746             window->macroCmdData == NULL) {
747         /* Parse the replay macro (it's stored in text form) and compile it into
748         an executable program "prog" */
749         prog = ParseMacro(ReplayMacro, &errMsg, &stoppedAt);
750         if (prog == NULL) {
751             fprintf(stderr,
752                 "NEdit internal error, learn/replay macro syntax error: %s\n",
753                 errMsg);
754             return;
755         }
756 
757         /* run the executable program */
758         runMacro(window, prog);
759     }
760 }
761 
762 /*
763 **  Read the initial NEdit macro file if one exists.
764 */
ReadMacroInitFile(WindowInfo * window)765 void ReadMacroInitFile(WindowInfo *window)
766 {
767     const char* autoloadName = GetRCFileName(AUTOLOAD_NM);
768     static int initFileLoaded = False;
769 
770     /*  GetRCFileName() might return NULL if an error occurs during
771         creation of the preference file directory. */
772     if (autoloadName != NULL && !initFileLoaded)
773     {
774         ReadMacroFile(window, autoloadName, False);
775         initFileLoaded = True;
776     }
777 }
778 
779 /*
780 ** Read an NEdit macro file.  Extends the syntax of the macro parser with
781 ** define keyword, and allows intermixing of defines with immediate actions.
782 */
ReadMacroFile(WindowInfo * window,const char * fileName,int warnNotExist)783 int ReadMacroFile(WindowInfo *window, const char *fileName, int warnNotExist)
784 {
785     int result;
786     char *fileString;
787 
788     /* read-in macro file and force a terminating \n, to prevent syntax
789     ** errors with statements on the last line
790     */
791     fileString = ReadAnyTextFile(fileName, True);
792     if (fileString == NULL){
793         if (errno != ENOENT || warnNotExist)
794         {
795             DialogF(DF_ERR, window->shell, 1, "Read Macro",
796                     "Error reading macro file %s: %s", "OK", fileName,
797 #ifdef VMS
798                     strerror(errno, vaxc$errno));
799 #else
800                     strerror(errno));
801 #endif
802         }
803         return False;
804     }
805 
806     /* Parse fileString */
807     result = readCheckMacroString(window->shell, fileString, window, fileName,
808 	    NULL);
809     NEditFree(fileString);
810     return result;
811 }
812 
813 /*
814 ** Parse and execute a macro string including macro definitions.  Report
815 ** parsing errors in a dialog posted over window->shell.
816 */
ReadMacroString(WindowInfo * window,char * string,const char * errIn)817 int ReadMacroString(WindowInfo *window, char *string, const char *errIn)
818 {
819     return readCheckMacroString(window->shell, string, window, errIn, NULL);
820 }
821 
822 /*
823 ** Check a macro string containing definitions for errors.  Returns True
824 ** if macro compiled successfully.  Returns False and puts up
825 ** a dialog explaining if macro did not compile successfully.
826 */
CheckMacroString(Widget dialogParent,char * string,const char * errIn,char ** errPos)827 int CheckMacroString(Widget dialogParent, char *string, const char *errIn,
828 	char **errPos)
829 {
830     return readCheckMacroString(dialogParent, string, NULL, errIn, errPos);
831 }
832 
833 /*
834 ** Parse and optionally execute a macro string including macro definitions.
835 ** Report parsing errors in a dialog posted over dialogParent, using the
836 ** string errIn to identify the entity being parsed (filename, macro string,
837 ** etc.).  If runWindow is specified, runs the macro against the window.  If
838 ** runWindow is passed as NULL, does parse only.  If errPos is non-null,
839 ** returns a pointer to the error location in the string.
840 */
readCheckMacroString(Widget dialogParent,char * string,WindowInfo * runWindow,const char * errIn,char ** errPos)841 static int readCheckMacroString(Widget dialogParent, char *string,
842 	WindowInfo *runWindow, const char *errIn, char **errPos)
843 {
844     char *stoppedAt, *inPtr, *namePtr, *errMsg;
845     char subrName[MAX_SYM_LEN];
846     Program *prog;
847     Symbol *sym;
848     DataValue subrPtr;
849     Stack* progStack = (Stack*) NEditMalloc(sizeof(Stack));
850     progStack->top = NULL;
851     progStack->size = 0;
852 
853     inPtr = string;
854     while (*inPtr != '\0') {
855 
856     	/* skip over white space and comments */
857 	while (*inPtr==' ' || *inPtr=='\t' || *inPtr=='\n'|| *inPtr=='#') {
858 	    if (*inPtr == '#')
859 	    	while (*inPtr != '\n' && *inPtr != '\0') inPtr++;
860 	    else
861 	    	inPtr++;
862 	}
863 	if (*inPtr == '\0')
864 	    break;
865 
866 	/* look for define keyword, and compile and store defined routines */
867 	if (!strncmp(inPtr, "define", 6) && (inPtr[6]==' ' || inPtr[6]=='\t')) {
868 	    inPtr += 6;
869 	    inPtr += strspn(inPtr, " \t\n");
870 	    namePtr = subrName;
871             while ((namePtr < &subrName[MAX_SYM_LEN - 1])
872                    && (isalnum((unsigned char)*inPtr) || *inPtr == '_')) {
873                 *namePtr++ = *inPtr++;
874             }
875             *namePtr = '\0';
876             if (isalnum((unsigned char)*inPtr) || *inPtr == '_') {
877                 return ParseError(dialogParent, string, inPtr, errIn,
878                                   "subroutine name too long");
879             }
880 	    inPtr += strspn(inPtr, " \t\n");
881 	    if (*inPtr != '{') {
882 	    	if (errPos != NULL) *errPos = stoppedAt;
883 		return ParseError(dialogParent, string, inPtr,
884 	    	    	errIn, "expected '{'");
885 	    }
886 	    prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
887 	    if (prog == NULL) {
888 	    	if (errPos != NULL) *errPos = stoppedAt;
889 	    	return ParseError(dialogParent, string, stoppedAt,
890 	    	    	errIn, errMsg);
891 	    }
892 	    if (runWindow != NULL) {
893 		sym = LookupSymbol(subrName);
894 		if (sym == NULL) {
895 		    subrPtr.val.prog = prog;
896 		    subrPtr.tag = NO_TAG;
897 		    sym = InstallSymbol(subrName, MACRO_FUNCTION_SYM, subrPtr);
898 		} else {
899 	    	    if (sym->type == MACRO_FUNCTION_SYM)
900 		    	FreeProgram(sym->value.val.prog);
901 		    else
902 			sym->type = MACRO_FUNCTION_SYM;
903 	    	    sym->value.val.prog = prog;
904 		}
905 	    }
906 	    inPtr = stoppedAt;
907 
908 	/* Parse and execute immediate (outside of any define) macro commands
909 	   and WAIT for them to finish executing before proceeding.  Note that
910 	   the code below is not perfect.  If you interleave code blocks with
911 	   definitions in a file which is loaded from another macro file, it
912 	   will probably run the code blocks in reverse order! */
913 	} else {
914 	    prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
915 	    if (prog == NULL) {
916                 if (errPos != NULL) {
917                     *errPos = stoppedAt;
918                 }
919 
920     	    	return ParseError(dialogParent, string, stoppedAt,
921 	    	    	errIn, errMsg);
922 	    }
923 
924 	    if (runWindow != NULL) {
925                 XEvent nextEvent;
926 		if (runWindow->macroCmdData == NULL) {
927 	            runMacro(runWindow, prog);
928 		    while (runWindow->macroCmdData != NULL) {
929 			XtAppNextEvent(XtWidgetToApplicationContext(
930 				runWindow->shell),  &nextEvent);
931                         ServerDispatchEvent(&nextEvent);
932                     }
933                 } else {
934                     /*  If we come here this means that the string was parsed
935                         from within another macro via load_macro_file(). In
936                         this case, plain code segments outside of define
937                         blocks are rolled into one Program each and put on
938                         the stack. At the end, the stack is unrolled, so the
939                         plain Programs would be executed in the wrong order.
940 
941                         So we don't hand the Programs over to the interpreter
942                         just yet (via RunMacroAsSubrCall()), but put it on a
943                         stack of our own, reversing order once again.   */
944                     Push(progStack, (void*) prog);
945                 }
946 	    }
947 	    inPtr = stoppedAt;
948     	}
949     }
950 
951     /*  Unroll reversal stack for macros loaded from macros.  */
952     while (NULL != (prog = (Program*) Pop(progStack))) {
953         RunMacroAsSubrCall(prog);
954     }
955 
956     /*  This stack is empty, so just free it without checking the members.  */
957     NEditFree(progStack);
958 
959     return True;
960 }
961 
962 /*
963 ** Run a pre-compiled macro, changing the interface state to reflect that
964 ** a macro is running, and handling preemption, resumption, and cancellation.
965 ** frees prog when macro execution is complete;
966 */
runMacro(WindowInfo * window,Program * prog)967 static void runMacro(WindowInfo *window, Program *prog)
968 {
969     DataValue result;
970     char *errMsg;
971     int stat;
972     macroCmdInfo *cmdData;
973     XmString s;
974 
975     /* If a macro is already running, just call the program as a subroutine,
976        instead of starting a new one, so we don't have to keep a separate
977        context, and the macros will serialize themselves automatically */
978     if (window->macroCmdData != NULL) {
979     	RunMacroAsSubrCall(prog);
980 	return;
981     }
982 
983     /* put up a watch cursor over the waiting window */
984     BeginWait(window->shell);
985 
986     /* enable the cancel menu item */
987     XtVaSetValues(window->cancelMacroItem, XmNlabelString,
988     	    s=XmStringCreateSimple("Cancel Macro"), NULL);
989     XmStringFree(s);
990     SetSensitive(window, window->cancelMacroItem, True);
991 
992     /* Create a data structure for passing macro execution information around
993        amongst the callback routines which will process i/o and completion */
994     cmdData = (macroCmdInfo *)NEditMalloc(sizeof(macroCmdInfo));
995     window->macroCmdData = cmdData;
996     cmdData->bannerIsUp = False;
997     cmdData->closeOnCompletion = False;
998     cmdData->program = prog;
999     cmdData->context = NULL;
1000     cmdData->continueWorkProcID = 0;
1001     cmdData->dialog = NULL;
1002 
1003     /* Set up timer proc for putting up banner when macro takes too long */
1004     cmdData->bannerTimeoutID = XtAppAddTimeOut(
1005     	    XtWidgetToApplicationContext(window->shell), BANNER_WAIT_TIME,
1006     	    bannerTimeoutProc, window);
1007 
1008     /* Begin macro execution */
1009     stat = ExecuteMacro(window, prog, 0, NULL, &result, &cmdData->context,
1010     	    &errMsg);
1011 
1012     if (stat == MACRO_ERROR)
1013     {
1014         finishMacroCmdExecution(window);
1015         DialogF(DF_ERR, window->shell, 1, "Macro Error",
1016                 "Error executing macro: %s", "OK", errMsg);
1017         return;
1018     }
1019 
1020     if (stat == MACRO_DONE) {
1021     	finishMacroCmdExecution(window);
1022     	return;
1023     }
1024     if (stat == MACRO_TIME_LIMIT) {
1025 	ResumeMacroExecution(window);
1026 	return;
1027     }
1028     /* (stat == MACRO_PREEMPT) Macro was preempted */
1029 }
1030 
1031 /*
1032 ** Continue with macro execution after preemption.  Called by the routines
1033 ** whose actions cause preemption when they have completed their lengthy tasks.
1034 ** Re-establishes macro execution work proc.  Window must be the window in
1035 ** which the macro is executing (the window to which macroCmdData is attached),
1036 ** and not the window to which operations are focused.
1037 */
ResumeMacroExecution(WindowInfo * window)1038 void ResumeMacroExecution(WindowInfo *window)
1039 {
1040     macroCmdInfo *cmdData = (macroCmdInfo *)window->macroCmdData;
1041 
1042     if (cmdData != NULL)
1043 	cmdData->continueWorkProcID = XtAppAddWorkProc(
1044 	    	XtWidgetToApplicationContext(window->shell),
1045 	    	continueWorkProc, window);
1046 }
1047 
1048 /*
1049 ** Cancel the macro command in progress (user cancellation via GUI)
1050 */
AbortMacroCommand(WindowInfo * window)1051 void AbortMacroCommand(WindowInfo *window)
1052 {
1053     if (window->macroCmdData == NULL)
1054     	return;
1055 
1056     /* If there's both a macro and a shell command executing, the shell command
1057        must have been called from the macro.  When called from a macro, shell
1058        commands don't put up cancellation controls of their own, but rely
1059        instead on the macro cancellation mechanism (here) */
1060 #ifndef VMS
1061     if (window->shellCmdData != NULL)
1062     	AbortShellCommand(window);
1063 #endif
1064 
1065     /* Free the continuation */
1066     FreeRestartData(((macroCmdInfo *)window->macroCmdData)->context);
1067 
1068     /* Kill the macro command */
1069     finishMacroCmdExecution(window);
1070 }
1071 
1072 /*
1073 ** Call this before closing a window, to clean up macro references to the
1074 ** window, stop any macro which might be running from it, free associated
1075 ** memory, and check that a macro is not attempting to close the window from
1076 ** which it is run.  If this is being called from a macro, and the window
1077 ** this routine is examining is the window from which the macro was run, this
1078 ** routine will return False, and the caller must NOT CLOSE THE WINDOW.
1079 ** Instead, empty it and make it Untitled, and let the macro completion
1080 ** process close the window when the macro is finished executing.
1081 */
MacroWindowCloseActions(WindowInfo * window)1082 int MacroWindowCloseActions(WindowInfo *window)
1083 {
1084     macroCmdInfo *mcd, *cmdData = window->macroCmdData;
1085     WindowInfo *w;
1086 
1087     if (MacroRecordActionHook != 0 && MacroRecordWindow == window) {
1088         FinishLearn();
1089     }
1090 
1091     /* If no macro is executing in the window, allow the close, but check
1092        if macros executing in other windows have it as focus.  If so, set
1093        their focus back to the window from which they were originally run */
1094     if (cmdData == NULL) {
1095     	for (w=WindowList; w!=NULL; w=w->next) {
1096 	    mcd = (macroCmdInfo *)w->macroCmdData;
1097 	    if (w == MacroRunWindow() && MacroFocusWindow() == window)
1098 		SetMacroFocusWindow(MacroRunWindow());
1099 	    else if (mcd != NULL && mcd->context->focusWindow == window)
1100 		mcd->context->focusWindow = mcd->context->runWindow;
1101 	}
1102     	return True;
1103     }
1104 
1105     /* If the macro currently running (and therefore calling us, because
1106        execution must otherwise return to the main loop to execute any
1107        commands), is running in this window, tell the caller not to close,
1108        and schedule window close on completion of macro */
1109     if (window == MacroRunWindow()) {
1110     	cmdData->closeOnCompletion = True;
1111 	return False;
1112     }
1113 
1114     /* Free the continuation */
1115     FreeRestartData(cmdData->context);
1116 
1117     /* Kill the macro command */
1118     finishMacroCmdExecution(window);
1119     return True;
1120 }
1121 
1122 /*
1123 ** Clean up after the execution of a macro command: free memory, and restore
1124 ** the user interface state.
1125 */
finishMacroCmdExecution(WindowInfo * window)1126 static void finishMacroCmdExecution(WindowInfo *window)
1127 {
1128     macroCmdInfo *cmdData = window->macroCmdData;
1129     int closeOnCompletion = cmdData->closeOnCompletion;
1130     XmString s;
1131     XClientMessageEvent event;
1132 
1133     /* Cancel pending timeout and work proc */
1134     if (cmdData->bannerTimeoutID != 0)
1135     	XtRemoveTimeOut(cmdData->bannerTimeoutID);
1136     if (cmdData->continueWorkProcID != 0)
1137     	XtRemoveWorkProc(cmdData->continueWorkProcID);
1138 
1139     /* Clean up waiting-for-macro-command-to-complete mode */
1140     EndWait(window->shell);
1141     XtVaSetValues(window->cancelMacroItem, XmNlabelString,
1142     	    s=XmStringCreateSimple("Cancel Learn"), NULL);
1143     XmStringFree(s);
1144     SetSensitive(window, window->cancelMacroItem, False);
1145     if (cmdData->bannerIsUp)
1146     	ClearModeMessage(window);
1147 
1148     /* If a dialog was up, get rid of it */
1149     if (cmdData->dialog != NULL)
1150     	XtDestroyWidget(XtParent(cmdData->dialog));
1151 
1152     /* Free execution information */
1153     FreeProgram(cmdData->program);
1154     NEditFree(cmdData);
1155     window->macroCmdData = NULL;
1156 
1157     /* If macro closed its own window, window was made empty and untitled,
1158        but close was deferred until completion.  This is completion, so if
1159        the window is still empty, do the close */
1160     if (closeOnCompletion && !window->filenameSet && !window->fileChanged) {
1161     	CloseWindow(window);
1162 	window = NULL;
1163     }
1164 
1165     /* If no other macros are executing, do garbage collection */
1166     SafeGC();
1167 
1168     /* In processing the .neditmacro file (and possibly elsewhere), there
1169        is an event loop which waits for macro completion.  Send an event
1170        to wake up that loop, otherwise execution will stall until the user
1171        does something to the window. */
1172     if (!closeOnCompletion) {
1173 	event.format = 8;
1174 	event.type = ClientMessage;
1175 	XSendEvent(XtDisplay(window->shell), XtWindow(window->shell), False,
1176 		NoEventMask, (XEvent *)&event);
1177     }
1178 }
1179 
1180 /*
1181 ** Do garbage collection of strings if there are no macros currently
1182 ** executing.  NEdit's macro language GC strategy is to call this routine
1183 ** whenever a macro completes.  If other macros are still running (preempted
1184 ** or waiting for a shell command or dialog), this does nothing and therefore
1185 ** defers GC to the completion of the last macro out.
1186 */
SafeGC(void)1187 void SafeGC(void)
1188 {
1189     WindowInfo *win;
1190 
1191     for (win=WindowList; win!=NULL; win=win->next)
1192 	if (win->macroCmdData != NULL || InSmartIndentMacros(win))
1193 	    return;
1194     GarbageCollectStrings();
1195 }
1196 
1197 /*
1198 ** Executes macro string "macro" using the lastFocus pane in "window".
1199 ** Reports errors via a dialog posted over "window", integrating the name
1200 ** "errInName" into the message to help identify the source of the error.
1201 */
DoMacro(WindowInfo * window,const char * macro,const char * errInName)1202 void DoMacro(WindowInfo *window, const char *macro, const char *errInName)
1203 {
1204     Program *prog;
1205     char *errMsg, *stoppedAt, *tMacro;
1206     int macroLen;
1207 
1208     /* Add a terminating newline (which command line users are likely to omit
1209        since they are typically invoking a single routine) */
1210     macroLen = strlen(macro);
1211     tMacro = (char*)NEditMalloc(strlen(macro)+2);
1212     strncpy(tMacro, macro, macroLen);
1213     tMacro[macroLen] = '\n';
1214     tMacro[macroLen+1] = '\0';
1215 
1216     /* Parse the macro and report errors if it fails */
1217     prog = ParseMacro(tMacro, &errMsg, &stoppedAt);
1218     if (prog == NULL) {
1219     	ParseError(window->shell, tMacro, stoppedAt, errInName, errMsg);
1220 	NEditFree(tMacro);
1221     	return;
1222     }
1223     NEditFree(tMacro);
1224 
1225     /* run the executable program (prog is freed upon completion) */
1226     runMacro(window, prog);
1227 }
1228 
1229 /*
1230 ** Get the current Learn/Replay macro in text form.  Returned string is a
1231 ** pointer to the stored macro and should not be freed by the caller (and
1232 ** will cease to exist when the next replay macro is installed)
1233 */
GetReplayMacro(void)1234 char *GetReplayMacro(void)
1235 {
1236     return ReplayMacro;
1237 }
1238 
1239 /*
1240 ** Present the user a dialog for "Repeat" command
1241 */
RepeatDialog(WindowInfo * window)1242 void RepeatDialog(WindowInfo *window)
1243 {
1244     Widget form, selBox, radioBox, timesForm;
1245     repeatDialog *rd;
1246     Arg selBoxArgs[1];
1247     char *lastCmdLabel, *parenChar;
1248     XmString s1;
1249     int cmdNameLen;
1250 
1251     if (LastCommand == NULL)
1252     {
1253         DialogF(DF_WARN, window->shell, 1, "Repeat Macro",
1254                 "No previous commands or learn/\nreplay sequences to repeat",
1255                 "OK");
1256         return;
1257     }
1258 
1259     /* Remeber the last command, since the user is allowed to work in the
1260        window while the dialog is up */
1261     rd = (repeatDialog *)NEditMalloc(sizeof(repeatDialog));
1262     rd->lastCommand = NEditStrdup(LastCommand);
1263 
1264     /* make a label for the Last command item of the dialog, which includes
1265        the last executed action name */
1266     parenChar = strchr(LastCommand, '(');
1267     if (parenChar == NULL)
1268 	return;
1269     cmdNameLen = parenChar-LastCommand;
1270     lastCmdLabel = (char*)NEditMalloc(16 + cmdNameLen);
1271     strcpy(lastCmdLabel, "Last Command (");
1272     strncpy(&lastCmdLabel[14], LastCommand, cmdNameLen);
1273     strcpy(&lastCmdLabel[14 + cmdNameLen], ")");
1274 
1275     XtSetArg(selBoxArgs[0], XmNautoUnmanage, False);
1276     selBox = CreatePromptDialog(window->shell, "repeat", selBoxArgs, 1);
1277     rd->shell = XtParent(selBox);
1278     XtAddCallback(rd->shell, XmNdestroyCallback, repeatDestroyCB, rd);
1279     XtAddCallback(selBox, XmNokCallback, repeatOKCB, rd);
1280     XtAddCallback(selBox, XmNapplyCallback, repeatApplyCB, rd);
1281     XtAddCallback(selBox, XmNcancelCallback, repeatCancelCB, rd);
1282     XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_TEXT));
1283     XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_SELECTION_LABEL));
1284     XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_HELP_BUTTON));
1285     XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_APPLY_BUTTON));
1286     XtVaSetValues(XtParent(selBox), XmNtitle, "Repeat Macro", NULL);
1287     AddMotifCloseCallback(XtParent(selBox), repeatCancelCB, rd);
1288 
1289     form = XtVaCreateManagedWidget("form", xmFormWidgetClass, selBox, NULL);
1290 
1291     radioBox = XtVaCreateManagedWidget("cmdSrc", xmRowColumnWidgetClass, form,
1292     	    XmNradioBehavior, True,
1293 	    XmNorientation, XmHORIZONTAL,
1294 	    XmNpacking, XmPACK_TIGHT,
1295 	    XmNtopAttachment, XmATTACH_FORM,
1296     	    XmNleftAttachment, XmATTACH_FORM, NULL);
1297     rd->lastCmdToggle = XtVaCreateManagedWidget("lastCmdToggle",
1298     	    xmToggleButtonWidgetClass, radioBox, XmNset, True,
1299 	    XmNlabelString, s1=XmStringCreateSimple(lastCmdLabel),
1300 	    XmNmnemonic, 'C', NULL);
1301     XmStringFree(s1);
1302     NEditFree(lastCmdLabel);
1303     XtVaCreateManagedWidget("learnReplayToggle",
1304     	    xmToggleButtonWidgetClass, radioBox, XmNset, False,
1305 	    XmNlabelString,
1306 	    	s1=XmStringCreateSimple("Learn/Replay"),
1307 	    XmNmnemonic, 'L',
1308 	    XmNsensitive, ReplayMacro != NULL, NULL);
1309     XmStringFree(s1);
1310 
1311     timesForm = XtVaCreateManagedWidget("form", xmFormWidgetClass, form,
1312 	    XmNtopAttachment, XmATTACH_WIDGET,
1313     	    XmNtopWidget, radioBox,
1314 	    XmNtopOffset, 10,
1315     	    XmNleftAttachment, XmATTACH_FORM, NULL);
1316     radioBox = XtVaCreateManagedWidget("method", xmRowColumnWidgetClass,
1317 	    timesForm,
1318     	    XmNradioBehavior, True,
1319 	    XmNorientation, XmHORIZONTAL,
1320 	    XmNpacking, XmPACK_TIGHT,
1321 	    XmNtopAttachment, XmATTACH_FORM,
1322     	    XmNbottomAttachment, XmATTACH_FORM,
1323     	    XmNleftAttachment, XmATTACH_FORM, NULL);
1324     rd->inSelToggle = XtVaCreateManagedWidget("inSelToggle",
1325     	    xmToggleButtonWidgetClass, radioBox, XmNset, False,
1326 	    XmNlabelString, s1=XmStringCreateSimple("In Selection"),
1327 	    XmNmnemonic, 'I', NULL);
1328     XmStringFree(s1);
1329     rd->toEndToggle = XtVaCreateManagedWidget("toEndToggle",
1330     	    xmToggleButtonWidgetClass, radioBox, XmNset, False,
1331 	    XmNlabelString, s1=XmStringCreateSimple("To End"),
1332 	    XmNmnemonic, 'T', NULL);
1333     XmStringFree(s1);
1334     XtVaCreateManagedWidget("nTimesToggle",
1335     	    xmToggleButtonWidgetClass, radioBox, XmNset, True,
1336 	    XmNlabelString, s1=XmStringCreateSimple("N Times"),
1337 	    XmNmnemonic, 'N',
1338 	    XmNset, True, NULL);
1339     XmStringFree(s1);
1340     rd->repeatText = XtVaCreateManagedWidget("repeatText", xmTextWidgetClass,
1341 	    timesForm,
1342     	    XmNcolumns, 5,
1343     	    XmNtopAttachment, XmATTACH_FORM,
1344 	    XmNbottomAttachment, XmATTACH_FORM,
1345     	    XmNleftAttachment, XmATTACH_WIDGET,
1346     	    XmNleftWidget, radioBox, NULL);
1347     RemapDeleteKey(rd->repeatText);
1348 
1349     /* Handle mnemonic selection of buttons and focus to dialog */
1350     AddDialogMnemonicHandler(form, FALSE);
1351 
1352     /* Set initial focus */
1353 #if XmVersion >= 1002
1354     XtVaSetValues(form, XmNinitialFocus, timesForm, NULL);
1355     XtVaSetValues(timesForm, XmNinitialFocus, rd->repeatText, NULL);
1356 #endif
1357 
1358     /* put up dialog */
1359     rd->forWindow = window;
1360     ManageDialogCenteredOnPointer(selBox);
1361 }
1362 
repeatOKCB(Widget w,XtPointer clientData,XtPointer callData)1363 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData)
1364 {
1365     repeatDialog *rd = (repeatDialog *)clientData;
1366 
1367     if (doRepeatDialogAction(rd, ((XmAnyCallbackStruct *)callData)->event))
1368     	XtDestroyWidget(rd->shell);
1369 }
1370 
1371 /* Note that the apply button is not managed in the repeat dialog.  The dialog
1372    itself is capable of non-modal operation, but to be complete, it needs
1373    to dynamically update last command, dimming of learn/replay, possibly a
1374    stop button for the macro, and possibly in-selection with selection */
repeatApplyCB(Widget w,XtPointer clientData,XtPointer callData)1375 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData)
1376 {
1377     doRepeatDialogAction((repeatDialog *)clientData,
1378 	    ((XmAnyCallbackStruct *)callData)->event);
1379 }
1380 
doRepeatDialogAction(repeatDialog * rd,XEvent * event)1381 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event)
1382 {
1383     int nTimes;
1384     char nTimesStr[TYPE_INT_STR_SIZE(int)];
1385     char *params[2];
1386 
1387     /* Find out from the dialog how to repeat the command */
1388     if (XmToggleButtonGetState(rd->inSelToggle))
1389     {
1390         if (!rd->forWindow->buffer->primary.selected)
1391         {
1392             DialogF(DF_WARN, rd->shell, 1, "Repeat Macro",
1393                     "No selection in window to repeat within", "OK");
1394             XmProcessTraversal(rd->inSelToggle, XmTRAVERSE_CURRENT);
1395             return False;
1396         }
1397         params[0] = "in_selection";
1398     } else if (XmToggleButtonGetState(rd->toEndToggle))
1399     {
1400         params[0] = "to_end";
1401     } else
1402     {
1403         if (GetIntTextWarn(rd->repeatText, &nTimes, "number of times", True)
1404                 != TEXT_READ_OK)
1405         {
1406             XmProcessTraversal(rd->repeatText, XmTRAVERSE_CURRENT);
1407             return False;
1408         }
1409         sprintf(nTimesStr, "%d", nTimes);
1410         params[0] = nTimesStr;
1411     }
1412 
1413     /* Figure out which command user wants to repeat */
1414     if (XmToggleButtonGetState(rd->lastCmdToggle))
1415 	params[1] = NEditStrdup(rd->lastCommand);
1416     else {
1417 	if (ReplayMacro == NULL)
1418 	    return False;
1419 	params[1] = NEditStrdup(ReplayMacro);
1420     }
1421 
1422     /* call the action routine repeat_macro to do the work */
1423     XtCallActionProc(rd->forWindow->lastFocus, "repeat_macro", event, params,2);
1424     NEditFree(params[1]);
1425     return True;
1426 }
1427 
repeatCancelCB(Widget w,XtPointer clientData,XtPointer callData)1428 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData)
1429 {
1430     repeatDialog *rd = (repeatDialog *)clientData;
1431 
1432     XtDestroyWidget(rd->shell);
1433 }
1434 
repeatDestroyCB(Widget w,XtPointer clientData,XtPointer callData)1435 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1436 {
1437     repeatDialog *rd = (repeatDialog *)clientData;
1438 
1439     NEditFree(rd->lastCommand);
1440     NEditFree(rd);
1441 }
1442 
1443 /*
1444 ** Dispatches a macro to which repeats macro command in "command", either
1445 ** an integer number of times ("how" == positive integer), or within a
1446 ** selected range ("how" == REPEAT_IN_SEL), or to the end of the window
1447 ** ("how == REPEAT_TO_END).
1448 **
1449 ** Note that as with most macro routines, this returns BEFORE the macro is
1450 ** finished executing
1451 */
RepeatMacro(WindowInfo * window,const char * command,int how)1452 void RepeatMacro(WindowInfo *window, const char *command, int how)
1453 {
1454     Program *prog;
1455     char *errMsg, *stoppedAt, *loopMacro, *loopedCmd;
1456 
1457     if (command == NULL)
1458 	return;
1459 
1460     /* Wrap a for loop and counter/tests around the command */
1461     if (how == REPEAT_TO_END)
1462 	loopMacro = "lastCursor=-1\nstartPos=$cursor\n\
1463 while($cursor>=startPos&&$cursor!=lastCursor){\nlastCursor=$cursor\n%s\n}\n";
1464     else if (how == REPEAT_IN_SEL)
1465 	loopMacro = "selStart = $selection_start\nif (selStart == -1)\nreturn\n\
1466 selEnd = $selection_end\nset_cursor_pos(selStart)\nselect(0,0)\n\
1467 boundText = get_range(selEnd, selEnd+10)\n\
1468 while($cursor >= selStart && $cursor < selEnd && \\\n\
1469 get_range(selEnd, selEnd+10) == boundText) {\n\
1470 startLength = $text_length\n%s\n\
1471 selEnd += $text_length - startLength\n}\n";
1472     else
1473     	loopMacro = "for(i=0;i<%d;i++){\n%s\n}\n";
1474     loopedCmd = (char*)NEditMalloc(strlen(command) + strlen(loopMacro) + 25);
1475     if (how == REPEAT_TO_END || how == REPEAT_IN_SEL)
1476 	sprintf(loopedCmd, loopMacro, command);
1477     else
1478 	sprintf(loopedCmd, loopMacro, how, command);
1479 
1480     /* Parse the resulting macro into an executable program "prog" */
1481     prog = ParseMacro(loopedCmd, &errMsg, &stoppedAt);
1482     if (prog == NULL) {
1483 	fprintf(stderr, "NEdit internal error, repeat macro syntax wrong: %s\n",
1484     		errMsg);
1485     	return;
1486     }
1487     NEditFree(loopedCmd);
1488 
1489     /* run the executable program */
1490     runMacro(window, prog);
1491 }
1492 
1493 /*
1494 ** Macro recording action hook for Learn/Replay, added temporarily during
1495 ** learn.
1496 */
learnActionHook(Widget w,XtPointer clientData,String actionName,XEvent * event,String * params,Cardinal * numParams)1497 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
1498 	XEvent *event, String *params, Cardinal *numParams)
1499 {
1500     WindowInfo *window;
1501     int i;
1502     char *actionString;
1503 
1504     /* Select only actions in text panes in the window for which this
1505        action hook is recording macros (from clientData). */
1506     for (window=WindowList; window!=NULL; window=window->next) {
1507 	if (window->textArea == w)
1508 	    break;
1509 	for (i=0; i<window->nPanes; i++) {
1510     	    if (window->textPanes[i] == w)
1511     	    	break;
1512 	}
1513 	if (i < window->nPanes)
1514 	    break;
1515     }
1516     if (window == NULL || window != (WindowInfo *)clientData)
1517     	return;
1518 
1519     /* beep on un-recordable operations which require a mouse position, to
1520        remind the user that the action was not recorded */
1521     if (isMouseAction(actionName)) {
1522     	XBell(XtDisplay(w), 0);
1523     	return;
1524     }
1525 
1526     /* Record the action and its parameters */
1527     actionString = actionToString(w, actionName, event, params, *numParams);
1528     if (actionString != NULL) {
1529 	BufInsert(MacroRecordBuf, MacroRecordBuf->length, actionString);
1530 	NEditFree(actionString);
1531     }
1532 }
1533 
1534 /*
1535 ** Permanent action hook for remembering last action for possible replay
1536 */
lastActionHook(Widget w,XtPointer clientData,String actionName,XEvent * event,String * params,Cardinal * numParams)1537 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
1538 	XEvent *event, String *params, Cardinal *numParams)
1539 {
1540     WindowInfo *window;
1541     int i;
1542     char *actionString;
1543 
1544     /* Find the window to which this action belongs */
1545     for (window=WindowList; window!=NULL; window=window->next) {
1546 	if (window->textArea == w)
1547 	    break;
1548 	for (i=0; i<window->nPanes; i++) {
1549     	    if (window->textPanes[i] == w)
1550     	    	break;
1551 	}
1552 	if (i < window->nPanes)
1553 	    break;
1554     }
1555     if (window == NULL)
1556     	return;
1557 
1558     /* The last action is recorded for the benefit of repeating the last
1559        action.  Don't record repeat_macro and wipe out the real action */
1560     if (!strcmp(actionName, "repeat_macro"))
1561 	return;
1562 
1563     /* Record the action and its parameters */
1564     actionString = actionToString(w, actionName, event, params, *numParams);
1565     if (actionString != NULL) {
1566         NEditFree(LastCommand);
1567 	LastCommand = actionString;
1568     }
1569 }
1570 
1571 /*
1572 ** Create a macro string to represent an invocation of an action routine.
1573 ** Returns NULL for non-operational or un-recordable actions.
1574 */
actionToString(Widget w,char * actionName,XEvent * event,String * params,Cardinal numParams)1575 static char *actionToString(Widget w, char *actionName, XEvent *event,
1576 	String *params, Cardinal numParams)
1577 {
1578     char chars[20], *charList[1], *outStr, *outPtr;
1579     KeySym keysym;
1580     int i, nChars, nParams, length, nameLength;
1581 #ifndef NO_XMIM
1582     int status;
1583 #endif
1584 
1585     if (isIgnoredAction(actionName) || isRedundantAction(actionName) ||
1586 	    isMouseAction(actionName))
1587     	return NULL;
1588 
1589     /* Convert self_insert actions, to insert_string */
1590     if (!strcmp(actionName, "self_insert") ||
1591     	    !strcmp(actionName, "self-insert")) {
1592     	actionName = "insert_string";
1593 #ifdef NO_XMIM
1594 	nChars = XLookupString((XKeyEvent *)event, chars, 19, &keysym, NULL);
1595 	if (nChars == 0)
1596 	    return NULL;
1597 #else
1598 
1599 	nChars = XmImMbLookupString(w, (XKeyEvent *)event,
1600 				    chars, 19, &keysym, &status);
1601 	if (nChars == 0 || status == XLookupNone ||
1602 		status == XLookupKeySym || status == XBufferOverflow)
1603 	    return NULL;
1604 #endif
1605     	chars[nChars] = '\0';
1606     	charList[0] = chars;
1607     	params = charList;
1608     	nParams = 1;
1609     } else
1610     	nParams = numParams;
1611 
1612     /* Figure out the length of string required */
1613     nameLength = strlen(actionName);
1614     length = nameLength + 3;
1615     for (i=0; i<nParams; i++)
1616 	length += escapedStringLength(params[i]) + 4;
1617 
1618     /* Allocate the string and copy the information to it */
1619     outPtr = outStr = (char*)NEditMalloc(length + 1);
1620     strcpy(outPtr, actionName);
1621     outPtr += nameLength;
1622     *outPtr++ = '(';
1623     for (i=0; i<nParams; i++) {
1624 	*outPtr++ = '\"';
1625 	outPtr += escapeStringChars(params[i], outPtr);
1626 	*outPtr++ = '\"'; *outPtr++ = ','; *outPtr++ = ' ';
1627     }
1628     if (nParams != 0)
1629 	outPtr -= 2;
1630     *outPtr++ = ')'; *outPtr++ = '\n'; *outPtr++ = '\0';
1631     return outStr;
1632 }
1633 
isMouseAction(const char * action)1634 static int isMouseAction(const char *action)
1635 {
1636     int i;
1637 
1638     for (i=0; i<(int)XtNumber(MouseActions); i++)
1639     	if (!strcmp(action, MouseActions[i]))
1640     	    return True;
1641     return False;
1642 }
1643 
isRedundantAction(const char * action)1644 static int isRedundantAction(const char *action)
1645 {
1646     int i;
1647 
1648     for (i=0; i<(int)XtNumber(RedundantActions); i++)
1649     	if (!strcmp(action, RedundantActions[i]))
1650     	    return True;
1651     return False;
1652 }
1653 
isIgnoredAction(const char * action)1654 static int isIgnoredAction(const char *action)
1655 {
1656     int i;
1657 
1658     for (i=0; i<(int)XtNumber(IgnoredActions); i++)
1659     	if (!strcmp(action, IgnoredActions[i]))
1660     	    return True;
1661     return False;
1662 }
1663 
1664 /*
1665 ** Timer proc for putting up the "Macro Command in Progress" banner if
1666 ** the process is taking too long.
1667 */
1668 #define MAX_TIMEOUT_MSG_LEN (MAX_ACCEL_LEN + 60)
bannerTimeoutProc(XtPointer clientData,XtIntervalId * id)1669 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id)
1670 {
1671     WindowInfo *window = (WindowInfo *)clientData;
1672     macroCmdInfo *cmdData = window->macroCmdData;
1673     XmString xmCancel;
1674     char *cCancel = "\0";
1675     char message[MAX_TIMEOUT_MSG_LEN];
1676 
1677     cmdData->bannerIsUp = True;
1678 
1679     /* Extract accelerator text from menu PushButtons */
1680     XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
1681 
1682     if (!XmStringEmpty(xmCancel))
1683         {
1684         /* Translate Motif string to char* */
1685         cCancel = GetXmStringText(xmCancel);
1686 
1687         /* Free Motif String */
1688         XmStringFree(xmCancel);
1689         }
1690 
1691     /* Create message */
1692     if (cCancel[0] == '\0') {
1693         strncpy(message, "Macro Command in Progress", MAX_TIMEOUT_MSG_LEN);
1694         message[MAX_TIMEOUT_MSG_LEN - 1] = '\0';
1695     }
1696     else {
1697         sprintf(message,
1698             "Macro Command in Progress -- Press %s to Cancel",
1699             cCancel);
1700     }
1701 
1702     /* Free C-string */
1703     NEditFree(cCancel);
1704 
1705     SetModeMessage(window, message);
1706     cmdData->bannerTimeoutID = 0;
1707 }
1708 
1709 /*
1710 ** Work proc for continuing execution of a preempted macro.
1711 **
1712 ** Xt WorkProcs are designed to run first-in first-out, which makes them
1713 ** very bad at sharing time between competing tasks.  For this reason, it's
1714 ** usually bad to use work procs anywhere where their execution is likely to
1715 ** overlap.  Using a work proc instead of a timer proc (which I usually
1716 ** prefer) here means macros will probably share time badly, but we're more
1717 ** interested in making the macros cancelable, and in continuing other work
1718 ** than having users run a bunch of them at once together.
1719 */
continueWorkProc(XtPointer clientData)1720 static Boolean continueWorkProc(XtPointer clientData)
1721 {
1722     WindowInfo *window = (WindowInfo *)clientData;
1723     macroCmdInfo *cmdData = window->macroCmdData;
1724     char *errMsg;
1725     int stat;
1726     DataValue result;
1727 
1728     stat = ContinueMacro(cmdData->context, &result, &errMsg);
1729     if (stat == MACRO_ERROR)
1730     {
1731         finishMacroCmdExecution(window);
1732         DialogF(DF_ERR, window->shell, 1, "Macro Error",
1733                 "Error executing macro: %s", "OK", errMsg);
1734         return True;
1735     } else if (stat == MACRO_DONE)
1736     {
1737         finishMacroCmdExecution(window);
1738         return True;
1739     } else if (stat == MACRO_PREEMPT)
1740     {
1741         cmdData->continueWorkProcID = 0;
1742         return True;
1743     }
1744 
1745     /* Macro exceeded time slice, re-schedule it */
1746     if (stat != MACRO_TIME_LIMIT)
1747     	return True; /* shouldn't happen */
1748     return False;
1749 }
1750 
1751 /*
1752 ** Copy fromString to toString replacing special characters in strings, such
1753 ** that they can be read back by the macro parser's string reader.  i.e. double
1754 ** quotes are replaced by \", backslashes are replaced with \\, C-std control
1755 ** characters like \n are replaced with their backslash counterparts.  This
1756 ** routine should be kept reasonably in sync with yylex in parse.y.  Companion
1757 ** routine escapedStringLength predicts the length needed to write the string
1758 ** when it is expanded with the additional characters.  Returns the number
1759 ** of characters to which the string expanded.
1760 */
escapeStringChars(char * fromString,char * toString)1761 static int escapeStringChars(char *fromString, char *toString)
1762 {
1763     char *e, *c, *outPtr = toString;
1764 
1765     /* substitute escape sequences */
1766     for (c=fromString; *c!='\0'; c++) {
1767     	for (e=EscapeChars; *e!='\0'; e++) {
1768     	    if (*c == *e) {
1769     		*outPtr++ = '\\';
1770     		*outPtr++ = ReplaceChars[e-EscapeChars];
1771 		break;
1772 	    }
1773 	}
1774 	if (*e == '\0')
1775     	   *outPtr++ = *c;
1776     }
1777     *outPtr = '\0';
1778     return outPtr - toString;
1779 }
1780 
1781 /*
1782 ** Predict the length of a string needed to hold a copy of "string" with
1783 ** special characters replaced with escape sequences by escapeStringChars.
1784 */
escapedStringLength(char * string)1785 static int escapedStringLength(char *string)
1786 {
1787     char *c, *e;
1788     int length = 0;
1789 
1790     /* calculate length and allocate returned string */
1791     for (c=string; *c!='\0'; c++) {
1792     	for (e=EscapeChars; *e!='\0'; e++) {
1793 	    if (*c == *e) {
1794     		length++;
1795 		break;
1796 	    }
1797 	}
1798     	length++;
1799     }
1800     return length;
1801 }
1802 
1803 /*
1804 ** Built-in macro subroutine for getting the length of a string
1805 */
lengthMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1806 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
1807     	DataValue *result, char **errMsg)
1808 {
1809     char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
1810 
1811     if (nArgs != 1)
1812     	return wrongNArgsErr(errMsg);
1813     if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1814 	return False;
1815     result->tag = INT_TAG;
1816     result->val.n = strlen(string);
1817     return True;
1818 }
1819 
1820 /*
1821 ** Built-in macro subroutines for min and max
1822 */
minMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1823 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
1824     	DataValue *result, char **errMsg)
1825 {
1826     int minVal, value, i;
1827 
1828     if (nArgs == 1)
1829     	return tooFewArgsErr(errMsg);
1830     if (!readIntArg(argList[0], &minVal, errMsg))
1831     	return False;
1832     for (i=0; i<nArgs; i++) {
1833 	if (!readIntArg(argList[i], &value, errMsg))
1834     	    return False;
1835     	minVal = value < minVal ? value : minVal;
1836     }
1837     result->tag = INT_TAG;
1838     result->val.n = minVal;
1839     return True;
1840 }
maxMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1841 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
1842     	DataValue *result, char **errMsg)
1843 {
1844     int maxVal, value, i;
1845 
1846     if (nArgs == 1)
1847     	return tooFewArgsErr(errMsg);
1848     if (!readIntArg(argList[0], &maxVal, errMsg))
1849     	return False;
1850     for (i=0; i<nArgs; i++) {
1851 	if (!readIntArg(argList[i], &value, errMsg))
1852     	    return False;
1853     	maxVal = value > maxVal ? value : maxVal;
1854     }
1855     result->tag = INT_TAG;
1856     result->val.n = maxVal;
1857     return True;
1858 }
1859 
focusWindowMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1860 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
1861       DataValue *result, char **errMsg)
1862 {
1863     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1864     WindowInfo *w;
1865     char fullname[MAXPATHLEN];
1866     char normalizedString[MAXPATHLEN];
1867 
1868     /* Read the argument representing the window to focus to, and translate
1869        it into a pointer to a real WindowInfo */
1870     if (nArgs != 1)
1871     	return wrongNArgsErr(errMsg);
1872 
1873     if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
1874         return False;
1875     } else if (!strcmp(string, "last")) {
1876         w = WindowList;
1877     } else if (!strcmp(string, "next")) {
1878         w = window->next;
1879     } else if (strlen(string) >= MAXPATHLEN) {
1880         *errMsg = "Pathname too long in focus_window()";
1881         return False;
1882     } else {
1883         /* just use the plain name as supplied */
1884 	for (w=WindowList; w != NULL; w = w->next) {
1885 	    sprintf(fullname, "%s%s", w->path, w->filename);
1886 	    if (!strcmp(string, fullname)) {
1887 		break;
1888 	    }
1889 	}
1890         /* didn't work? try normalizing the string passed in */
1891         if (w == NULL) {
1892             strncpy(normalizedString, string, MAXPATHLEN);
1893             normalizedString[MAXPATHLEN-1] = '\0';
1894             if (1 == NormalizePathname(normalizedString)) {
1895                 /*  Something is broken with the input pathname. */
1896                 *errMsg = "Pathname too long in focus_window()";
1897                 return False;
1898             }
1899             for (w=WindowList; w != NULL; w = w->next) {
1900                 sprintf(fullname, "%s%s", w->path, w->filename);
1901                 if (!strcmp(normalizedString, fullname))
1902                     break;
1903             }
1904         }
1905     }
1906 
1907     /* If no matching window was found, return empty string and do nothing */
1908     if (w == NULL) {
1909 	result->tag = STRING_TAG;
1910 	result->val.str.rep = PERM_ALLOC_STR("");
1911         result->val.str.len = 0;
1912 	return True;
1913     }
1914 
1915     /* Change the focused window to the requested one */
1916     SetMacroFocusWindow(w);
1917 
1918     /* turn on syntax highlight that might have been deferred */
1919     if (w->highlightSyntax && w->highlightData==NULL)
1920     	StartHighlighting(w, False);
1921 
1922     /* Return the name of the window */
1923     result->tag = STRING_TAG;
1924     AllocNString(&result->val.str, strlen(w->path)+strlen(w->filename)+1);
1925     sprintf(result->val.str.rep, "%s%s", w->path, w->filename);
1926     return True;
1927 }
1928 
1929 /*
1930 ** Built-in macro subroutine for getting text from the current window's text
1931 ** buffer
1932 */
getRangeMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1933 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1934     	DataValue *result, char **errMsg)
1935 {
1936     int from, to;
1937     textBuffer *buf = window->buffer;
1938     char *rangeText;
1939 
1940     /* Validate arguments and convert to int */
1941     if (nArgs != 2)
1942     	return wrongNArgsErr(errMsg);
1943     if (!readIntArg(argList[0], &from, errMsg))
1944     	return False;
1945     if (!readIntArg(argList[1], &to, errMsg))
1946 	return False;
1947     if (from < 0) from = 0;
1948     if (from > buf->length) from = buf->length;
1949     if (to < 0) to = 0;
1950     if (to > buf->length) to = buf->length;
1951     if (from > to) {int temp = from; from = to; to = temp;}
1952 
1953     /* Copy text from buffer (this extra copy could be avoided if textBuf.c
1954        provided a routine for writing into a pre-allocated string) */
1955     result->tag = STRING_TAG;
1956     AllocNString(&result->val.str, to - from + 1);
1957     rangeText = BufGetRange(buf, from, to);
1958     BufUnsubstituteNullChars(rangeText, buf);
1959     strcpy(result->val.str.rep, rangeText);
1960     /* Note: after the un-substitution, it is possible that strlen() != len,
1961        but that's because strlen() can't deal with 0-characters. */
1962     NEditFree(rangeText);
1963     return True;
1964 }
1965 
1966 /*
1967 ** Built-in macro subroutine for getting a single character at the position
1968 ** given, from the current window
1969 */
getCharacterMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1970 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
1971     	DataValue *result, char **errMsg)
1972 {
1973     int pos;
1974     textBuffer *buf = window->buffer;
1975 
1976     /* Validate argument and convert it to int */
1977     if (nArgs != 1)
1978     	return wrongNArgsErr(errMsg);
1979     if (!readIntArg(argList[0], &pos, errMsg))
1980     	return False;
1981     if (pos < 0) pos = 0;
1982     if (pos > buf->length) pos = buf->length;
1983 
1984     /* Return the character in a pre-allocated string) */
1985     result->tag = STRING_TAG;
1986     AllocNString(&result->val.str, 2);
1987     result->val.str.rep[0] = BufGetCharacter(buf, pos);
1988     BufUnsubstituteNullChars(result->val.str.rep, buf);
1989     /* Note: after the un-substitution, it is possible that strlen() != len,
1990        but that's because strlen() can't deal with 0-characters. */
1991     return True;
1992 }
1993 
1994 /*
1995 ** Built-in macro subroutine for replacing text in the current window's text
1996 ** buffer
1997 */
replaceRangeMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)1998 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1999     	DataValue *result, char **errMsg)
2000 {
2001     int from, to;
2002     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2003     textBuffer *buf = window->buffer;
2004 
2005     /* Validate arguments and convert to int */
2006     if (nArgs != 3)
2007     	return wrongNArgsErr(errMsg);
2008     if (!readIntArg(argList[0], &from, errMsg))
2009     	return False;
2010     if (!readIntArg(argList[1], &to, errMsg))
2011 	return False;
2012     if (!readStringArg(argList[2], &string, stringStorage, errMsg))
2013     	return False;
2014     if (from < 0) from = 0;
2015     if (from > buf->length) from = buf->length;
2016     if (to < 0) to = 0;
2017     if (to > buf->length) to = buf->length;
2018     if (from > to) {int temp = from; from = to; to = temp;}
2019 
2020     /* Don't allow modifications if the window is read-only */
2021     if (IS_ANY_LOCKED(window->lockReasons)) {
2022 	XBell(XtDisplay(window->shell), 0);
2023 	result->tag = NO_TAG;
2024 	return True;
2025     }
2026 
2027     /* There are no null characters in the string (because macro strings
2028        still have null termination), but if the string contains the
2029        character used by the buffer for null substitution, it could
2030        theoretically become a null.  In the highly unlikely event that
2031        all of the possible substitution characters in the buffer are used
2032        up, stop the macro and tell the user of the failure */
2033     if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
2034 	*errMsg = "Too much binary data in file";
2035 	return False;
2036     }
2037 
2038     /* Do the replace */
2039     BufReplace(buf, from, to, string);
2040     result->tag = NO_TAG;
2041     return True;
2042 }
2043 
2044 /*
2045 ** Built-in macro subroutine for replacing the primary-selection selected
2046 ** text in the current window's text buffer
2047 */
replaceSelectionMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2048 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2049     	DataValue *result, char **errMsg)
2050 {
2051     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2052 
2053     /* Validate argument and convert to string */
2054     if (nArgs != 1)
2055     	return wrongNArgsErr(errMsg);
2056     if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2057     	return False;
2058 
2059     /* Don't allow modifications if the window is read-only */
2060     if (IS_ANY_LOCKED(window->lockReasons)) {
2061 	XBell(XtDisplay(window->shell), 0);
2062 	result->tag = NO_TAG;
2063 	return True;
2064     }
2065 
2066     /* There are no null characters in the string (because macro strings
2067        still have null termination), but if the string contains the
2068        character used by the buffer for null substitution, it could
2069        theoretically become a null.  In the highly unlikely event that
2070        all of the possible substitution characters in the buffer are used
2071        up, stop the macro and tell the user of the failure */
2072     if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
2073 	*errMsg = "Too much binary data in file";
2074 	return False;
2075     }
2076 
2077     /* Do the replace */
2078     BufReplaceSelected(window->buffer, string);
2079     result->tag = NO_TAG;
2080     return True;
2081 }
2082 
2083 /*
2084 ** Built-in macro subroutine for getting the text currently selected by
2085 ** the primary selection in the current window's text buffer, or in any
2086 ** part of screen if "any" argument is given
2087 */
getSelectionMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2088 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2089     	DataValue *result, char **errMsg)
2090 {
2091     char *selText;
2092 
2093     /* Read argument list to check for "any" keyword, and get the appropriate
2094        selection */
2095     if (nArgs != 0 && nArgs != 1)
2096       	return wrongNArgsErr(errMsg);
2097     if (nArgs == 1) {
2098         if (argList[0].tag != STRING_TAG || strcmp(argList[0].val.str.rep, "any")) {
2099 	    *errMsg = "Unrecognized argument to %s";
2100 	    return False;
2101     	}
2102 	selText = GetAnySelection(window);
2103 	if (selText == NULL)
2104 	    selText = NEditStrdup("");
2105     } else {
2106 	selText = BufGetSelectionText(window->buffer);
2107     	BufUnsubstituteNullChars(selText, window->buffer);
2108     }
2109 
2110     /* Return the text as an allocated string */
2111     result->tag = STRING_TAG;
2112     AllocNStringCpy(&result->val.str, selText);
2113     NEditFree(selText);
2114     return True;
2115 }
2116 
2117 /*
2118 ** Built-in macro subroutine for determining if implicit conversion of
2119 ** a string to number will succeed or fail
2120 */
validNumberMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2121 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
2122         DataValue *result, char **errMsg)
2123 {
2124     char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
2125 
2126     if (nArgs != 1) {
2127         return wrongNArgsErr(errMsg);
2128     }
2129     if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
2130         return False;
2131     }
2132 
2133     result->tag = INT_TAG;
2134     result->val.n = StringToNum(string, NULL);
2135 
2136     return True;
2137 }
2138 
2139 /*
2140 ** Built-in macro subroutine for replacing a substring within another string
2141 */
replaceSubstringMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2142 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
2143     	DataValue *result, char **errMsg)
2144 {
2145     int from, to, length, replaceLen, outLen;
2146     char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *replStr;
2147 
2148     /* Validate arguments and convert to int */
2149     if (nArgs != 4)
2150     	return wrongNArgsErr(errMsg);
2151     if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2152     	return False;
2153     if (!readIntArg(argList[1], &from, errMsg))
2154     	return False;
2155     if (!readIntArg(argList[2], &to, errMsg))
2156 	return False;
2157     if (!readStringArg(argList[3], &replStr, stringStorage[1], errMsg))
2158     	return False;
2159     length = strlen(string);
2160     if (from < 0) from = 0;
2161     if (from > length) from = length;
2162     if (to < 0) to = 0;
2163     if (to > length) to = length;
2164     if (from > to) {int temp = from; from = to; to = temp;}
2165 
2166     /* Allocate a new string and do the replacement */
2167     replaceLen = strlen(replStr);
2168     outLen = length - (to - from) + replaceLen;
2169     result->tag = STRING_TAG;
2170     AllocNString(&result->val.str, outLen+1);
2171     strncpy(result->val.str.rep, string, from);
2172     strncpy(&result->val.str.rep[from], replStr, replaceLen);
2173     strncpy(&result->val.str.rep[from + replaceLen], &string[to], length - to);
2174     return True;
2175 }
2176 
2177 /*
2178 ** Built-in macro subroutine for getting a substring of a string.
2179 ** Called as substring(string, from [, to])
2180 */
substringMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2181 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
2182     	DataValue *result, char **errMsg)
2183 {
2184     int from, to, length;
2185     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2186 
2187     /* Validate arguments and convert to int */
2188     if (nArgs != 2 && nArgs != 3)
2189     	return wrongNArgsErr(errMsg);
2190     if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2191     	return False;
2192     if (!readIntArg(argList[1], &from, errMsg))
2193     	return False;
2194     length = to = strlen(string);
2195     if (nArgs == 3)
2196         if (!readIntArg(argList[2], &to, errMsg))
2197             return False;
2198     if (from < 0) from += length;
2199     if (from < 0) from = 0;
2200     if (from > length) from = length;
2201     if (to < 0) to += length;
2202     if (to < 0) to = 0;
2203     if (to > length) to = length;
2204     if (from > to) to = from;
2205 
2206     /* Allocate a new string and copy the sub-string into it */
2207     result->tag = STRING_TAG;
2208     AllocNStringNCpy(&result->val.str, &string[from], to - from);
2209     return True;
2210 }
2211 
toupperMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2212 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
2213     	DataValue *result, char **errMsg)
2214 {
2215     int i, length;
2216     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2217 
2218     /* Validate arguments and convert to int */
2219     if (nArgs != 1)
2220     	return wrongNArgsErr(errMsg);
2221     if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2222     	return False;
2223     length = strlen(string);
2224 
2225     /* Allocate a new string and copy an uppercased version of the string it */
2226     result->tag = STRING_TAG;
2227     AllocNString(&result->val.str, length + 1);
2228     for (i=0; i<length; i++)
2229     	result->val.str.rep[i] = toupper((unsigned char)string[i]);
2230     return True;
2231 }
2232 
tolowerMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2233 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
2234     	DataValue *result, char **errMsg)
2235 {
2236     int i, length;
2237     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2238 
2239     /* Validate arguments and convert to int */
2240     if (nArgs != 1)
2241     	return wrongNArgsErr(errMsg);
2242     if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2243     	return False;
2244     length = strlen(string);
2245 
2246     /* Allocate a new string and copy an lowercased version of the string it */
2247     result->tag = STRING_TAG;
2248     AllocNString(&result->val.str, length + 1);
2249     for (i=0; i<length; i++)
2250     	result->val.str.rep[i] = tolower((unsigned char)string[i]);
2251     return True;
2252 }
2253 
stringToClipboardMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2254 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
2255     	DataValue *result, char **errMsg)
2256 {
2257     long itemID = 0;
2258     XmString s;
2259     int stat;
2260     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2261 
2262     /* Get the string argument */
2263     if (nArgs != 1)
2264     	return wrongNArgsErr(errMsg);
2265     if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2266     	return False;
2267 
2268     /* Use the XmClipboard routines to copy the text to the clipboard.
2269        If errors occur, just give up.  */
2270     result->tag = NO_TAG;
2271     stat = SpinClipboardStartCopy(TheDisplay, XtWindow(window->textArea),
2272     	  s=XmStringCreateSimple("NEdit"), XtLastTimestampProcessed(TheDisplay),
2273 	  window->textArea, NULL, &itemID);
2274     XmStringFree(s);
2275     if (stat != ClipboardSuccess)
2276     	return True;
2277     if (SpinClipboardCopy(TheDisplay, XtWindow(window->textArea), itemID, "STRING",
2278     	    string, strlen(string), 0, NULL) != ClipboardSuccess) {
2279         SpinClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2280     	return True;
2281     }
2282     SpinClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2283     return True;
2284 }
2285 
clipboardToStringMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2286 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2287     	DataValue *result, char **errMsg)
2288 {
2289     unsigned long length, retLength;
2290     long id = 0;
2291 
2292     /* Should have no arguments */
2293     if (nArgs != 0)
2294     	return wrongNArgsErr(errMsg);
2295 
2296     /* Ask if there's a string in the clipboard, and get its length */
2297     if (SpinClipboardInquireLength(TheDisplay, XtWindow(window->shell), "STRING",
2298     	    &length) != ClipboardSuccess) {
2299     	result->tag = STRING_TAG;
2300     	result->val.str.rep = PERM_ALLOC_STR("");
2301     	result->val.str.len = 0;
2302         /*
2303          * Possibly, the clipboard can remain in a locked state after
2304          * a failure, so we try to remove the lock, just to be sure.
2305          */
2306         SpinClipboardUnlock(TheDisplay, XtWindow(window->shell));
2307 	return True;
2308     }
2309 
2310     /* Allocate a new string to hold the data */
2311     result->tag = STRING_TAG;
2312     AllocNString(&result->val.str, (int)length + 1);
2313 
2314     /* Copy the clipboard contents to the string */
2315     if (SpinClipboardRetrieve(TheDisplay, XtWindow(window->shell), "STRING",
2316     	    result->val.str.rep, length, &retLength, &id) != ClipboardSuccess) {
2317     	retLength = 0;
2318         /*
2319          * Possibly, the clipboard can remain in a locked state after
2320          * a failure, so we try to remove the lock, just to be sure.
2321          */
2322         SpinClipboardUnlock(TheDisplay, XtWindow(window->shell));
2323     }
2324     result->val.str.rep[retLength] = '\0';
2325     result->val.str.len = retLength;
2326 
2327     return True;
2328 }
2329 
2330 
2331 /*
2332 ** Built-in macro subroutine for reading the contents of a text file into
2333 ** a string.  On success, returns 1 in $readStatus, and the contents of the
2334 ** file as a string in the subroutine return value.  On failure, returns
2335 ** the empty string "" and an 0 $readStatus.
2336 */
readFileMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2337 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2338     	DataValue *result, char **errMsg)
2339 {
2340     char stringStorage[TYPE_INT_STR_SIZE(int)], *name;
2341     struct stat statbuf;
2342     FILE *fp;
2343     int readLen;
2344 
2345     /* Validate arguments and convert to int */
2346     if (nArgs != 1)
2347     	return wrongNArgsErr(errMsg);
2348     if (!readStringArg(argList[0], &name, stringStorage, errMsg))
2349     	return False;
2350 
2351     /* Read the whole file into an allocated string */
2352     if ((fp = fopen(name, "r")) == NULL)
2353     	goto errorNoClose;
2354     if (fstat(fileno(fp), &statbuf) != 0)
2355     	goto error;
2356     result->tag = STRING_TAG;
2357     AllocNString(&result->val.str, statbuf.st_size+1);
2358     readLen = fread(result->val.str.rep, sizeof(char), statbuf.st_size+1, fp);
2359     if (ferror(fp))
2360 	goto error;
2361     if(!feof(fp)){
2362         /* Couldn't trust file size. Use slower but more general method */
2363         int chunkSize = 1024;
2364         char *buffer;
2365 
2366         buffer = (char*)NEditMalloc(readLen * sizeof(char));
2367         memcpy(buffer, result->val.str.rep, readLen * sizeof(char));
2368         while (!feof(fp)){
2369             buffer = NEditRealloc(buffer, (readLen+chunkSize)*sizeof(char));
2370             readLen += fread(&buffer[readLen], sizeof(char), chunkSize, fp);
2371             if (ferror(fp)){
2372                 NEditFree(buffer);
2373 	        goto error;
2374             }
2375         }
2376         AllocNString(&result->val.str, readLen + 1);
2377         memcpy(result->val.str.rep, buffer, readLen * sizeof(char));
2378         NEditFree(buffer);
2379     }
2380     fclose(fp);
2381 
2382     /* Return the results */
2383     ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2384     ReturnGlobals[READ_STATUS]->value.val.n = True;
2385     return True;
2386 
2387 error:
2388     fclose(fp);
2389 
2390 errorNoClose:
2391     ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2392     ReturnGlobals[READ_STATUS]->value.val.n = False;
2393     result->tag = STRING_TAG;
2394     result->val.str.rep = PERM_ALLOC_STR("");
2395     result->val.str.len = 0;
2396     return True;
2397 }
2398 
2399 /*
2400 ** Built-in macro subroutines for writing or appending a string (parameter $1)
2401 ** to a file named in parameter $2. Returns 1 on successful write, or 0 if
2402 ** unsuccessful.
2403 */
writeFileMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2404 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2405     	DataValue *result, char **errMsg)
2406 {
2407     return writeOrAppendFile(False, window, argList, nArgs, result, errMsg);
2408 }
2409 
appendFileMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2410 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2411     	DataValue *result, char **errMsg)
2412 {
2413     return writeOrAppendFile(True, window, argList, nArgs, result, errMsg);
2414 }
2415 
writeOrAppendFile(int append,WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2416 static int writeOrAppendFile(int append, WindowInfo *window,
2417     	DataValue *argList, int nArgs, DataValue *result, char **errMsg)
2418 {
2419     char stringStorage[2][TYPE_INT_STR_SIZE(int)], *name, *string;
2420     FILE *fp;
2421 
2422     /* Validate argument */
2423     if (nArgs != 2)
2424     	return wrongNArgsErr(errMsg);
2425     if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2426     	return False;
2427     if (!readStringArg(argList[1], &name, stringStorage[0], errMsg))
2428     	return False;
2429 
2430     /* open the file */
2431     if ((fp = fopen(name, append ? "a" : "w")) == NULL) {
2432 	result->tag = INT_TAG;
2433 	result->val.n = False;
2434 	return True;
2435     }
2436 
2437     /* write the string to the file */
2438     fwrite(string, sizeof(char), strlen(string), fp);
2439     if (ferror(fp)) {
2440 	fclose(fp);
2441 	result->tag = INT_TAG;
2442 	result->val.n = False;
2443 	return True;
2444     }
2445     fclose(fp);
2446 
2447     /* return the status */
2448     result->tag = INT_TAG;
2449     result->val.n = True;
2450     return True;
2451 }
2452 
2453 /*
2454 ** Built-in macro subroutine for searching silently in a window without
2455 ** dialogs, beeps, or changes to the selection.  Arguments are: $1: string to
2456 ** search for, $2: starting position. Optional arguments may include the
2457 ** strings: "wrap" to make the search wrap around the beginning or end of the
2458 ** string, "backward" or "forward" to change the search direction ("forward" is
2459 ** the default), "literal", "case" or "regex" to change the search type
2460 ** (default is "literal").
2461 **
2462 ** Returns the starting position of the match, or -1 if nothing matched.
2463 ** also returns the ending position of the match in $searchEndPos
2464 */
searchMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2465 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
2466     	DataValue *result, char **errMsg)
2467 {
2468     DataValue newArgList[9];
2469 
2470     /* Use the search string routine, by adding the buffer contents as
2471        the string argument */
2472     if (nArgs > 8)
2473     	return wrongNArgsErr(errMsg);
2474 
2475     /* we remove constness from BufAsString() result since we know
2476        searchStringMS will not modify the result */
2477     newArgList[0].tag = STRING_TAG;
2478     newArgList[0].val.str.rep = (char *)BufAsString(window->buffer);
2479     newArgList[0].val.str.len = window->buffer->length;
2480 
2481     /* copy other arguments to the new argument list */
2482     memcpy(&newArgList[1], argList, nArgs * sizeof(DataValue));
2483 
2484     return searchStringMS(window, newArgList, nArgs+1, result, errMsg);
2485 }
2486 
2487 /*
2488 ** Built-in macro subroutine for searching a string.  Arguments are $1:
2489 ** string to search in, $2: string to search for, $3: starting position.
2490 ** Optional arguments may include the strings: "wrap" to make the search
2491 ** wrap around the beginning or end of the string, "backward" or "forward"
2492 ** to change the search direction ("forward" is the default), "literal",
2493 ** "case" or "regex" to change the search type (default is "literal").
2494 **
2495 ** Returns the starting position of the match, or -1 if nothing matched.
2496 ** also returns the ending position of the match in $searchEndPos
2497 */
searchStringMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2498 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2499     	DataValue *result, char **errMsg)
2500 {
2501     int beginPos, wrap, direction, found = False, foundStart, foundEnd, type;
2502     int skipSearch = False, len;
2503     char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *searchStr;
2504 
2505     /* Validate arguments and convert to proper types */
2506     if (nArgs < 3)
2507     	return tooFewArgsErr(errMsg);
2508     if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2509     	return False;
2510     if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2511     	return False;
2512     if (!readIntArg(argList[2], &beginPos, errMsg))
2513     	return False;
2514     if (!readSearchArgs(&argList[3], nArgs-3, &direction, &type, &wrap, errMsg))
2515     	return False;
2516 
2517     len = argList[0].val.str.len;
2518     if (beginPos > len) {
2519 	if (direction == SEARCH_FORWARD) {
2520 	    if (wrap) {
2521 		beginPos = 0; /* Wrap immediately */
2522 	    } else {
2523 		found = False;
2524 		skipSearch = True;
2525 	    }
2526 	} else {
2527 	    beginPos = len;
2528 	}
2529     } else if (beginPos < 0) {
2530 	if (direction == SEARCH_BACKWARD) {
2531 	    if (wrap) {
2532 		beginPos = len; /* Wrap immediately */
2533 	    } else {
2534 		found = False;
2535 		skipSearch = True;
2536 	    }
2537 	} else {
2538 	    beginPos = 0;
2539 	}
2540     }
2541 
2542     if (!skipSearch)
2543 	found = SearchString(string, searchStr, direction, type, wrap, beginPos,
2544 	    &foundStart, &foundEnd, NULL, NULL, GetWindowDelimiters(window));
2545 
2546     /* Return the results */
2547     ReturnGlobals[SEARCH_END]->value.tag = INT_TAG;
2548     ReturnGlobals[SEARCH_END]->value.val.n = found ? foundEnd : 0;
2549     result->tag = INT_TAG;
2550     result->val.n = found ? foundStart : -1;
2551     return True;
2552 }
2553 
2554 /*
2555 ** Built-in macro subroutine for replacing all occurences of a search string in
2556 ** a string with a replacement string.  Arguments are $1: string to search in,
2557 ** $2: string to search for, $3: replacement string. Also takes an optional
2558 ** search type: one of "literal", "case" or "regex" (default is "literal"), and
2559 ** an optional "copy" argument.
2560 **
2561 ** Returns a new string with all of the replacements done.  If no replacements
2562 ** were performed and "copy" was specified, returns a copy of the original
2563 ** string.  Otherwise returns an empty string ("").
2564 */
replaceInStringMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2565 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2566     	DataValue *result, char **errMsg)
2567 {
2568     char stringStorage[3][TYPE_INT_STR_SIZE(int)], *string, *searchStr, *replaceStr;
2569     char *argStr, *replacedStr;
2570     int searchType = SEARCH_LITERAL, copyStart, copyEnd;
2571     int replacedLen, replaceEnd, force=False, i;
2572 
2573     /* Validate arguments and convert to proper types */
2574     if (nArgs < 3 || nArgs > 5)
2575     	return wrongNArgsErr(errMsg);
2576     if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2577     	return False;
2578     if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2579     	return False;
2580     if (!readStringArg(argList[2], &replaceStr, stringStorage[2], errMsg))
2581     	return False;
2582     for (i = 3; i < nArgs; i++) {
2583         /* Read the optional search type and force arguments */
2584 	if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
2585     	    return False;
2586 	if (!StringToSearchType(argStr, &searchType)) {
2587             /* It's not a search type.  is it "copy"? */
2588             if (!strcmp(argStr, "copy")) {
2589                 force = True;
2590             } else {
2591     	        *errMsg = "unrecognized argument to %s";
2592     	        return False;
2593             }
2594     	}
2595     }
2596 
2597     /* Do the replace */
2598     replacedStr = ReplaceAllInString(string, searchStr, replaceStr, searchType,
2599 	    &copyStart, &copyEnd, &replacedLen, GetWindowDelimiters(window));
2600 
2601     /* Return the results */
2602     result->tag = STRING_TAG;
2603     if (replacedStr == NULL) {
2604         if (force) {
2605             /* Just copy the original DataValue */
2606             if (argList[0].tag == STRING_TAG) {
2607                 result->val.str.rep = argList[0].val.str.rep;
2608                 result->val.str.len = argList[0].val.str.len;
2609             }
2610             else {
2611                 AllocNStringCpy(&result->val.str, string);
2612             }
2613         }
2614         else {
2615             result->val.str.rep = PERM_ALLOC_STR("");
2616             result->val.str.len = 0;
2617         }
2618     }
2619     else {
2620 	size_t remainder = strlen(&string[copyEnd]);
2621         replaceEnd = copyStart + replacedLen;
2622 	AllocNString(&result->val.str, replaceEnd + remainder + 1);
2623 	strncpy(result->val.str.rep, string, copyStart);
2624 	strcpy(&result->val.str.rep[copyStart], replacedStr);
2625 	strcpy(&result->val.str.rep[replaceEnd], &string[copyEnd]);
2626         NEditFree(replacedStr);
2627     }
2628     return True;
2629 }
2630 
readSearchArgs(DataValue * argList,int nArgs,int * searchDirection,int * searchType,int * wrap,char ** errMsg)2631 static int readSearchArgs(DataValue *argList, int nArgs, int *searchDirection,
2632 	int *searchType, int *wrap, char **errMsg)
2633 {
2634     int i;
2635     char *argStr, stringStorage[TYPE_INT_STR_SIZE(int)];
2636 
2637     *wrap = False;
2638     *searchDirection = SEARCH_FORWARD;
2639     *searchType = SEARCH_LITERAL;
2640     for (i=0; i<nArgs; i++) {
2641     	if (!readStringArg(argList[i], &argStr, stringStorage, errMsg))
2642     	    return False;
2643     	else if (!strcmp(argStr, "wrap"))
2644     	    *wrap = True;
2645     	else if (!strcmp(argStr, "nowrap"))
2646     	    *wrap = False;
2647     	else if (!strcmp(argStr, "backward"))
2648     	    *searchDirection = SEARCH_BACKWARD;
2649     	else if (!strcmp(argStr, "forward"))
2650     	    *searchDirection = SEARCH_FORWARD;
2651    	else if (!StringToSearchType(argStr, searchType)) {
2652     	    	*errMsg = "Unrecognized argument to %s";
2653     	    	return False;
2654 	}
2655     }
2656     return True;
2657 }
2658 
setCursorPosMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2659 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
2660     	DataValue *result, char **errMsg)
2661 {
2662     int pos;
2663 
2664     /* Get argument and convert to int */
2665     if (nArgs != 1)
2666     	return wrongNArgsErr(errMsg);
2667     if (!readIntArg(argList[0], &pos, errMsg))
2668     	return False;
2669 
2670     /* Set the position */
2671     TextSetCursorPos(window->lastFocus, pos);
2672     result->tag = NO_TAG;
2673     return True;
2674 }
2675 
selectMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2676 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
2677     	DataValue *result, char **errMsg)
2678 {
2679     int start, end, startTmp;
2680 
2681     /* Get arguments and convert to int */
2682     if (nArgs != 2)
2683     	return wrongNArgsErr(errMsg);
2684     if (!readIntArg(argList[0], &start, errMsg))
2685     	return False;
2686     if (!readIntArg(argList[1], &end, errMsg))
2687     	return False;
2688 
2689     /* Verify integrity of arguments */
2690     if (start > end) {
2691     	startTmp = start;
2692 	start = end;
2693 	end = startTmp;
2694     }
2695     if (start < 0) start = 0;
2696     if (start > window->buffer->length) start = window->buffer->length;
2697     if (end < 0) end = 0;
2698     if (end > window->buffer->length) end = window->buffer->length;
2699 
2700     /* Make the selection */
2701     BufSelect(window->buffer, start, end);
2702     result->tag = NO_TAG;
2703     return True;
2704 }
2705 
selectRectangleMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2706 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
2707     	DataValue *result, char **errMsg)
2708 {
2709     int start, end, left, right;
2710 
2711     /* Get arguments and convert to int */
2712     if (nArgs != 4)
2713     	return wrongNArgsErr(errMsg);
2714     if (!readIntArg(argList[0], &start, errMsg))
2715     	return False;
2716     if (!readIntArg(argList[1], &end, errMsg))
2717     	return False;
2718     if (!readIntArg(argList[2], &left, errMsg))
2719     	return False;
2720     if (!readIntArg(argList[3], &right, errMsg))
2721     	return False;
2722 
2723     /* Make the selection */
2724     BufRectSelect(window->buffer, start, end, left, right);
2725     result->tag = NO_TAG;
2726     return True;
2727 }
2728 
2729 /*
2730 ** Macro subroutine to ring the bell
2731 */
beepMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2732 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
2733     	DataValue *result, char **errMsg)
2734 {
2735     if (nArgs != 0)
2736     	return wrongNArgsErr(errMsg);
2737     XBell(XtDisplay(window->shell), 0);
2738     result->tag = NO_TAG;
2739     return True;
2740 }
2741 
tPrintMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2742 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
2743     	DataValue *result, char **errMsg)
2744 {
2745     char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2746     int i;
2747 
2748     if (nArgs == 0)
2749     	return tooFewArgsErr(errMsg);
2750     for (i=0; i<nArgs; i++) {
2751 	if (!readStringArg(argList[i], &string, stringStorage, errMsg))
2752 	    return False;
2753 	printf("%s%s", string, i==nArgs-1 ? "" : " ");
2754     }
2755     fflush( stdout );
2756     result->tag = NO_TAG;
2757     return True;
2758 }
2759 
2760 /*
2761 ** Built-in macro subroutine for getting the value of an environment variable
2762 */
getenvMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2763 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
2764     	DataValue *result, char **errMsg)
2765 {
2766     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
2767     char *name;
2768     char *value;
2769 
2770     /* Get name of variable to get */
2771     if (nArgs != 1)
2772       	return wrongNArgsErr(errMsg);
2773     if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
2774 	*errMsg = "argument to %s must be a string";
2775 	return False;
2776     }
2777     value = getenv(name);
2778     if (value == NULL)
2779 	value = "";
2780 
2781     /* Return the text as an allocated string */
2782     result->tag = STRING_TAG;
2783     AllocNStringCpy(&result->val.str, value);
2784     return True;
2785 }
2786 
shellCmdMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2787 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
2788     	DataValue *result, char **errMsg)
2789 {
2790     char stringStorage[2][TYPE_INT_STR_SIZE(int)], *cmdString, *inputString;
2791 
2792     if (nArgs != 2)
2793     	return wrongNArgsErr(errMsg);
2794     if (!readStringArg(argList[0], &cmdString, stringStorage[0], errMsg))
2795     	return False;
2796     if (!readStringArg(argList[1], &inputString, stringStorage[1], errMsg))
2797     	return False;
2798 
2799     /* Shell command execution requires that the macro be suspended, so
2800        this subroutine can't be run if macro execution can't be interrupted */
2801     if (MacroRunWindow()->macroCmdData == NULL) {
2802       *errMsg = "%s can't be called from non-suspendable context";
2803        return False;
2804     }
2805 
2806 #ifdef VMS
2807     *errMsg = "Shell commands not supported under VMS";
2808     return False;
2809 #else
2810     ShellCmdToMacroString(window, cmdString, inputString);
2811     result->tag = INT_TAG;
2812     result->val.n = 0;
2813     return True;
2814 #endif /*VMS*/
2815 }
2816 
2817 /*
2818 ** Method used by ShellCmdToMacroString (called by shellCmdMS), for returning
2819 ** macro string and exit status after the execution of a shell command is
2820 ** complete.  (Sorry about the poor modularity here, it's just not worth
2821 ** teaching other modules about macro return globals, since other than this,
2822 ** they're not used outside of macro.c)
2823 */
ReturnShellCommandOutput(WindowInfo * window,const char * outText,int status)2824 void ReturnShellCommandOutput(WindowInfo *window, const char *outText, int status)
2825 {
2826     DataValue retVal;
2827     macroCmdInfo *cmdData = window->macroCmdData;
2828 
2829     if (cmdData == NULL)
2830     	return;
2831     retVal.tag = STRING_TAG;
2832     AllocNStringCpy(&retVal.val.str, outText);
2833     ModifyReturnedValue(cmdData->context, retVal);
2834     ReturnGlobals[SHELL_CMD_STATUS]->value.tag = INT_TAG;
2835     ReturnGlobals[SHELL_CMD_STATUS]->value.val.n = status;
2836 }
2837 
dialogMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)2838 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2839     	DataValue *result, char **errMsg)
2840 {
2841     macroCmdInfo *cmdData;
2842     char stringStorage[TYPE_INT_STR_SIZE(int)];
2843     char btnStorage[TYPE_INT_STR_SIZE(int)];
2844     char *btnLabel;
2845     char *message;
2846     Arg al[20];
2847     int ac;
2848     Widget dialog, btn;
2849     int i, nBtns;
2850     XmString s1, s2;
2851 
2852     /* Ignore the focused window passed as the function argument and put
2853        the dialog up over the window which is executing the macro */
2854     window = MacroRunWindow();
2855     cmdData = window->macroCmdData;
2856 
2857     /* Dialogs require macro to be suspended and interleaved with other macros.
2858        This subroutine can't be run if macro execution can't be interrupted */
2859     if (!cmdData) {
2860       *errMsg = "%s can't be called from non-suspendable context";
2861        return False;
2862     }
2863 
2864     /* Read and check the arguments.  The first being the dialog message,
2865        and the rest being the button labels */
2866     if (nArgs == 0) {
2867     	*errMsg = "%s subroutine called with no arguments";
2868     	return False;
2869     }
2870     if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
2871         return False;
2872     }
2873 
2874     /* check that all button labels can be read */
2875     for (i=1; i<nArgs; i++) {
2876         if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg)) {
2877             return False;
2878         }
2879     }
2880 
2881     /* pick up the first button */
2882     if (nArgs == 1) {
2883         btnLabel = "OK";
2884         nBtns = 1;
2885     }
2886     else {
2887         nBtns = nArgs - 1;
2888         argList++;
2889         readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
2890     }
2891 
2892     /* Create the message box dialog widget and its dialog shell parent */
2893     ac = 0;
2894     XtSetArg(al[ac], XmNtitle, " "); ac++;
2895     XtSetArg(al[ac], XmNmessageString, s1=MKSTRING(message)); ac++;
2896     XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
2897     dialog = CreateMessageDialog(window->shell, "macroDialog", al, ac);
2898     if (1 == nArgs)
2899     {
2900         /*  Only set margin width for the default OK button  */
2901         XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2902                 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
2903                 NULL);
2904     }
2905 
2906     XmStringFree(s1);
2907     XmStringFree(s2);
2908     AddMotifCloseCallback(XtParent(dialog), dialogCloseCB, window);
2909     XtAddCallback(dialog, XmNokCallback, dialogBtnCB, window);
2910     XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2911     	    XmNuserData, (XtPointer)1, NULL);
2912     cmdData->dialog = dialog;
2913 
2914     /* Unmanage default buttons, except for "OK" */
2915     XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2916     XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2917 
2918     /* Make callback for the unmanaged cancel button (which can
2919        still get executed via the esc key) activate close box action */
2920     XtAddCallback(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2921 	    XmNactivateCallback, dialogCloseCB, window);
2922 
2923     /* Add user specified buttons (1st is already done) */
2924     for (i=1; i<nBtns; i++) {
2925         readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
2926     	btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2927     	    	XmNlabelString, s1=XmStringCreateSimple(btnLabel),
2928     	    	XmNuserData, (XtPointer)(intptr_t)(i+1), NULL);
2929     	XtAddCallback(btn, XmNactivateCallback, dialogBtnCB, window);
2930     	XmStringFree(s1);
2931     }
2932 
2933 #ifdef LESSTIF_VERSION
2934     /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2935        the escape key for closing the dialog (probably because the
2936        cancel button is not managed). */
2937     XtAddEventHandler(dialog, KeyPressMask, False, dialogEscCB,
2938             (XtPointer)window);
2939     XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
2940 	    True, GrabModeAsync, GrabModeAsync);
2941 #endif /* LESSTIF_VERSION */
2942 
2943     /* Put up the dialog */
2944     ManageDialogCenteredOnPointer(dialog);
2945 
2946     /* Stop macro execution until the dialog is complete */
2947     PreemptMacro();
2948 
2949     /* Return placeholder result.  Value will be changed by button callback */
2950     result->tag = INT_TAG;
2951     result->val.n = 0;
2952     return True;
2953 }
2954 
dialogBtnCB(Widget w,XtPointer clientData,XtPointer callData)2955 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData)
2956 {
2957     WindowInfo *window = (WindowInfo *)clientData;
2958     macroCmdInfo *cmdData = window->macroCmdData;
2959     XtPointer userData;
2960     DataValue retVal;
2961 
2962     /* Return the index of the button which was pressed (stored in the userData
2963        field of the button widget).  The 1st button, being a gadget, is not
2964        returned in w. */
2965     if (cmdData == NULL)
2966     	return; /* shouldn't happen */
2967     if (XtClass(w) == xmPushButtonWidgetClass) {
2968 	XtVaGetValues(w, XmNuserData, &userData, NULL);
2969 	retVal.val.n = (int)(intptr_t)userData;
2970     } else
2971     	retVal.val.n = 1;
2972     retVal.tag = INT_TAG;
2973     ModifyReturnedValue(cmdData->context, retVal);
2974 
2975     /* Pop down the dialog */
2976     XtDestroyWidget(XtParent(cmdData->dialog));
2977     cmdData->dialog = NULL;
2978 
2979     /* Continue preempted macro execution */
2980     ResumeMacroExecution(window);
2981 }
2982 
dialogCloseCB(Widget w,XtPointer clientData,XtPointer callData)2983 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData)
2984 {
2985     WindowInfo *window = (WindowInfo *)clientData;
2986     macroCmdInfo *cmdData = window->macroCmdData;
2987     DataValue retVal;
2988 
2989     /* Return 0 to show that the dialog was closed via the window close box */
2990     retVal.val.n = 0;
2991     retVal.tag = INT_TAG;
2992     ModifyReturnedValue(cmdData->context, retVal);
2993 
2994     /* Pop down the dialog */
2995     XtDestroyWidget(XtParent(cmdData->dialog));
2996     cmdData->dialog = NULL;
2997 
2998     /* Continue preempted macro execution */
2999     ResumeMacroExecution(window);
3000 }
3001 
3002 #ifdef LESSTIF_VERSION
dialogEscCB(Widget w,XtPointer clientData,XEvent * event,Boolean * cont)3003 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3004     	Boolean *cont)
3005 {
3006     if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3007     	return;
3008     if (clientData != NULL) {
3009         dialogCloseCB(w, (WindowInfo *)clientData, NULL);
3010     }
3011     *cont = False;
3012 }
3013 #endif /* LESSTIF_VERSION */
3014 
stringDialogMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3015 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3016     	DataValue *result, char **errMsg)
3017 {
3018     macroCmdInfo *cmdData;
3019     char stringStorage[TYPE_INT_STR_SIZE(int)];
3020     char btnStorage[TYPE_INT_STR_SIZE(int)];
3021     char *btnLabel;
3022     char *message;
3023     Widget dialog, btn;
3024     int i, nBtns;
3025     XmString s1, s2;
3026     Arg al[20];
3027     int ac;
3028 
3029     /* Ignore the focused window passed as the function argument and put
3030        the dialog up over the window which is executing the macro */
3031     window = MacroRunWindow();
3032     cmdData = window->macroCmdData;
3033 
3034     /* Dialogs require macro to be suspended and interleaved with other macros.
3035        This subroutine can't be run if macro execution can't be interrupted */
3036     if (!cmdData) {
3037       *errMsg = "%s can't be called from non-suspendable context";
3038        return False;
3039     }
3040 
3041     /* Read and check the arguments.  The first being the dialog message,
3042        and the rest being the button labels */
3043     if (nArgs == 0) {
3044     	*errMsg = "%s subroutine called with no arguments";
3045     	return False;
3046     }
3047     if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
3048         return False;
3049     }
3050     /* check that all button labels can be read */
3051     for (i=1; i<nArgs; i++) {
3052         if (!readStringArg(argList[i], &btnLabel, stringStorage, errMsg)) {
3053             return False;
3054         }
3055     }
3056     if (nArgs == 1) {
3057         btnLabel = "OK";
3058         nBtns = 1;
3059     }
3060     else {
3061         nBtns = nArgs - 1;
3062         argList++;
3063         readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
3064     }
3065 
3066     /* Create the selection box dialog widget and its dialog shell parent */
3067     ac = 0;
3068     XtSetArg(al[ac], XmNtitle, " "); ac++;
3069     XtSetArg(al[ac], XmNselectionLabelString, s1=MKSTRING(message)); ac++;
3070     XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
3071     dialog = CreatePromptDialog(window->shell, "macroStringDialog", al, ac);
3072     if (1 == nArgs)
3073     {
3074         /*  Only set margin width for the default OK button  */
3075         XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3076                 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
3077                 NULL);
3078     }
3079 
3080     XmStringFree(s1);
3081     XmStringFree(s2);
3082     AddMotifCloseCallback(XtParent(dialog), stringDialogCloseCB, window);
3083     XtAddCallback(dialog, XmNokCallback, stringDialogBtnCB, window);
3084     XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3085     	    XmNuserData, (XtPointer)1, NULL);
3086     cmdData->dialog = dialog;
3087 
3088     /* Unmanage unneded widgets */
3089     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3090     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3091 
3092     /* Make callback for the unmanaged cancel button (which can
3093        still get executed via the esc key) activate close box action */
3094     XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3095 	    XmNactivateCallback, stringDialogCloseCB, window);
3096 
3097     /* Add user specified buttons (1st is already done).  Selection box
3098        requires a place-holder widget to be added before buttons can be
3099        added, that's what the separator below is for */
3100     XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3101     for (i=1; i<nBtns; i++) {
3102         readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
3103     	btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3104     	    	XmNlabelString, s1=XmStringCreateSimple(btnLabel),
3105     	    	XmNuserData, (XtPointer)(intptr_t)(i+1), NULL);
3106     	XtAddCallback(btn, XmNactivateCallback, stringDialogBtnCB, window);
3107     	XmStringFree(s1);
3108     }
3109 
3110 #ifdef LESSTIF_VERSION
3111     /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3112        the escape key for closing the dialog (probably because the
3113        cancel button is not managed). */
3114     XtAddEventHandler(dialog, KeyPressMask, False, stringDialogEscCB,
3115             (XtPointer)window);
3116     XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3117 	    True, GrabModeAsync, GrabModeAsync);
3118 #endif /* LESSTIF_VERSION */
3119 
3120     /* Put up the dialog */
3121     ManageDialogCenteredOnPointer(dialog);
3122 
3123     /* Stop macro execution until the dialog is complete */
3124     PreemptMacro();
3125 
3126     /* Return placeholder result.  Value will be changed by button callback */
3127     result->tag = INT_TAG;
3128     result->val.n = 0;
3129     return True;
3130 }
3131 
stringDialogBtnCB(Widget w,XtPointer clientData,XtPointer callData)3132 static void stringDialogBtnCB(Widget w, XtPointer clientData,
3133     	XtPointer callData)
3134 {
3135     WindowInfo *window = (WindowInfo *)clientData;
3136     macroCmdInfo *cmdData = window->macroCmdData;
3137     XtPointer userData;
3138     DataValue retVal;
3139     char *text;
3140     int btnNum;
3141 
3142     /* shouldn't happen, but would crash if it did */
3143     if (cmdData == NULL)
3144     	return;
3145 
3146     /* Return the string entered in the selection text area */
3147     text = XmTextGetString(XmSelectionBoxGetChild(cmdData->dialog,
3148     	    XmDIALOG_TEXT));
3149     retVal.tag = STRING_TAG;
3150     AllocNStringCpy(&retVal.val.str, text);
3151     NEditFree(text);
3152     ModifyReturnedValue(cmdData->context, retVal);
3153 
3154     /* Find the index of the button which was pressed (stored in the userData
3155        field of the button widget).  The 1st button, being a gadget, is not
3156        returned in w. */
3157     if (XtClass(w) == xmPushButtonWidgetClass) {
3158 	XtVaGetValues(w, XmNuserData, &userData, NULL);
3159 	btnNum = (int)(intptr_t)userData;
3160     } else
3161     	btnNum = 1;
3162 
3163     /* Return the button number in the global variable $string_dialog_button */
3164     ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3165     ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = btnNum;
3166 
3167     /* Pop down the dialog */
3168     XtDestroyWidget(XtParent(cmdData->dialog));
3169     cmdData->dialog = NULL;
3170 
3171     /* Continue preempted macro execution */
3172     ResumeMacroExecution(window);
3173 }
3174 
stringDialogCloseCB(Widget w,XtPointer clientData,XtPointer callData)3175 static void stringDialogCloseCB(Widget w, XtPointer clientData,
3176     	XtPointer callData)
3177 {
3178     WindowInfo *window = (WindowInfo *)clientData;
3179     macroCmdInfo *cmdData = window->macroCmdData;
3180     DataValue retVal;
3181 
3182     /* shouldn't happen, but would crash if it did */
3183     if (cmdData == NULL)
3184     	return;
3185 
3186     /* Return an empty string */
3187     retVal.tag = STRING_TAG;
3188     retVal.val.str.rep = PERM_ALLOC_STR("");
3189     retVal.val.str.len = 0;
3190     ModifyReturnedValue(cmdData->context, retVal);
3191 
3192     /* Return button number 0 in the global variable $string_dialog_button */
3193     ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3194     ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = 0;
3195 
3196     /* Pop down the dialog */
3197     XtDestroyWidget(XtParent(cmdData->dialog));
3198     cmdData->dialog = NULL;
3199 
3200     /* Continue preempted macro execution */
3201     ResumeMacroExecution(window);
3202 }
3203 
3204 #ifdef LESSTIF_VERSION
stringDialogEscCB(Widget w,XtPointer clientData,XEvent * event,Boolean * cont)3205 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3206     	Boolean *cont)
3207 {
3208     if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3209     	return;
3210     if (clientData != NULL) {
3211         stringDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3212     }
3213     *cont = False;
3214 }
3215 #endif /* LESSTIF_VERSION */
3216 
3217 /*
3218 ** A subroutine to put up a calltip
3219 ** First arg is either text to be displayed or a key for tip/tag lookup.
3220 ** Optional second arg is the buffer position beneath which to display the
3221 **      upper-left corner of the tip.  Default (or -1) puts it under the cursor.
3222 ** Additional optional arguments:
3223 **      "tipText": (default) Indicates first arg is text to be displayed in tip.
3224 **      "tipKey":   Indicates first arg is key in calltips database.  If key
3225 **                  is not found in tip database then the tags database is also
3226 **                  searched.
3227 **      "tagKey":   Indicates first arg is key in tags database.  (Skips
3228 **                  search in calltips database.)
3229 **      "center":   Horizontally center the calltip at the position
3230 **      "right":    Put the right edge of the calltip at the position
3231 **                  "center" and "right" cannot both be specified.
3232 **      "above":    Place the calltip above the position
3233 **      "strict":   Don't move the calltip to keep it on-screen and away
3234 **                  from the cursor's line.
3235 **
3236 ** Returns the new calltip's ID on success, 0 on failure.
3237 **
3238 ** Does this need to go on IgnoredActions?  I don't think so, since
3239 ** showing a calltip may be part of the action you want to learn.
3240 */
calltipMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3241 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3242       DataValue *result, char **errMsg)
3243 {
3244     char stringStorage[TYPE_INT_STR_SIZE(int)], *tipText, *txtArg;
3245     Boolean anchored = False, lookup = True;
3246     int mode = -1, i;
3247     int anchorPos, hAlign = TIP_LEFT, vAlign = TIP_BELOW,
3248             alignMode = TIP_SLOPPY;
3249 
3250     /* Read and check the string */
3251     if (nArgs < 1) {
3252         *errMsg = "%s subroutine called with too few arguments";
3253         return False;
3254     }
3255     if (nArgs > 6) {
3256         *errMsg = "%s subroutine called with too many arguments";
3257         return False;
3258     }
3259 
3260     /* Read the tip text or key */
3261     if (!readStringArg(argList[0], &tipText, stringStorage, errMsg))
3262         return False;
3263 
3264     /* Read the anchor position (-1 for unanchored) */
3265     if (nArgs > 1) {
3266         if (!readIntArg(argList[1], &anchorPos, errMsg))
3267             return False;
3268     } else {
3269         anchorPos = -1;
3270     }
3271     if (anchorPos >= 0) anchored = True;
3272 
3273     /* Any further args are directives for relative positioning */
3274     for (i = 2; i < nArgs; ++i) {
3275         if (!readStringArg(argList[i], &txtArg, stringStorage, errMsg)){
3276             return False;
3277         }
3278         switch( txtArg[0] ) {
3279           case 'c':
3280             if (strcmp(txtArg, "center"))
3281                 goto bad_arg;
3282             hAlign = TIP_CENTER;
3283             break;
3284           case 'r':
3285             if (strcmp(txtArg, "right"))
3286                 goto bad_arg;
3287             hAlign = TIP_RIGHT;
3288             break;
3289           case 'a':
3290             if (strcmp(txtArg, "above"))
3291                 goto bad_arg;
3292             vAlign = TIP_ABOVE;
3293             break;
3294           case 's':
3295             if (strcmp(txtArg, "strict"))
3296                 goto bad_arg;
3297             alignMode = TIP_STRICT;
3298             break;
3299           case 't':
3300             if (!strcmp(txtArg, "tipText"))
3301                 mode = -1;
3302             else if (!strcmp(txtArg, "tipKey"))
3303                 mode = TIP;
3304             else if (!strcmp(txtArg, "tagKey"))
3305                 mode = TIP_FROM_TAG;
3306             else
3307                 goto bad_arg;
3308             break;
3309           default:
3310             goto bad_arg;
3311         }
3312     }
3313 
3314     result->tag = INT_TAG;
3315     if (mode < 0) lookup = False;
3316     /* Look up (maybe) a calltip and display it */
3317     result->val.n = ShowTipString( window, tipText, anchored, anchorPos, lookup,
3318                                  mode, hAlign, vAlign, alignMode );
3319 
3320     return True;
3321 
3322 bad_arg:
3323     /* This is how the (more informative) global var. version would work,
3324         assuming there was a global buffer called msg.  */
3325     /* sprintf(msg, "unrecognized argument to %%s: \"%s\"", txtArg);
3326     *errMsg = msg; */
3327     *errMsg = "unrecognized argument to %s";
3328     return False;
3329 }
3330 
3331 /*
3332 ** A subroutine to kill the current calltip
3333 */
killCalltipMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3334 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3335       DataValue *result, char **errMsg)
3336 {
3337     int calltipID = 0;
3338 
3339     if (nArgs > 1) {
3340         *errMsg = "%s subroutine called with too many arguments";
3341         return False;
3342     }
3343     if (nArgs > 0) {
3344         if (!readIntArg(argList[0], &calltipID, errMsg))
3345             return False;
3346     }
3347 
3348     KillCalltip( window, calltipID );
3349 
3350     result->tag = NO_TAG;
3351     return True;
3352 }
3353 
3354 /*
3355  * A subroutine to get the ID of the current calltip, or 0 if there is none.
3356  */
calltipIDMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3357 static int calltipIDMV(WindowInfo *window, DataValue *argList,
3358         int nArgs, DataValue *result, char **errMsg)
3359 {
3360     result->tag = INT_TAG;
3361     result->val.n = GetCalltipID(window, 0);
3362     return True;
3363 }
3364 
3365 /*
3366 **  filename_dialog([title[, mode[, defaultPath[, filter[, defaultName]]]]])
3367 **
3368 **  Presents a FileSelectionDialog to the user prompting for a new file.
3369 **
3370 **  Options are:
3371 **  title       - will be the title of the dialog, defaults to "Choose file".
3372 **  mode        - if set to "exist" (default), the "New File Name" TextField
3373 **                of the FSB will be unmanaged. If "new", the TextField will
3374 **                be managed.
3375 **  defaultPath - is the default path to use. Default (or "") will use the
3376 **                active document's directory.
3377 **  filter      - the file glob which determines which files to display.
3378 **                Is set to "*" if filter is "" and by default.
3379 **  defaultName - is the default filename that is filled in automatically.
3380 **
3381 ** Returns "" if the user cancelled the dialog, otherwise returns the path to
3382 ** the file that was selected
3383 **
3384 ** Note that defaultName doesn't work on all *tifs.  :-(
3385 */
filenameDialogMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3386 static int filenameDialogMS(WindowInfo* window, DataValue* argList, int nArgs,
3387         DataValue* result, char** errMsg)
3388 {
3389     char stringStorage[5][TYPE_INT_STR_SIZE(int)];
3390     char filename[MAXPATHLEN + 1];
3391     char* title = "Choose Filename";
3392     char* mode = "exist";
3393     char* defaultPath = "";
3394     char* filter = "";
3395     char* defaultName = "";
3396     char* orgDefaultPath;
3397     char* orgFilter;
3398     int gfnResult;
3399 
3400     /* Ignore the focused window passed as the function argument and put
3401        the dialog up over the window which is executing the macro */
3402     window = MacroRunWindow();
3403 
3404     /* Dialogs require macro to be suspended and interleaved with other macros.
3405        This subroutine can't be run if macro execution can't be interrupted */
3406     if (NULL == window->macroCmdData) {
3407         M_FAILURE("%s can't be called from non-suspendable context");
3408     }
3409 
3410     /*  Get the argument list.  */
3411     if (nArgs > 0 && !readStringArg(argList[0], &title, stringStorage[0],
3412             errMsg)) {
3413         return False;
3414     }
3415 
3416     if (nArgs > 1 && !readStringArg(argList[1], &mode, stringStorage[1],
3417             errMsg)) {
3418         return False;
3419     }
3420     if (0 != strcmp(mode, "exist") && 0 != strcmp(mode, "new")) {
3421         M_FAILURE("Invalid value for mode in %s");
3422     }
3423 
3424     if (nArgs > 2 && !readStringArg(argList[2], &defaultPath, stringStorage[2],
3425             errMsg)) {
3426         return False;
3427     }
3428 
3429     if (nArgs > 3 && !readStringArg(argList[3], &filter, stringStorage[3],
3430             errMsg)) {
3431         return False;
3432     }
3433 
3434     if (nArgs > 4 && !readStringArg(argList[4], &defaultName, stringStorage[4],
3435             errMsg)) {
3436         return False;
3437     }
3438 
3439     if (nArgs > 5) {
3440         M_FAILURE("%s called with too many arguments. Expects at most 5 arguments.");
3441     }
3442 
3443     /*  Set default directory (saving original for later)  */
3444     orgDefaultPath = GetFileDialogDefaultDirectory();
3445     if ('\0' != defaultPath[0]) {
3446         SetFileDialogDefaultDirectory(defaultPath);
3447     } else {
3448         SetFileDialogDefaultDirectory(window->path);
3449     }
3450 
3451     /*  Set filter (saving original for later)  */
3452     orgFilter = GetFileDialogDefaultPattern();
3453     if ('\0' != filter[0]) {
3454         SetFileDialogDefaultPattern(filter);
3455     }
3456 
3457     /*  Fork to one of the worker methods from util/getfiles.c.
3458         (This should obviously be refactored.)  */
3459     if (0 == strcmp(mode, "exist")) {
3460         gfnResult = GetExistingFilename(window->shell, title, filename);
3461     } else {
3462         gfnResult = GetNewFilename(window->shell, title, filename, defaultName);
3463     }   /*  Invalid values are weeded out above.  */
3464 
3465     /*  Reset original values and free temps  */
3466     SetFileDialogDefaultDirectory(orgDefaultPath);
3467     SetFileDialogDefaultPattern(orgFilter);
3468     NEditFree(orgDefaultPath);
3469     NEditFree(orgFilter);
3470 
3471     result->tag = STRING_TAG;
3472     if (GFN_OK == gfnResult) {
3473         /*  Got a string, copy it to the result  */
3474         if (!AllocNStringNCpy(&result->val.str, filename, MAXPATHLEN)) {
3475             M_FAILURE("failed to allocate return value: %s");
3476         }
3477     } else {
3478         /* User cancelled.  Return "" */
3479         result->val.str.rep = PERM_ALLOC_STR("");
3480         result->val.str.len = 0;
3481     }
3482 
3483     return True;
3484 }
3485 
3486 /* T Balinski */
listDialogMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3487 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3488       DataValue *result, char **errMsg)
3489 {
3490     macroCmdInfo *cmdData;
3491     char stringStorage[TYPE_INT_STR_SIZE(int)];
3492     char textStorage[TYPE_INT_STR_SIZE(int)];
3493     char btnStorage[TYPE_INT_STR_SIZE(int)];
3494     char *btnLabel;
3495     char *message, *text;
3496     Widget dialog, btn;
3497     int i, nBtns;
3498     XmString s1, s2;
3499     long nlines = 0;
3500     char *p, *old_p, **text_lines, *tmp;
3501     int tmp_len;
3502     int n, is_last;
3503     XmString *test_strings;
3504     int tabDist;
3505     Arg al[20];
3506     int ac;
3507 
3508 
3509     /* Ignore the focused window passed as the function argument and put
3510        the dialog up over the window which is executing the macro */
3511     window = MacroRunWindow();
3512     cmdData = window->macroCmdData;
3513 
3514     /* Dialogs require macro to be suspended and interleaved with other macros.
3515        This subroutine can't be run if macro execution can't be interrupted */
3516     if (!cmdData) {
3517       *errMsg = "%s can't be called from non-suspendable context";
3518        return False;
3519     }
3520 
3521     /* Read and check the arguments.  The first being the dialog message,
3522        and the rest being the button labels */
3523     if (nArgs < 2) {
3524       *errMsg = "%s subroutine called with no message, string or arguments";
3525       return False;
3526     }
3527 
3528     if (!readStringArg(argList[0], &message, stringStorage, errMsg))
3529       return False;
3530 
3531     if (!readStringArg(argList[1], &text, textStorage, errMsg))
3532       return False;
3533 
3534     if (!text || text[0] == '\0') {
3535       *errMsg = "%s subroutine called with empty list data";
3536       return False;
3537     }
3538 
3539     /* check that all button labels can be read */
3540     for (i=2; i<nArgs; i++)
3541       if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg))
3542           return False;
3543 
3544     /* pick up the first button */
3545     if (nArgs == 2) {
3546       btnLabel = "OK";
3547       nBtns = 1;
3548     }
3549     else {
3550       nBtns = nArgs - 2;
3551       argList += 2;
3552       readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
3553     }
3554 
3555     /* count the lines in the text - add one for unterminated last line */
3556     nlines = 1;
3557     for (p = text; *p; p++)
3558       if (*p == '\n')
3559           nlines++;
3560 
3561     /* now set up arrays of pointers to lines */
3562     /*   test_strings to hold the display strings (tab expanded) */
3563     /*   text_lines to hold the original text lines (without the '\n's) */
3564     test_strings = (XmString *) NEditMalloc(sizeof(XmString) * nlines);
3565     text_lines = (char **)NEditMalloc(sizeof(char *) * (nlines + 1));
3566     for (n = 0; n < nlines; n++) {
3567       test_strings[n] = (XmString)0;
3568       text_lines[n] = (char *)0;
3569     }
3570     text_lines[n] = (char *)0;        /* make sure this is a null-terminated table */
3571 
3572     /* pick up the tabDist value */
3573     tabDist = window->buffer->tabDist;
3574 
3575     /* load the table */
3576     n = 0;
3577     is_last = 0;
3578     p = old_p = text;
3579     tmp_len = 0;      /* current allocated size of temporary buffer tmp */
3580     tmp = (char*)NEditMalloc(1);  /* temporary buffer into which to expand tabs */
3581     do {
3582       is_last = (*p == '\0');
3583       if (*p == '\n' || is_last) {
3584           *p = '\0';
3585           if (strlen(old_p) > 0) {    /* only include non-empty lines */
3586               char *s, *t;
3587               int l;
3588 
3589               /* save the actual text line in text_lines[n] */
3590               text_lines[n] = (char *)NEditMalloc(strlen(old_p) + 1);
3591               strcpy(text_lines[n], old_p);
3592 
3593               /* work out the tabs expanded length */
3594               for (s = old_p, l = 0; *s; s++)
3595                   l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
3596 
3597               /* verify tmp is big enough then tab-expand old_p into tmp */
3598               if (l > tmp_len)
3599                   tmp = (char*)NEditRealloc(tmp, (tmp_len = l) + 1);
3600               for (s = old_p, t = tmp, l = 0; *s; s++) {
3601                   if (*s == '\t') {
3602                       for (i = tabDist - (l % tabDist); i--; l++)
3603                           *t++ = ' ';
3604                   }
3605                   else {
3606                     *t++ = *s;
3607                     l++;
3608                   }
3609               }
3610               *t = '\0';
3611               /* that's it: tmp is the tab-expanded version of old_p */
3612               test_strings[n] = MKSTRING(tmp);
3613               n++;
3614           }
3615           old_p = p + 1;
3616           if (!is_last)
3617               *p = '\n';              /* put back our newline */
3618       }
3619       p++;
3620     } while (!is_last);
3621 
3622     NEditFree(tmp);                /* don't need this anymore */
3623     nlines = n;
3624     if (nlines == 0) {
3625       test_strings[0] = MKSTRING("");
3626       nlines = 1;
3627     }
3628 
3629     /* Create the selection box dialog widget and its dialog shell parent */
3630     ac = 0;
3631     XtSetArg(al[ac], XmNtitle, " "); ac++;
3632     XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++;
3633     XtSetArg(al[ac], XmNlistItems, test_strings); ac++;
3634     XtSetArg(al[ac], XmNlistItemCount, nlines); ac++;
3635     XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++;
3636     XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
3637     dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac);
3638     if (2 == nArgs)
3639     {
3640         /*  Only set margin width for the default OK button  */
3641         XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3642                 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
3643                 NULL);
3644     }
3645 
3646     AddMotifCloseCallback(XtParent(dialog), listDialogCloseCB, window);
3647     XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window);
3648     XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3649           XmNuserData, (XtPointer)1, NULL);
3650     XmStringFree(s1);
3651     XmStringFree(s2);
3652     cmdData->dialog = dialog;
3653 
3654     /* forget lines stored in list */
3655     while (n--)
3656         XmStringFree(test_strings[n]);
3657     NEditFree(test_strings);
3658 
3659     /* modify the list */
3660     XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST),
3661                 XmNselectionPolicy, XmSINGLE_SELECT,
3662                 XmNuserData, (XtPointer)text_lines, NULL);
3663 
3664     /* Unmanage unneeded widgets */
3665     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
3666     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3667     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3668     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
3669     XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));
3670 
3671     /* Make callback for the unmanaged cancel button (which can
3672        still get executed via the esc key) activate close box action */
3673     XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3674 	    XmNactivateCallback, listDialogCloseCB, window);
3675 
3676     /* Add user specified buttons (1st is already done).  Selection box
3677        requires a place-holder widget to be added before buttons can be
3678        added, that's what the separator below is for */
3679     XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3680     for (i=1; i<nBtns; i++) {
3681       readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
3682       btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3683               XmNlabelString, s1=XmStringCreateSimple(btnLabel),
3684               XmNuserData, (XtPointer)(intptr_t)(i+1), NULL);
3685       XtAddCallback(btn, XmNactivateCallback, listDialogBtnCB, window);
3686       XmStringFree(s1);
3687     }
3688 
3689 #ifdef LESSTIF_VERSION
3690     /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3691        the escape key for closing the dialog. */
3692     XtAddEventHandler(dialog, KeyPressMask, False, listDialogEscCB,
3693             (XtPointer)window);
3694     XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3695 	    True, GrabModeAsync, GrabModeAsync);
3696 #endif /* LESSTIF_VERSION */
3697 
3698     /* Put up the dialog */
3699     ManageDialogCenteredOnPointer(dialog);
3700 
3701     /* Stop macro execution until the dialog is complete */
3702     PreemptMacro();
3703 
3704     /* Return placeholder result.  Value will be changed by button callback */
3705     result->tag = INT_TAG;
3706     result->val.n = 0;
3707     return True;
3708 }
3709 
listDialogBtnCB(Widget w,XtPointer clientData,XtPointer callData)3710 static void listDialogBtnCB(Widget w, XtPointer clientData,
3711       XtPointer callData)
3712 {
3713     WindowInfo *window = (WindowInfo *)clientData;
3714     macroCmdInfo *cmdData = window->macroCmdData;
3715     XtPointer userData;
3716     DataValue retVal;
3717     char *text;
3718     char **text_lines;
3719     int btnNum;
3720     int n_sel, *seltable, sel_index = 0;
3721     Widget theList;
3722     size_t length;
3723 
3724     /* shouldn't happen, but would crash if it did */
3725     if (cmdData == NULL)
3726       return;
3727 
3728     theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3729     /* Return the string selected in the selection list area */
3730     XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3731     if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) {
3732       n_sel = 0;
3733     }
3734     else {
3735       sel_index = seltable[0] - 1;
3736       NEditFree(seltable);
3737     }
3738 
3739     if (!n_sel) {
3740       text = NEditStrdup("");
3741       length = 0;
3742     }
3743     else {
3744       length = strlen((char *)text_lines[sel_index]);
3745       text = NEditStrdup(text_lines[sel_index]);
3746     }
3747 
3748     /* don't need text_lines anymore: free it */
3749     for (sel_index = 0; text_lines[sel_index]; sel_index++)
3750       NEditFree(text_lines[sel_index]);
3751     NEditFree(text_lines);
3752 
3753     retVal.tag = STRING_TAG;
3754     retVal.val.str.rep = text;
3755     retVal.val.str.len = length;
3756     ModifyReturnedValue(cmdData->context, retVal);
3757 
3758     /* Find the index of the button which was pressed (stored in the userData
3759        field of the button widget).  The 1st button, being a gadget, is not
3760        returned in w. */
3761     if (XtClass(w) == xmPushButtonWidgetClass) {
3762       XtVaGetValues(w, XmNuserData, &userData, NULL);
3763       btnNum = (int)(intptr_t)userData;
3764     } else
3765       btnNum = 1;
3766 
3767     /* Return the button number in the global variable $list_dialog_button */
3768     ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3769     ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = btnNum;
3770 
3771     /* Pop down the dialog */
3772     XtDestroyWidget(XtParent(cmdData->dialog));
3773     cmdData->dialog = NULL;
3774 
3775     /* Continue preempted macro execution */
3776     ResumeMacroExecution(window);
3777 }
3778 
listDialogCloseCB(Widget w,XtPointer clientData,XtPointer callData)3779 static void listDialogCloseCB(Widget w, XtPointer clientData,
3780       XtPointer callData)
3781 {
3782     WindowInfo *window = (WindowInfo *)clientData;
3783     macroCmdInfo *cmdData = window->macroCmdData;
3784     DataValue retVal;
3785     char **text_lines;
3786     int sel_index;
3787     Widget theList;
3788 
3789     /* shouldn't happen, but would crash if it did */
3790     if (cmdData == NULL)
3791       return;
3792 
3793     /* don't need text_lines anymore: retrieve it then free it */
3794     theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3795     XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3796     for (sel_index = 0; text_lines[sel_index]; sel_index++)
3797       NEditFree(text_lines[sel_index]);
3798     NEditFree(text_lines);
3799 
3800     /* Return an empty string */
3801     retVal.tag = STRING_TAG;
3802     retVal.val.str.rep = NEditStrdup("");
3803     retVal.val.str.len = 0;
3804     ModifyReturnedValue(cmdData->context, retVal);
3805 
3806     /* Return button number 0 in the global variable $list_dialog_button */
3807     ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3808     ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = 0;
3809 
3810     /* Pop down the dialog */
3811     XtDestroyWidget(XtParent(cmdData->dialog));
3812     cmdData->dialog = NULL;
3813 
3814     /* Continue preempted macro execution */
3815     ResumeMacroExecution(window);
3816 }
3817 /* T Balinski End */
3818 
3819 #ifdef LESSTIF_VERSION
listDialogEscCB(Widget w,XtPointer clientData,XEvent * event,Boolean * cont)3820 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3821     	Boolean *cont)
3822 {
3823     if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3824     	return;
3825     if (clientData != NULL) {
3826         listDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3827     }
3828     *cont = False;
3829 }
3830 #endif /* LESSTIF_VERSION */
3831 
3832 
stringCompareMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3833 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
3834     	DataValue *result, char **errMsg)
3835 {
3836     char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3837     char *leftStr, *rightStr, *argStr;
3838     int considerCase = True;
3839     int i;
3840     int compareResult;
3841 
3842     if (nArgs < 2) {
3843 	return(wrongNArgsErr(errMsg));
3844     }
3845     if (!readStringArg(argList[0], &leftStr, stringStorage[0], errMsg))
3846         return False;
3847     if (!readStringArg(argList[1], &rightStr, stringStorage[1], errMsg))
3848         return False;
3849     for (i = 2; i < nArgs; ++i) {
3850     	if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
3851     	    return False;
3852     	else if (!strcmp(argStr, "case"))
3853     	    considerCase = True;
3854     	else if (!strcmp(argStr, "nocase"))
3855     	    considerCase = False;
3856     	else {
3857     	    *errMsg = "Unrecognized argument to %s";
3858     	    return False;
3859     	}
3860     }
3861     if (considerCase) {
3862         compareResult = strcmp(leftStr, rightStr);
3863         compareResult = (compareResult > 0) ? 1 : ((compareResult < 0) ? -1 : 0);
3864     }
3865     else {
3866         compareResult = strCaseCmp(leftStr, rightStr);
3867     }
3868     result->tag = INT_TAG;
3869     result->val.n = compareResult;
3870     return True;
3871 }
3872 
3873 /*
3874 ** This function is intended to split strings into an array of substrings
3875 ** Importatnt note: It should always return at least one entry with key 0
3876 ** split("", ",") result[0] = ""
3877 ** split("1,2", ",") result[0] = "1" result[1] = "2"
3878 ** split("1,2,", ",") result[0] = "1" result[1] = "2" result[2] = ""
3879 **
3880 ** This behavior is specifically important when used to break up
3881 ** array sub-scripts
3882 */
3883 
splitMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)3884 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
3885     	DataValue *result, char **errMsg)
3886 {
3887     char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3888     char *sourceStr, *splitStr, *typeSplitStr;
3889     int searchType, beginPos, foundStart, foundEnd, strLength, lastEnd;
3890     int found, elementEnd, indexNum;
3891     char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
3892     DataValue element;
3893     int elementLen;
3894 
3895     if (nArgs < 2) {
3896         return(wrongNArgsErr(errMsg));
3897     }
3898     if (!readStringArg(argList[0], &sourceStr, stringStorage[0], errMsg)) {
3899         *errMsg = "first argument must be a string: %s";
3900         return(False);
3901     }
3902     if (!readStringArg(argList[1], &splitStr, stringStorage[1], errMsg)) {
3903         splitStr = NULL;
3904     }
3905     else {
3906         if (splitStr[0] == 0) {
3907             splitStr = NULL;
3908         }
3909     }
3910     if (splitStr == NULL) {
3911         *errMsg = "second argument must be a non-empty string: %s";
3912         return(False);
3913     }
3914     if (nArgs > 2 && readStringArg(argList[2], &typeSplitStr, stringStorage[2], errMsg)) {
3915       	if (!StringToSearchType(typeSplitStr, &searchType)) {
3916             *errMsg = "unrecognized argument to %s";
3917             return(False);
3918         }
3919     }
3920     else {
3921     	searchType = SEARCH_LITERAL;
3922     }
3923 
3924     result->tag = ARRAY_TAG;
3925     result->val.arrayPtr = ArrayNew();
3926 
3927     beginPos = 0;
3928     lastEnd = 0;
3929     indexNum = 0;
3930     strLength = strlen(sourceStr);
3931     found = 1;
3932     while (found && beginPos < strLength) {
3933         sprintf(indexStr, "%d", indexNum);
3934         allocIndexStr = AllocString(strlen(indexStr) + 1);
3935         if (!allocIndexStr) {
3936             *errMsg = "array element failed to allocate key: %s";
3937             return(False);
3938         }
3939         strcpy(allocIndexStr, indexStr);
3940         found = SearchString(sourceStr, splitStr, SEARCH_FORWARD, searchType,
3941             False, beginPos, &foundStart, &foundEnd,
3942 	        NULL, NULL, GetWindowDelimiters(window));
3943         elementEnd = found ? foundStart : strLength;
3944         elementLen = elementEnd - lastEnd;
3945         element.tag = STRING_TAG;
3946         if (!AllocNStringNCpy(&element.val.str, &sourceStr[lastEnd], elementLen)) {
3947             *errMsg = "failed to allocate element value: %s";
3948             return(False);
3949         }
3950 
3951         if (!ArrayInsert(result, allocIndexStr, &element)) {
3952             M_ARRAY_INSERT_FAILURE();
3953         }
3954 
3955         if (found) {
3956             if (foundStart == foundEnd) {
3957                 beginPos = foundEnd + 1; /* Avoid endless loop for 0-width match */
3958             } else {
3959                 beginPos = foundEnd;
3960             }
3961         } else {
3962             beginPos = strLength; /* Break the loop */
3963         }
3964         lastEnd = foundEnd;
3965         ++indexNum;
3966     }
3967     if (found) {
3968         sprintf(indexStr, "%d", indexNum);
3969         allocIndexStr = AllocString(strlen(indexStr) + 1);
3970         if (!allocIndexStr) {
3971             *errMsg = "array element failed to allocate key: %s";
3972             return(False);
3973         }
3974         strcpy(allocIndexStr, indexStr);
3975         element.tag = STRING_TAG;
3976         if (lastEnd == strLength) {
3977             /* The pattern mathed the end of the string. Add an empty chunk. */
3978             element.val.str.rep = PERM_ALLOC_STR("");
3979             element.val.str.len = 0;
3980 
3981             if (!ArrayInsert(result, allocIndexStr, &element)) {
3982                 M_ARRAY_INSERT_FAILURE();
3983             }
3984         } else {
3985             /* We skipped the last character to prevent an endless loop.
3986                Add it to the list. */
3987             elementLen = strLength - lastEnd;
3988             if (!AllocNStringNCpy(&element.val.str, &sourceStr[lastEnd], elementLen)) {
3989                 *errMsg = "failed to allocate element value: %s";
3990                 return(False);
3991             }
3992 
3993             if (!ArrayInsert(result, allocIndexStr, &element)) {
3994                 M_ARRAY_INSERT_FAILURE();
3995             }
3996 
3997             /* If the pattern can match zero-length strings, we may have to
3998                add a final empty chunk.
3999                For instance:  split("abc\n", "$", "regex")
4000                  -> matches before \n and at end of string
4001                  -> expected output: "abc", "\n", ""
4002                The '\n' gets added in the lines above, but we still have to
4003                verify whether the pattern also matches the end of the string,
4004                and add an empty chunk in case it does. */
4005             found = SearchString(sourceStr, splitStr, SEARCH_FORWARD,
4006                 searchType, False, strLength, &foundStart, &foundEnd,
4007                 NULL, NULL, GetWindowDelimiters(window));
4008             if (found) {
4009                 ++indexNum;
4010                 sprintf(indexStr, "%d", indexNum);
4011                 allocIndexStr = AllocString(strlen(indexStr) + 1);
4012                 if (!allocIndexStr) {
4013                     *errMsg = "array element failed to allocate key: %s";
4014                     return(False);
4015                 }
4016                 strcpy(allocIndexStr, indexStr);
4017                 element.tag = STRING_TAG;
4018                 element.val.str.rep = PERM_ALLOC_STR("");
4019                 element.val.str.len = 0;
4020 
4021                 if (!ArrayInsert(result, allocIndexStr, &element)) {
4022                     M_ARRAY_INSERT_FAILURE();
4023                 }
4024             }
4025         }
4026     }
4027     return(True);
4028 }
4029 
4030 /*
4031 ** Set the backlighting string resource for the current window. If no parameter
4032 ** is passed or the value "default" is passed, it attempts to set the preference
4033 ** value of the resource. If the empty string is passed, the backlighting string
4034 ** will be cleared, turning off backlighting.
4035 */
4036 /* DISABLED for 5.4
4037 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
4038       int nArgs, DataValue *result, char **errMsg)
4039 {
4040     char *backlightString;
4041 
4042     if (nArgs == 0) {
4043       backlightString = GetPrefBacklightCharTypes();
4044     }
4045     else if (nArgs == 1) {
4046       if (argList[0].tag != STRING_TAG) {
4047           *errMsg = "%s not called with a string parameter";
4048           return False;
4049       }
4050       backlightString = argList[0].val.str.rep;
4051     }
4052     else
4053       return wrongNArgsErr(errMsg);
4054 
4055     if (strcmp(backlightString, "default") == 0)
4056       backlightString = GetPrefBacklightCharTypes();
4057     if (backlightString && *backlightString == '\0')  / * empty string param * /
4058       backlightString = NULL;                 / * turns of backlighting * /
4059 
4060     SetBacklightChars(window, backlightString);
4061     return True;
4062 } */
4063 
cursorMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4064 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
4065     	DataValue *result, char **errMsg)
4066 {
4067     result->tag = INT_TAG;
4068     result->val.n = TextGetCursorPos(window->lastFocus);
4069     return True;
4070 }
4071 
lineMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4072 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
4073         DataValue *result, char **errMsg)
4074 {
4075     int line, cursorPos, colNum;
4076 
4077     result->tag = INT_TAG;
4078     cursorPos = TextGetCursorPos(window->lastFocus);
4079     if (!TextPosToLineAndCol(window->lastFocus, cursorPos, &line, &colNum))
4080     	line = BufCountLines(window->buffer, 0, cursorPos) + 1;
4081     result->val.n = line;
4082     return True;
4083 }
4084 
columnMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4085 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
4086         DataValue *result, char **errMsg)
4087 {
4088     textBuffer *buf = window->buffer;
4089     int cursorPos;
4090 
4091     result->tag = INT_TAG;
4092     cursorPos = TextGetCursorPos(window->lastFocus);
4093     result->val.n = BufCountDispChars(buf, BufStartOfLine(buf, cursorPos),
4094 	    cursorPos);
4095     return True;
4096 }
4097 
fileNameMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4098 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4099     	DataValue *result, char **errMsg)
4100 {
4101     result->tag = STRING_TAG;
4102     AllocNStringCpy(&result->val.str, window->filename);
4103     return True;
4104 }
4105 
filePathMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4106 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
4107     	DataValue *result, char **errMsg)
4108 {
4109     result->tag = STRING_TAG;
4110     AllocNStringCpy(&result->val.str, window->path);
4111     return True;
4112 }
4113 
lengthMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4114 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
4115     	DataValue *result, char **errMsg)
4116 {
4117     result->tag = INT_TAG;
4118     result->val.n = window->buffer->length;
4119     return True;
4120 }
4121 
selectionStartMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4122 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
4123     	DataValue *result, char **errMsg)
4124 {
4125     result->tag = INT_TAG;
4126     result->val.n = window->buffer->primary.selected ?
4127     	    window->buffer->primary.start : -1;
4128     return True;
4129 }
4130 
selectionEndMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4131 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
4132     	DataValue *result, char **errMsg)
4133 {
4134     result->tag = INT_TAG;
4135     result->val.n = window->buffer->primary.selected ?
4136     	    window->buffer->primary.end : -1;
4137     return True;
4138 }
4139 
selectionLeftMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4140 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
4141     	DataValue *result, char **errMsg)
4142 {
4143     selection *sel = &window->buffer->primary;
4144 
4145     result->tag = INT_TAG;
4146     result->val.n = sel->selected && sel->rectangular ? sel->rectStart : -1;
4147     return True;
4148 }
4149 
selectionRightMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4150 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
4151     	DataValue *result, char **errMsg)
4152 {
4153     selection *sel = &window->buffer->primary;
4154 
4155     result->tag = INT_TAG;
4156     result->val.n = sel->selected && sel->rectangular ? sel->rectEnd : -1;
4157     return True;
4158 }
4159 
wrapMarginMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4160 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
4161     	DataValue *result, char **errMsg)
4162 {
4163     int margin, nCols;
4164 
4165     XtVaGetValues(window->textArea, textNcolumns, &nCols,
4166     	    textNwrapMargin, &margin, NULL);
4167     result->tag = INT_TAG;
4168     result->val.n = margin == 0 ? nCols : margin;
4169     return True;
4170 }
4171 
statisticsLineMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4172 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4173     DataValue *result, char **errMsg)
4174 {
4175     result->tag = INT_TAG;
4176     result->val.n = window->showStats ? 1 : 0;
4177     return True;
4178 }
4179 
incSearchLineMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4180 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4181     DataValue *result, char **errMsg)
4182 {
4183     result->tag = INT_TAG;
4184     result->val.n = window->showISearchLine ? 1 : 0;
4185     return True;
4186 }
4187 
showLineNumbersMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4188 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
4189     DataValue *result, char **errMsg)
4190 {
4191     result->tag = INT_TAG;
4192     result->val.n = window->showLineNumbers ? 1 : 0;
4193     return True;
4194 }
4195 
autoIndentMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4196 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
4197     DataValue *result, char **errMsg)
4198 {
4199     char *res = NULL;
4200 
4201     switch (window->indentStyle) {
4202         case NO_AUTO_INDENT:
4203             res = PERM_ALLOC_STR("off");
4204             break;
4205         case AUTO_INDENT:
4206             res = PERM_ALLOC_STR("on");
4207             break;
4208         case SMART_INDENT:
4209             res = PERM_ALLOC_STR("smart");
4210             break;
4211         default:
4212             *errMsg = "Invalid indent style value encountered in %s";
4213             return False;
4214             break;
4215     }
4216     result->tag = STRING_TAG;
4217     result->val.str.rep = res;
4218     result->val.str.len = strlen(res);
4219     return True;
4220 }
4221 
wrapTextMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4222 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
4223     DataValue *result, char **errMsg)
4224 {
4225     char *res = NULL;
4226 
4227     switch (window->wrapMode) {
4228         case NO_WRAP:
4229             res = PERM_ALLOC_STR("none");
4230             break;
4231         case NEWLINE_WRAP:
4232             res = PERM_ALLOC_STR("auto");
4233             break;
4234         case CONTINUOUS_WRAP:
4235             res = PERM_ALLOC_STR("continuous");
4236             break;
4237         default:
4238             *errMsg = "Invalid wrap style value encountered in %s";
4239             return False;
4240             break;
4241     }
4242     result->tag = STRING_TAG;
4243     result->val.str.rep = res;
4244     result->val.str.len = strlen(res);
4245     return True;
4246 }
4247 
highlightSyntaxMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4248 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
4249     DataValue *result, char **errMsg)
4250 {
4251     result->tag = INT_TAG;
4252     result->val.n = window->highlightSyntax ? 1 : 0;
4253     return True;
4254 }
4255 
makeBackupCopyMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4256 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
4257     DataValue *result, char **errMsg)
4258 {
4259     result->tag = INT_TAG;
4260     result->val.n = window->saveOldVersion ? 1 : 0;
4261     return True;
4262 }
4263 
incBackupMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4264 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
4265     DataValue *result, char **errMsg)
4266 {
4267     result->tag = INT_TAG;
4268     result->val.n = window->autoSave ? 1 : 0;
4269     return True;
4270 }
4271 
showMatchingMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4272 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
4273     DataValue *result, char **errMsg)
4274 {
4275     char *res = NULL;
4276 
4277     switch (window->showMatchingStyle) {
4278         case NO_FLASH:
4279             res = PERM_ALLOC_STR(NO_FLASH_STRING);
4280             break;
4281         case FLASH_DELIMIT:
4282             res = PERM_ALLOC_STR(FLASH_DELIMIT_STRING);
4283             break;
4284         case FLASH_RANGE:
4285             res = PERM_ALLOC_STR(FLASH_RANGE_STRING);
4286             break;
4287         default:
4288             *errMsg = "Invalid match flashing style value encountered in %s";
4289             return False;
4290             break;
4291     }
4292     result->tag = STRING_TAG;
4293     result->val.str.rep = res;
4294     result->val.str.len = strlen(res);
4295     return True;
4296 }
4297 
matchSyntaxBasedMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4298 static int matchSyntaxBasedMV(WindowInfo *window, DataValue *argList, int nArgs,
4299     DataValue *result, char **errMsg)
4300 {
4301     result->tag = INT_TAG;
4302     result->val.n = window->matchSyntaxBased ? 1 : 0;
4303     return True;
4304 }
4305 
4306 
4307 
overTypeModeMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4308 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4309     DataValue *result, char **errMsg)
4310 {
4311     result->tag = INT_TAG;
4312     result->val.n = window->overstrike ? 1 : 0;
4313     return True;
4314 }
4315 
readOnlyMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4316 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
4317     DataValue *result, char **errMsg)
4318 {
4319     result->tag = INT_TAG;
4320     result->val.n = (IS_ANY_LOCKED(window->lockReasons)) ? 1 : 0;
4321     return True;
4322 }
4323 
lockedMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4324 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
4325     DataValue *result, char **errMsg)
4326 {
4327     result->tag = INT_TAG;
4328     result->val.n = (IS_USER_LOCKED(window->lockReasons)) ? 1 : 0;
4329     return True;
4330 }
4331 
fileFormatMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4332 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
4333     DataValue *result, char **errMsg)
4334 {
4335     char *res = NULL;
4336 
4337     switch (window->fileFormat) {
4338         case UNIX_FILE_FORMAT:
4339             res = PERM_ALLOC_STR("unix");
4340             break;
4341         case DOS_FILE_FORMAT:
4342             res = PERM_ALLOC_STR("dos");
4343             break;
4344         case MAC_FILE_FORMAT:
4345             res = PERM_ALLOC_STR("macintosh");
4346             break;
4347         default:
4348             *errMsg = "Invalid linefeed style value encountered in %s";
4349             return False;
4350     }
4351     result->tag = STRING_TAG;
4352     result->val.str.rep = res;
4353     result->val.str.len = strlen(res);
4354     return True;
4355 }
4356 
fontNameMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4357 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4358     DataValue *result, char **errMsg)
4359 {
4360     result->tag = STRING_TAG;
4361     AllocNStringCpy(&result->val.str, window->fontName);
4362     return True;
4363 }
4364 
fontNameItalicMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4365 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4366     DataValue *result, char **errMsg)
4367 {
4368     result->tag = STRING_TAG;
4369     AllocNStringCpy(&result->val.str, window->italicFontName);
4370     return True;
4371 }
4372 
fontNameBoldMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4373 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
4374     DataValue *result, char **errMsg)
4375 {
4376     result->tag = STRING_TAG;
4377     AllocNStringCpy(&result->val.str, window->boldFontName);
4378     return True;
4379 }
4380 
fontNameBoldItalicMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4381 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4382     DataValue *result, char **errMsg)
4383 {
4384     result->tag = STRING_TAG;
4385     AllocNStringCpy(&result->val.str, window->boldItalicFontName);
4386     return True;
4387 }
4388 
subscriptSepMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4389 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
4390     DataValue *result, char **errMsg)
4391 {
4392     result->tag = STRING_TAG;
4393     result->val.str.rep = PERM_ALLOC_STR(ARRAY_DIM_SEP);
4394     result->val.str.len = strlen(result->val.str.rep);
4395     return True;
4396 }
4397 
minFontWidthMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4398 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4399     DataValue *result, char **errMsg)
4400 {
4401     result->tag = INT_TAG;
4402     result->val.n = TextGetMinFontWidth(window->textArea, window->highlightSyntax);
4403     return True;
4404 }
4405 
maxFontWidthMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4406 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4407     DataValue *result, char **errMsg)
4408 {
4409     result->tag = INT_TAG;
4410     result->val.n = TextGetMaxFontWidth(window->textArea, window->highlightSyntax);
4411     return True;
4412 }
4413 
topLineMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4414 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4415     DataValue *result, char **errMsg)
4416 {
4417     result->tag = INT_TAG;
4418     result->val.n = TextFirstVisibleLine(window->lastFocus);
4419     return True;
4420 }
4421 
numDisplayLinesMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4422 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
4423     DataValue *result, char **errMsg)
4424 {
4425     result->tag = INT_TAG;
4426     result->val.n = TextNumVisibleLines(window->lastFocus);
4427     return True;
4428 }
4429 
displayWidthMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4430 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4431     DataValue *result, char **errMsg)
4432 {
4433     result->tag = INT_TAG;
4434     result->val.n = TextVisibleWidth(window->lastFocus);
4435     return True;
4436 }
4437 
activePaneMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4438 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
4439     DataValue *result, char **errMsg)
4440 {
4441     result->tag = INT_TAG;
4442     result->val.n = WidgetToPaneIndex(window, window->lastFocus) + 1;
4443     return True;
4444 }
4445 
nPanesMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4446 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
4447     DataValue *result, char **errMsg)
4448 {
4449     result->tag = INT_TAG;
4450     result->val.n = window->nPanes + 1;
4451     return True;
4452 }
4453 
emptyArrayMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4454 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
4455     DataValue *result, char **errMsg)
4456 {
4457     result->tag = ARRAY_TAG;
4458     result->val.arrayPtr = NULL;
4459     return True;
4460 }
4461 
serverNameMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4462 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4463     DataValue *result, char **errMsg)
4464 {
4465     result->tag = STRING_TAG;
4466     AllocNStringCpy(&result->val.str, GetPrefServerName());
4467     return True;
4468 }
4469 
tabDistMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4470 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4471     	DataValue *result, char **errMsg)
4472 {
4473     result->tag = INT_TAG;
4474     result->val.n = window->buffer->tabDist;
4475     return True;
4476 }
4477 
emTabDistMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4478 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4479     	DataValue *result, char **errMsg)
4480 {
4481     int dist;
4482 
4483     XtVaGetValues(window->textArea, textNemulateTabs, &dist, NULL);
4484     result->tag = INT_TAG;
4485     result->val.n = dist;
4486     return True;
4487 }
4488 
useTabsMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4489 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
4490     	DataValue *result, char **errMsg)
4491 {
4492     result->tag = INT_TAG;
4493     result->val.n = window->buffer->useTabs;
4494     return True;
4495 }
4496 
modifiedMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4497 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
4498     	DataValue *result, char **errMsg)
4499 {
4500     result->tag = INT_TAG;
4501     result->val.n = window->fileChanged;
4502     return True;
4503 }
4504 
languageModeMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4505 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4506     	DataValue *result, char **errMsg)
4507 {
4508     char *lmName = LanguageModeName(window->languageMode);
4509 
4510     if (lmName == NULL)
4511     	lmName = "Plain";
4512     result->tag = STRING_TAG;
4513     AllocNStringCpy(&result->val.str, lmName);
4514     return True;
4515 }
4516 
4517 /* DISABLED for 5.4
4518 static int backlightStringMV(WindowInfo *window, DataValue *argList,
4519       int nArgs, DataValue *result, char **errMsg)
4520 {
4521     char *backlightString = window->backlightCharTypes;
4522 
4523     result->tag = STRING_TAG;
4524     if (!backlightString || !window->backlightChars)
4525       backlightString = "";
4526     AllocNStringCpy(&result->val.str, backlightString);
4527     return True;
4528 } */
4529 
4530 /* -------------------------------------------------------------------------- */
4531 
4532 /*
4533 ** Range set macro variables and functions
4534 */
rangesetListMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4535 static int rangesetListMV(WindowInfo *window, DataValue *argList, int nArgs,
4536       DataValue *result, char **errMsg)
4537 {
4538     RangesetTable *rangesetTable = window->buffer->rangesetTable;
4539     unsigned char *rangesetList;
4540     char *allocIndexStr;
4541     char indexStr[TYPE_INT_STR_SIZE(int)] ;
4542     int nRangesets, i;
4543     DataValue element;
4544 
4545     result->tag = ARRAY_TAG;
4546     result->val.arrayPtr = ArrayNew();
4547 
4548     if (rangesetTable == NULL) {
4549         return True;
4550     }
4551 
4552     rangesetList = RangesetGetList(rangesetTable);
4553     nRangesets = strlen((char*)rangesetList);
4554     for(i = 0; i < nRangesets; i++) {
4555         element.tag = INT_TAG;
4556         element.val.n = rangesetList[i];
4557 
4558         sprintf(indexStr, "%d", nRangesets - i - 1);
4559         allocIndexStr = AllocString(strlen(indexStr) + 1);
4560         if (allocIndexStr == NULL)
4561             M_FAILURE("Failed to allocate array key in %s");
4562         strcpy(allocIndexStr, indexStr);
4563 
4564         if (!ArrayInsert(result, allocIndexStr, &element))
4565             M_FAILURE("Failed to insert array element in %s");
4566     }
4567 
4568     return True;
4569 }
4570 
4571 /*
4572 **  Returns the version number of the current macro language implementation.
4573 **  For releases, this is the same number as NEdit's major.minor version
4574 **  number to keep things simple. For developer versions this could really
4575 **  be anything.
4576 **
4577 **  Note that the current way to build $VERSION builds the same value for
4578 **  different point revisions. This is done because the macro interface
4579 **  does not change for the same version.
4580 */
versionMV(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4581 static int versionMV(WindowInfo* window, DataValue* argList, int nArgs,
4582         DataValue* result, char** errMsg)
4583 {
4584     static unsigned version = NEDIT_VERSION * 1000 + NEDIT_REVISION;
4585 
4586     result->tag = INT_TAG;
4587     result->val.n = version;
4588     return True;
4589 }
4590 
4591 /*
4592 ** Built-in macro subroutine to create a new rangeset or rangesets.
4593 ** If called with one argument: $1 is the number of rangesets required and
4594 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4595 ** (or an empty array if the requested number of rangesets are not available).
4596 ** If called with no arguments, returns a single rangeset label (not an array),
4597 ** or an empty string if there are no rangesets available.
4598 */
rangesetCreateMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4599 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
4600       DataValue *result, char **errMsg)
4601 {
4602     int label;
4603     int i, nRangesetsRequired;
4604     DataValue element;
4605     char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
4606 
4607     RangesetTable *rangesetTable = window->buffer->rangesetTable;
4608 
4609     if (nArgs > 1)
4610         return wrongNArgsErr(errMsg);
4611 
4612     if (rangesetTable == NULL) {
4613         window->buffer->rangesetTable = rangesetTable =
4614                 RangesetTableAlloc(window->buffer);
4615     }
4616 
4617     if (nArgs == 0) {
4618         label = RangesetCreate(rangesetTable);
4619 
4620         result->tag = INT_TAG;
4621         result->val.n = label;
4622         return True;
4623     }
4624     else {
4625         if (!readIntArg(argList[0], &nRangesetsRequired, errMsg))
4626             return False;
4627 
4628         result->tag = ARRAY_TAG;
4629         result->val.arrayPtr = ArrayNew();
4630 
4631         if (nRangesetsRequired > nRangesetsAvailable(rangesetTable))
4632             return True;
4633 
4634         for (i = 0; i < nRangesetsRequired; i++) {
4635             element.tag = INT_TAG;
4636             element.val.n = RangesetCreate(rangesetTable);
4637 
4638             sprintf(indexStr, "%d", i);
4639             allocIndexStr = AllocString(strlen(indexStr) + 1);
4640             if (!allocIndexStr) {
4641                 *errMsg = "Array element failed to allocate key: %s";
4642                 return(False);
4643             }
4644             strcpy(allocIndexStr, indexStr);
4645             ArrayInsert(result, allocIndexStr, &element);
4646         }
4647 
4648         return True;
4649     }
4650 }
4651 
4652 /*
4653 ** Built-in macro subroutine for forgetting a range set.
4654 */
rangesetDestroyMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4655 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
4656       DataValue *result, char **errMsg)
4657 {
4658     RangesetTable *rangesetTable = window->buffer->rangesetTable;
4659     DataValue *array;
4660     DataValue element;
4661     char keyString[TYPE_INT_STR_SIZE(int)];
4662     int deleteLabels[N_RANGESETS];
4663     int i, arraySize;
4664     int label = 0;
4665 
4666     if (nArgs != 1) {
4667         return wrongNArgsErr(errMsg);
4668     }
4669 
4670     if (argList[0].tag == ARRAY_TAG) {
4671         array = &argList[0];
4672         arraySize = ArraySize(array);
4673 
4674         if (arraySize > N_RANGESETS) {
4675             M_FAILURE("Too many elements in array in %s");
4676         }
4677 
4678         for (i = 0; i < arraySize; i++) {
4679             sprintf(keyString, "%d", i);
4680 
4681             if (!ArrayGet(array, keyString, &element)) {
4682                 M_FAILURE("Invalid key in array in %s");
4683             }
4684 
4685             if (!readIntArg(element, &label, errMsg)
4686                     || !RangesetLabelOK(label)) {
4687                 M_FAILURE("Invalid rangeset label in array in %s");
4688             }
4689 
4690             deleteLabels[i] = label;
4691         }
4692 
4693         for (i = 0; i < arraySize; i++) {
4694             RangesetForget(rangesetTable, deleteLabels[i]);
4695         }
4696     } else {
4697         if (!readIntArg(argList[0], &label, errMsg)
4698                 || !RangesetLabelOK(label)) {
4699             M_FAILURE("Invalid rangeset label in %s");
4700         }
4701 
4702         if(rangesetTable != NULL) {
4703             RangesetForget(rangesetTable, label);
4704         }
4705     }
4706 
4707     /* set up result */
4708     result->tag = NO_TAG;
4709     return True;
4710 }
4711 
4712 
4713 /*
4714 ** Built-in macro subroutine for getting all range sets with a specfic name.
4715 ** Arguments are $1: range set name.
4716 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4717 */
rangesetGetByNameMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4718 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
4719       DataValue *result, char **errMsg)
4720 {
4721     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4722     Rangeset *rangeset;
4723     int label;
4724     char *name, *rangeset_name;
4725     RangesetTable *rangesetTable = window->buffer->rangesetTable;
4726     unsigned char *rangesetList;
4727     char *allocIndexStr;
4728     char indexStr[TYPE_INT_STR_SIZE(int)] ;
4729     int nRangesets, i, insertIndex = 0;
4730     DataValue element;
4731 
4732     if (nArgs != 1) {
4733         return wrongNArgsErr(errMsg);
4734     }
4735 
4736     if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
4737         M_FAILURE("First parameter is not a name string in %s");
4738     }
4739 
4740     result->tag = ARRAY_TAG;
4741     result->val.arrayPtr = ArrayNew();
4742 
4743     if (rangesetTable == NULL) {
4744         return True;
4745     }
4746 
4747     rangesetList = RangesetGetList(rangesetTable);
4748     nRangesets = strlen((char *)rangesetList);
4749     for (i = 0; i < nRangesets; ++i) {
4750         label = rangesetList[i];
4751         rangeset = RangesetFetch(rangesetTable, label);
4752         if (rangeset) {
4753             rangeset_name = RangesetGetName(rangeset);
4754             if (strcmp(name, rangeset_name ? rangeset_name : "") == 0) {
4755                 element.tag = INT_TAG;
4756                 element.val.n = label;
4757 
4758                 sprintf(indexStr, "%d", insertIndex);
4759                 allocIndexStr = AllocString(strlen(indexStr) + 1);
4760                 if (allocIndexStr == NULL)
4761                     M_FAILURE("Failed to allocate array key in %s");
4762 
4763                 strcpy(allocIndexStr, indexStr);
4764 
4765                 if (!ArrayInsert(result, allocIndexStr, &element))
4766                     M_FAILURE("Failed to insert array element in %s");
4767 
4768                 ++insertIndex;
4769             }
4770         }
4771     }
4772 
4773     return True;
4774 }
4775 
4776 /*
4777 ** Built-in macro subroutine for adding to a range set. Arguments are $1: range
4778 ** set label (one integer), then either (a) $2: source range set label,
4779 ** (b) $2: int start-range, $3: int end-range, (c) nothing (use selection
4780 ** if any to specify range to add - must not be rectangular). Returns the
4781 ** index of the newly added range (cases b and c), or 0 (case a).
4782 */
rangesetAddMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4783 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
4784       DataValue *result, char **errMsg)
4785 {
4786     textBuffer *buffer = window->buffer;
4787     RangesetTable *rangesetTable = buffer->rangesetTable;
4788     Rangeset *targetRangeset, *sourceRangeset;
4789     int start, end, isRect, rectStart, rectEnd, maxpos, index;
4790     int label = 0;
4791 
4792     if (nArgs < 1 || nArgs > 3)
4793         return wrongNArgsErr(errMsg);
4794 
4795     if (!readIntArg(argList[0], &label, errMsg)
4796             || !RangesetLabelOK(label)) {
4797         M_FAILURE("First parameter is an invalid rangeset label in %s");
4798     }
4799 
4800     if (rangesetTable == NULL) {
4801         M_FAILURE("Rangeset does not exist in %s");
4802     }
4803 
4804     targetRangeset = RangesetFetch(rangesetTable, label);
4805 
4806     if (targetRangeset == NULL) {
4807         M_FAILURE("Rangeset does not exist in %s");
4808     }
4809 
4810     start = end = -1;
4811 
4812     if (nArgs == 1) {
4813         /* pick up current selection in this window */
4814         if (!BufGetSelectionPos(buffer, &start, &end,
4815               &isRect, &rectStart, &rectEnd) || isRect) {
4816             M_FAILURE("Selection missing or rectangular in call to %s");
4817         }
4818         if (!RangesetAddBetween(targetRangeset, start, end)) {
4819             M_FAILURE("Failure to add selection in %s");
4820         }
4821     }
4822 
4823     if (nArgs == 2) {
4824         /* add ranges taken from a second set */
4825         if (!readIntArg(argList[1], &label, errMsg)
4826                 || !RangesetLabelOK(label)) {
4827             M_FAILURE("Second parameter is an invalid rangeset label in %s");
4828         }
4829 
4830         sourceRangeset = RangesetFetch(rangesetTable, label);
4831         if (sourceRangeset == NULL) {
4832             M_FAILURE("Second rangeset does not exist in %s");
4833         }
4834 
4835         RangesetAdd(targetRangeset, sourceRangeset);
4836     }
4837 
4838     if (nArgs == 3) {
4839         /* add a range bounded by the start and end positions in $2, $3 */
4840         if (!readIntArg(argList[1], &start, errMsg)) {
4841             return False;
4842         }
4843         if (!readIntArg(argList[2], &end, errMsg)) {
4844             return False;
4845         }
4846 
4847         /* make sure range is in order and fits buffer size */
4848         maxpos = buffer->length;
4849         if (start < 0) start = 0;
4850         if (start > maxpos) start = maxpos;
4851         if (end < 0) end = 0;
4852         if (end > maxpos) end = maxpos;
4853         if (start > end) {int temp = start; start = end; end = temp;}
4854 
4855         if ((start != end) && !RangesetAddBetween(targetRangeset, start, end)) {
4856             M_FAILURE("Failed to add range in %s");
4857         }
4858     }
4859 
4860     /* (to) which range did we just add? */
4861     if (nArgs != 2 && start >= 0) {
4862         start = (start + end) / 2;      /* "middle" of added range */
4863         index = 1 + RangesetFindRangeOfPos(targetRangeset, start, False);
4864     }
4865     else {
4866         index = 0;
4867     }
4868 
4869     /* set up result */
4870     result->tag = INT_TAG;
4871     result->val.n = index;
4872     return True;
4873 }
4874 
4875 
4876 /*
4877 ** Built-in macro subroutine for removing from a range set. Almost identical to
4878 ** rangesetAddMS() - only changes are from RangesetAdd()/RangesetAddBetween()
4879 ** to RangesetSubtract()/RangesetSubtractBetween(), the handling of an
4880 ** undefined destination range, and that it returns no value.
4881 */
rangesetSubtractMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4882 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
4883       DataValue *result, char **errMsg)
4884 {
4885     textBuffer *buffer = window->buffer;
4886     RangesetTable *rangesetTable = buffer->rangesetTable;
4887     Rangeset *targetRangeset, *sourceRangeset;
4888     int start, end, isRect, rectStart, rectEnd, maxpos;
4889     int label = 0;
4890 
4891     if (nArgs < 1 || nArgs > 3) {
4892         return wrongNArgsErr(errMsg);
4893     }
4894 
4895     if (!readIntArg(argList[0], &label, errMsg)
4896             || !RangesetLabelOK(label)) {
4897         M_FAILURE("First parameter is an invalid rangeset label in %s");
4898     }
4899 
4900     if (rangesetTable == NULL) {
4901         M_FAILURE("Rangeset does not exist in %s");
4902     }
4903 
4904     targetRangeset = RangesetFetch(rangesetTable, label);
4905     if (targetRangeset == NULL) {
4906         M_FAILURE("Rangeset does not exist in %s");
4907     }
4908 
4909     if (nArgs == 1) {
4910       /* remove current selection in this window */
4911         if (!BufGetSelectionPos(buffer, &start, &end, &isRect, &rectStart, &rectEnd)
4912                 || isRect) {
4913             M_FAILURE("Selection missing or rectangular in call to %s");
4914         }
4915         RangesetRemoveBetween(targetRangeset, start, end);
4916     }
4917 
4918     if (nArgs == 2) {
4919         /* remove ranges taken from a second set */
4920         if (!readIntArg(argList[1], &label, errMsg)
4921                 || !RangesetLabelOK(label)) {
4922             M_FAILURE("Second parameter is an invalid rangeset label in %s");
4923         }
4924 
4925         sourceRangeset = RangesetFetch(rangesetTable, label);
4926         if (sourceRangeset == NULL) {
4927             M_FAILURE("Second rangeset does not exist in %s");
4928         }
4929         RangesetRemove(targetRangeset, sourceRangeset);
4930     }
4931 
4932     if (nArgs == 3) {
4933         /* remove a range bounded by the start and end positions in $2, $3 */
4934         if (!readIntArg(argList[1], &start, errMsg))
4935             return False;
4936         if (!readIntArg(argList[2], &end, errMsg))
4937             return False;
4938 
4939         /* make sure range is in order and fits buffer size */
4940         maxpos = buffer->length;
4941         if (start < 0) start = 0;
4942         if (start > maxpos) start = maxpos;
4943         if (end < 0) end = 0;
4944         if (end > maxpos) end = maxpos;
4945         if (start > end) {int temp = start; start = end; end = temp;}
4946 
4947         RangesetRemoveBetween(targetRangeset, start, end);
4948     }
4949 
4950     /* set up result */
4951     result->tag = NO_TAG;
4952     return True;
4953 }
4954 
4955 
4956 /*
4957 ** Built-in macro subroutine to invert a range set. Argument is $1: range set
4958 ** label (one alphabetic character). Returns nothing. Fails if range set
4959 ** undefined.
4960 */
rangesetInvertMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)4961 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
4962       DataValue *result, char **errMsg)
4963 {
4964 
4965     RangesetTable *rangesetTable = window->buffer->rangesetTable;
4966     Rangeset *rangeset;
4967     int label = 0;
4968 
4969     if (nArgs != 1)
4970         return wrongNArgsErr(errMsg);
4971 
4972     if (!readIntArg(argList[0], &label, errMsg)
4973             || !RangesetLabelOK(label)) {
4974         M_FAILURE("First parameter is an invalid rangeset label in %s");
4975     }
4976 
4977     if (rangesetTable == NULL) {
4978         M_FAILURE("Rangeset does not exist in %s");
4979     }
4980 
4981     rangeset = RangesetFetch(rangesetTable, label);
4982     if (rangeset == NULL) {
4983         M_FAILURE("Rangeset does not exist in %s");
4984     }
4985 
4986     if (RangesetInverse(rangeset) < 0) {
4987         M_FAILURE("Problem inverting rangeset in %s");
4988     }
4989 
4990     /* set up result */
4991     result->tag = NO_TAG;
4992     return True;
4993 }
4994 
4995 
4996 /*
4997 ** Built-in macro subroutine for finding out info about a rangeset.  Takes one
4998 ** argument of a rangeset label.  Returns an array with the following keys:
4999 **    defined, count, color, mode.
5000 */
rangesetInfoMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5001 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
5002       DataValue *result, char **errMsg)
5003 {
5004     RangesetTable *rangesetTable = window->buffer->rangesetTable;
5005     Rangeset *rangeset = NULL;
5006     int count, defined;
5007     char *color, *name, *mode;
5008     DataValue element;
5009     int label = 0;
5010 
5011     if (nArgs != 1)
5012       return wrongNArgsErr(errMsg);
5013 
5014     if (!readIntArg(argList[0], &label, errMsg)
5015             || !RangesetLabelOK(label)) {
5016         M_FAILURE("First parameter is an invalid rangeset label in %s");
5017     }
5018 
5019     if (rangesetTable != NULL) {
5020         rangeset = RangesetFetch(rangesetTable, label);
5021     }
5022 
5023     RangesetGetInfo(rangeset, &defined, &label, &count, &color, &name, &mode);
5024 
5025     /* set up result */
5026     result->tag = ARRAY_TAG;
5027     result->val.arrayPtr = ArrayNew();
5028 
5029     element.tag = INT_TAG;
5030     element.val.n = defined;
5031     if (!ArrayInsert(result, PERM_ALLOC_STR("defined"), &element))
5032         M_FAILURE("Failed to insert array element \"defined\" in %s");
5033 
5034     element.tag = INT_TAG;
5035     element.val.n = count;
5036     if (!ArrayInsert(result, PERM_ALLOC_STR("count"), &element))
5037         M_FAILURE("Failed to insert array element \"count\" in %s");
5038 
5039     element.tag = STRING_TAG;
5040     if (!AllocNStringCpy(&element.val.str, color))
5041         M_FAILURE("Failed to allocate array value \"color\" in %s");
5042     if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &element))
5043         M_FAILURE("Failed to insert array element \"color\" in %s");
5044 
5045     element.tag = STRING_TAG;
5046     if (!AllocNStringCpy(&element.val.str, name))
5047         M_FAILURE("Failed to allocate array value \"name\" in %s");
5048     if (!ArrayInsert(result, PERM_ALLOC_STR("name"), &element)) {
5049         M_FAILURE("Failed to insert array element \"name\" in %s");
5050     }
5051 
5052     element.tag = STRING_TAG;
5053     if (!AllocNStringCpy(&element.val.str, mode))
5054         M_FAILURE("Failed to allocate array value \"mode\" in %s");
5055     if (!ArrayInsert(result, PERM_ALLOC_STR("mode"), &element))
5056         M_FAILURE("Failed to insert array element \"mode\" in %s");
5057 
5058     return True;
5059 }
5060 
5061 /*
5062 ** Built-in macro subroutine for finding the extent of a range in a set.
5063 ** If only one parameter is supplied, use the spanning range of all
5064 ** ranges, otherwise select the individual range specified.  Returns
5065 ** an array with the keys "start" and "end" and values
5066 */
rangesetRangeMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5067 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
5068       DataValue *result, char **errMsg)
5069 {
5070     textBuffer *buffer = window->buffer;
5071     RangesetTable *rangesetTable = buffer->rangesetTable;
5072     Rangeset *rangeset;
5073     int start, end, dummy, rangeIndex, ok;
5074     DataValue element;
5075     int label = 0;
5076 
5077     if (nArgs < 1 || nArgs > 2) {
5078         return wrongNArgsErr(errMsg);
5079     }
5080 
5081     if (!readIntArg(argList[0], &label, errMsg)
5082             || !RangesetLabelOK(label)) {
5083         M_FAILURE("First parameter is an invalid rangeset label in %s");
5084     }
5085 
5086     if (rangesetTable == NULL) {
5087         M_FAILURE("Rangeset does not exist in %s");
5088     }
5089 
5090     ok = False;
5091     rangeset = RangesetFetch(rangesetTable, label);
5092     if (rangeset != NULL) {
5093         if (nArgs == 1) {
5094             rangeIndex = RangesetGetNRanges(rangeset) - 1;
5095             ok = RangesetFindRangeNo(rangeset, 0, &start, &dummy);
5096             ok &= RangesetFindRangeNo(rangeset, rangeIndex, &dummy, &end);
5097             rangeIndex = -1;
5098         }
5099         else if (nArgs == 2) {
5100             if (!readIntArg(argList[1], &rangeIndex, errMsg)) {
5101                 return False;
5102             }
5103             ok = RangesetFindRangeNo(rangeset, rangeIndex-1, &start, &end);
5104         }
5105     }
5106 
5107     /* set up result */
5108     result->tag = ARRAY_TAG;
5109     result->val.arrayPtr = ArrayNew();
5110 
5111     if (!ok)
5112         return True;
5113 
5114     element.tag = INT_TAG;
5115     element.val.n = start;
5116     if (!ArrayInsert(result, PERM_ALLOC_STR("start"), &element))
5117         M_FAILURE("Failed to insert array element \"start\" in %s");
5118 
5119     element.tag = INT_TAG;
5120     element.val.n = end;
5121     if (!ArrayInsert(result, PERM_ALLOC_STR("end"), &element))
5122         M_FAILURE("Failed to insert array element \"end\" in %s");
5123 
5124     return True;
5125 }
5126 
5127 /*
5128 ** Built-in macro subroutine for checking a position against a range. If only
5129 ** one parameter is supplied, the current cursor position is used. Returns
5130 ** false (zero) if not in a range, range index (1-based) if in a range;
5131 ** fails if parameters were bad.
5132 */
rangesetIncludesPosMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5133 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
5134       int nArgs, DataValue *result, char **errMsg)
5135 {
5136     textBuffer *buffer = window->buffer;
5137     RangesetTable *rangesetTable = buffer->rangesetTable;
5138     Rangeset *rangeset;
5139     int pos, rangeIndex, maxpos;
5140     int label = 0;
5141 
5142     if (nArgs < 1 || nArgs > 2) {
5143         return wrongNArgsErr(errMsg);
5144     }
5145 
5146     if (!readIntArg(argList[0], &label, errMsg)
5147             || !RangesetLabelOK(label)) {
5148         M_FAILURE("First parameter is an invalid rangeset label in %s");
5149     }
5150 
5151     if (rangesetTable == NULL) {
5152         M_FAILURE("Rangeset does not exist in %s");
5153     }
5154 
5155     rangeset = RangesetFetch(rangesetTable, label);
5156     if (rangeset == NULL) {
5157         M_FAILURE("Rangeset does not exist in %s");
5158     }
5159 
5160     if (nArgs == 1) {
5161         pos = TextGetCursorPos(window->lastFocus);
5162     }
5163     else if (nArgs == 2) {
5164         if (!readIntArg(argList[1], &pos, errMsg))
5165             return False;
5166     }
5167 
5168     maxpos = buffer->length;
5169     if (pos < 0 || pos > maxpos) {
5170         rangeIndex = 0;
5171     }
5172     else {
5173         rangeIndex = RangesetFindRangeOfPos(rangeset, pos, False) + 1;
5174     }
5175 
5176     /* set up result */
5177     result->tag = INT_TAG;
5178     result->val.n = rangeIndex;
5179     return True;
5180 }
5181 
5182 /*
5183 ** Set the color of a range set's ranges. it is ignored if the color cannot be
5184 ** found/applied. If no color is applied, any current color is removed. Returns
5185 ** true if the rangeset is valid.
5186 */
rangesetSetColorMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5187 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
5188       int nArgs, DataValue *result, char **errMsg)
5189 {
5190     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5191     textBuffer *buffer = window->buffer;
5192     RangesetTable *rangesetTable = buffer->rangesetTable;
5193     Rangeset *rangeset;
5194     char *color_name;
5195     int label = 0;
5196 
5197     if (nArgs != 2) {
5198         return wrongNArgsErr(errMsg);
5199     }
5200 
5201     if (!readIntArg(argList[0], &label, errMsg)
5202             || !RangesetLabelOK(label)) {
5203         M_FAILURE("First parameter is an invalid rangeset label in %s");
5204     }
5205 
5206     if (rangesetTable == NULL) {
5207         M_FAILURE("Rangeset does not exist in %s");
5208     }
5209 
5210     rangeset = RangesetFetch(rangesetTable, label);
5211     if (rangeset == NULL) {
5212         M_FAILURE("Rangeset does not exist in %s");
5213     }
5214 
5215     color_name = "";
5216     if (rangeset != NULL) {
5217         if (!readStringArg(argList[1], &color_name, stringStorage[0], errMsg)) {
5218             M_FAILURE("Second parameter is not a color name string in %s");
5219         }
5220     }
5221 
5222     RangesetAssignColorName(rangeset, color_name);
5223 
5224     /* set up result */
5225     result->tag = NO_TAG;
5226     return True;
5227 }
5228 
5229 /*
5230 ** Set the name of a range set's ranges. Returns
5231 ** true if the rangeset is valid.
5232 */
rangesetSetNameMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5233 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
5234       int nArgs, DataValue *result, char **errMsg)
5235 {
5236     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5237     textBuffer *buffer = window->buffer;
5238     RangesetTable *rangesetTable = buffer->rangesetTable;
5239     Rangeset *rangeset;
5240     char *name;
5241     int label = 0;
5242 
5243     if (nArgs != 2) {
5244         return wrongNArgsErr(errMsg);
5245     }
5246 
5247     if (!readIntArg(argList[0], &label, errMsg)
5248         || !RangesetLabelOK(label)) {
5249         M_FAILURE("First parameter is an invalid rangeset label in %s");
5250     }
5251 
5252     if (rangesetTable == NULL) {
5253         M_FAILURE("Rangeset does not exist in %s");
5254     }
5255 
5256     rangeset = RangesetFetch(rangesetTable, label);
5257     if (rangeset == NULL) {
5258         M_FAILURE("Rangeset does not exist in %s");
5259     }
5260 
5261     name = "";
5262     if (rangeset != NULL) {
5263         if (!readStringArg(argList[1], &name, stringStorage[0], errMsg)) {
5264             M_FAILURE("Second parameter is not a valid name string in %s");
5265         }
5266     }
5267 
5268     RangesetAssignName(rangeset, name);
5269 
5270     /* set up result */
5271     result->tag = NO_TAG;
5272     return True;
5273 }
5274 
5275 /*
5276 ** Change a range's modification response. Returns true if the rangeset is
5277 ** valid and the response type name is valid.
5278 */
rangesetSetModeMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5279 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
5280       int nArgs, DataValue *result, char **errMsg)
5281 {
5282     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5283     textBuffer *buffer = window->buffer;
5284     RangesetTable *rangesetTable = buffer->rangesetTable;
5285     Rangeset *rangeset;
5286     char *update_fn_name;
5287     int ok;
5288     int label = 0;
5289 
5290     if (nArgs < 1 || nArgs > 2) {
5291         return wrongNArgsErr(errMsg);
5292     }
5293 
5294     if (!readIntArg(argList[0], &label, errMsg)
5295             || !RangesetLabelOK(label)) {
5296         M_FAILURE("First parameter is an invalid rangeset label in %s");
5297     }
5298 
5299     if (rangesetTable == NULL) {
5300         M_FAILURE("Rangeset does not exist in %s");
5301     }
5302 
5303     rangeset = RangesetFetch(rangesetTable, label);
5304     if (rangeset == NULL) {
5305         M_FAILURE("Rangeset does not exist in %s");
5306     }
5307 
5308     update_fn_name = "";
5309     if (rangeset != NULL) {
5310         if (nArgs == 2) {
5311             if (!readStringArg(argList[1], &update_fn_name, stringStorage[0], errMsg)) {
5312                 M_FAILURE("Second parameter is not a string in %s");
5313             }
5314         }
5315     }
5316 
5317     ok = RangesetChangeModifyResponse(rangeset, update_fn_name);
5318 
5319     if (!ok) {
5320         M_FAILURE("Second parameter is not a valid mode in %s");
5321     }
5322 
5323     /* set up result */
5324     result->tag = NO_TAG;
5325     return True;
5326 }
5327 
5328 /* -------------------------------------------------------------------------- */
5329 
5330 
5331 /*
5332 ** Routines to get details directly from the window.
5333 */
5334 
5335 /*
5336 ** Sets up an array containing information about a style given its name or
5337 ** a buffer position (bufferPos >= 0) and its highlighting pattern code
5338 ** (patCode >= 0).
5339 ** From the name we obtain:
5340 **      ["color"]       Foreground color name of style
5341 **      ["background"]  Background color name of style if specified
5342 **      ["bold"]        '1' if style is bold, '0' otherwise
5343 **      ["italic"]      '1' if style is italic, '0' otherwise
5344 ** Given position and pattern code we obtain:
5345 **      ["rgb"]         RGB representation of foreground color of style
5346 **      ["back_rgb"]    RGB representation of background color of style
5347 **      ["extent"]      Forward distance from position over which style applies
5348 ** We only supply the style name if the includeName parameter is set:
5349 **      ["style"]       Name of style
5350 **
5351 */
fillStyleResult(DataValue * result,char ** errMsg,WindowInfo * window,char * styleName,Boolean preallocatedStyleName,Boolean includeName,int patCode,int bufferPos)5352 static int fillStyleResult(DataValue *result, char **errMsg,
5353         WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
5354         Boolean includeName, int patCode, int bufferPos)
5355 {
5356     DataValue DV;
5357     char colorValue[20];
5358     int r, g, b;
5359 
5360     /* initialize array */
5361     result->tag = ARRAY_TAG;
5362     result->val.arrayPtr = ArrayNew();
5363 
5364     /* the following array entries will be strings */
5365     DV.tag = STRING_TAG;
5366 
5367     if (includeName) {
5368         /* insert style name */
5369         if (preallocatedStyleName) {
5370             DV.val.str.rep = styleName;
5371             DV.val.str.len = strlen(styleName);
5372         }
5373         else {
5374             AllocNStringCpy(&DV.val.str, styleName);
5375         }
5376         M_STR_ALLOC_ASSERT(DV);
5377         if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5378             M_ARRAY_INSERT_FAILURE();
5379         }
5380     }
5381 
5382     /* insert color name */
5383     AllocNStringCpy(&DV.val.str, ColorOfNamedStyle(styleName));
5384     M_STR_ALLOC_ASSERT(DV);
5385     if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &DV)) {
5386         M_ARRAY_INSERT_FAILURE();
5387     }
5388 
5389     /* Prepare array element for color value
5390        (only possible if we pass through the dynamic highlight pattern tables
5391        in other words, only if we have a pattern code) */
5392     if (patCode) {
5393         HighlightColorValueOfCode(window, patCode, &r, &g, &b);
5394         sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
5395         AllocNStringCpy(&DV.val.str, colorValue);
5396         M_STR_ALLOC_ASSERT(DV);
5397         if (!ArrayInsert(result, PERM_ALLOC_STR("rgb"), &DV)) {
5398             M_ARRAY_INSERT_FAILURE();
5399         }
5400     }
5401 
5402     /* Prepare array element for background color name */
5403     AllocNStringCpy(&DV.val.str, BgColorOfNamedStyle(styleName));
5404     M_STR_ALLOC_ASSERT(DV);
5405     if (!ArrayInsert(result, PERM_ALLOC_STR("background"), &DV)) {
5406         M_ARRAY_INSERT_FAILURE();
5407     }
5408 
5409     /* Prepare array element for background color value
5410        (only possible if we pass through the dynamic highlight pattern tables
5411        in other words, only if we have a pattern code) */
5412     if (patCode) {
5413         GetHighlightBGColorOfCode(window, patCode, &r, &g, &b);
5414         sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
5415         AllocNStringCpy(&DV.val.str, colorValue);
5416         M_STR_ALLOC_ASSERT(DV);
5417         if (!ArrayInsert(result, PERM_ALLOC_STR("back_rgb"), &DV)) {
5418             M_ARRAY_INSERT_FAILURE();
5419         }
5420     }
5421 
5422     /* the following array entries will be integers */
5423     DV.tag = INT_TAG;
5424 
5425     /* Put boldness value in array */
5426     DV.val.n = FontOfNamedStyleIsBold(styleName);
5427     if (!ArrayInsert(result, PERM_ALLOC_STR("bold"), &DV)) {
5428         M_ARRAY_INSERT_FAILURE();
5429     }
5430 
5431     /* Put italicity value in array */
5432     DV.val.n = FontOfNamedStyleIsItalic(styleName);
5433     if (!ArrayInsert(result, PERM_ALLOC_STR("italic"), &DV)) {
5434         M_ARRAY_INSERT_FAILURE();
5435     }
5436 
5437     if (bufferPos >= 0) {
5438         /* insert extent */
5439         const char *styleNameNotUsed = NULL;
5440         DV.val.n = StyleLengthOfCodeFromPos(window, bufferPos, &styleNameNotUsed);
5441         if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) {
5442             M_ARRAY_INSERT_FAILURE();
5443         }
5444     }
5445     return True;
5446 }
5447 
5448 /*
5449 ** Returns an array containing information about the style of name $1
5450 **      ["color"]       Foreground color name of style
5451 **      ["background"]  Background color name of style if specified
5452 **      ["bold"]        '1' if style is bold, '0' otherwise
5453 **      ["italic"]      '1' if style is italic, '0' otherwise
5454 **
5455 */
getStyleByNameMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5456 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5457         DataValue *result, char **errMsg)
5458 {
5459     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5460     char *styleName;
5461 
5462     /* Validate number of arguments */
5463     if (nArgs != 1) {
5464         return wrongNArgsErr(errMsg);
5465     }
5466 
5467     /* Prepare result */
5468     result->tag = ARRAY_TAG;
5469     result->val.arrayPtr = NULL;
5470 
5471     if (!readStringArg(argList[0], &styleName, stringStorage[0], errMsg)) {
5472         M_FAILURE("First parameter is not a string in %s");
5473     }
5474 
5475     if (!NamedStyleExists(styleName)) {
5476         /* if the given name is invalid we just return an empty array. */
5477         return True;
5478     }
5479 
5480     return fillStyleResult(result, errMsg, window,
5481         styleName, (argList[0].tag == STRING_TAG), False, 0, -1);
5482 }
5483 
5484 /*
5485 ** Returns an array containing information about the style of position $1
5486 **      ["style"]       Name of style
5487 **      ["color"]       Foreground color name of style
5488 **      ["background"]  Background color name of style if specified
5489 **      ["bold"]        '1' if style is bold, '0' otherwise
5490 **      ["italic"]      '1' if style is italic, '0' otherwise
5491 **      ["rgb"]         RGB representation of foreground color of style
5492 **      ["back_rgb"]    RGB representation of background color of style
5493 **      ["extent"]      Forward distance from position over which style applies
5494 **
5495 */
getStyleAtPosMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5496 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5497         DataValue *result, char **errMsg)
5498 {
5499     int patCode;
5500     int bufferPos;
5501     textBuffer *buf = window->buffer;
5502 
5503     /* Validate number of arguments */
5504     if (nArgs != 1) {
5505         return wrongNArgsErr(errMsg);
5506     }
5507 
5508     /* Prepare result */
5509     result->tag = ARRAY_TAG;
5510     result->val.arrayPtr = NULL;
5511 
5512     if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5513         return False;
5514     }
5515 
5516     /*  Verify sane buffer position */
5517     if ((bufferPos < 0) || (bufferPos >= buf->length)) {
5518         /*  If the position is not legal, we cannot guess anything about
5519             the style, so we return an empty array. */
5520         return True;
5521     }
5522 
5523     /* Determine pattern code */
5524     patCode = HighlightCodeOfPos(window, bufferPos);
5525     if (patCode == 0) {
5526         /* if there is no pattern we just return an empty array. */
5527         return True;
5528     }
5529 
5530     return fillStyleResult(result, errMsg, window,
5531         HighlightStyleOfCode(window, patCode), False, True, patCode, bufferPos);
5532 }
5533 
5534 /*
5535 ** Sets up an array containing information about a pattern given its name or
5536 ** a buffer position (bufferPos >= 0).
5537 ** From the name we obtain:
5538 **      ["style"]       Name of style
5539 **      ["extent"]      Forward distance from position over which style applies
5540 ** We only supply the pattern name if the includeName parameter is set:
5541 **      ["pattern"]     Name of pattern
5542 **
5543 */
fillPatternResult(DataValue * result,char ** errMsg,WindowInfo * window,char * patternName,Boolean preallocatedPatternName,Boolean includeName,char * styleName,int bufferPos)5544 static int fillPatternResult(DataValue *result, char **errMsg,
5545         WindowInfo *window, char *patternName, Boolean preallocatedPatternName,
5546         Boolean includeName, char* styleName, int bufferPos)
5547 {
5548     DataValue DV;
5549 
5550     /* initialize array */
5551     result->tag = ARRAY_TAG;
5552     result->val.arrayPtr = ArrayNew();
5553 
5554     /* the following array entries will be strings */
5555     DV.tag = STRING_TAG;
5556 
5557     if (includeName) {
5558         /* insert pattern name */
5559         if (preallocatedPatternName) {
5560             DV.val.str.rep = patternName;
5561             DV.val.str.len = strlen(patternName);
5562         }
5563         else {
5564             AllocNStringCpy(&DV.val.str, patternName);
5565         }
5566         M_STR_ALLOC_ASSERT(DV);
5567         if (!ArrayInsert(result, PERM_ALLOC_STR("pattern"), &DV)) {
5568             M_ARRAY_INSERT_FAILURE();
5569         }
5570     }
5571 
5572     /* insert style name */
5573     AllocNStringCpy(&DV.val.str, styleName);
5574     M_STR_ALLOC_ASSERT(DV);
5575     if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5576         M_ARRAY_INSERT_FAILURE();
5577     }
5578 
5579     /* the following array entries will be integers */
5580     DV.tag = INT_TAG;
5581 
5582     if (bufferPos >= 0) {
5583         /* insert extent */
5584         int checkCode = 0;
5585         DV.val.n = HighlightLengthOfCodeFromPos(window, bufferPos, &checkCode);
5586         if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) {
5587             M_ARRAY_INSERT_FAILURE();
5588         }
5589     }
5590 
5591     return True;
5592 }
5593 
5594 /*
5595 ** Returns an array containing information about a highlighting pattern. The
5596 ** single parameter contains the pattern name for which this information is
5597 ** requested.
5598 ** The returned array looks like this:
5599 **      ["style"]       Name of style
5600 */
getPatternByNameMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5601 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5602         DataValue *result, char **errMsg)
5603 {
5604     char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5605     char *patternName = NULL;
5606     highlightPattern *pattern;
5607 
5608     /* Begin of building the result. */
5609     result->tag = ARRAY_TAG;
5610     result->val.arrayPtr = NULL;
5611 
5612     /* Validate number of arguments */
5613     if (nArgs != 1) {
5614         return wrongNArgsErr(errMsg);
5615     }
5616 
5617     if (!readStringArg(argList[0], &patternName, stringStorage[0], errMsg)) {
5618         M_FAILURE("First parameter is not a string in %s");
5619     }
5620 
5621     pattern = FindPatternOfWindow(window, patternName);
5622     if (pattern == NULL) {
5623         /* The pattern's name is unknown. */
5624         return True;
5625     }
5626 
5627     return fillPatternResult(result, errMsg, window, patternName,
5628             (argList[0].tag == STRING_TAG), False, pattern->style, -1);
5629 }
5630 
5631 /*
5632 ** Returns an array containing information about the highlighting pattern
5633 ** applied at a given position, passed as the only parameter.
5634 ** The returned array looks like this:
5635 **      ["pattern"]     Name of pattern
5636 **      ["style"]       Name of style
5637 **      ["extent"]      Distance from position over which this pattern applies
5638 */
getPatternAtPosMS(WindowInfo * window,DataValue * argList,int nArgs,DataValue * result,char ** errMsg)5639 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5640         DataValue *result, char **errMsg)
5641 {
5642     int bufferPos = -1;
5643     textBuffer *buffer = window->buffer;
5644     int patCode = 0;
5645 
5646     /* Begin of building the result. */
5647     result->tag = ARRAY_TAG;
5648     result->val.arrayPtr = NULL;
5649 
5650     /* Validate number of arguments */
5651     if (nArgs != 1) {
5652         return wrongNArgsErr(errMsg);
5653     }
5654 
5655     /* The most straightforward case: Get a pattern, style and extent
5656        for a buffer position. */
5657     if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5658         return False;
5659     }
5660 
5661     /*  Verify sane buffer position
5662      *  You would expect that buffer->length would be among the sane
5663      *  positions, but we have n characters and n+1 buffer positions. */
5664     if ((bufferPos < 0) || (bufferPos >= buffer->length)) {
5665         /*  If the position is not legal, we cannot guess anything about
5666             the highlighting pattern, so we return an empty array. */
5667         return True;
5668     }
5669 
5670     /* Determine the highlighting pattern used */
5671     patCode = HighlightCodeOfPos(window, bufferPos);
5672     if (patCode == 0) {
5673         /* if there is no highlighting pattern we just return an empty array. */
5674         return True;
5675     }
5676 
5677     return fillPatternResult(result, errMsg, window,
5678         HighlightNameOfCode(window, patCode), False, True,
5679         HighlightStyleOfCode(window, patCode), bufferPos);
5680 }
5681 
wrongNArgsErr(char ** errMsg)5682 static int wrongNArgsErr(char **errMsg)
5683 {
5684     *errMsg = "Wrong number of arguments to function %s";
5685     return False;
5686 }
5687 
tooFewArgsErr(char ** errMsg)5688 static int tooFewArgsErr(char **errMsg)
5689 {
5690     *errMsg = "Too few arguments to function %s";
5691     return False;
5692 }
5693 
5694 /*
5695 ** strCaseCmp compares its arguments and returns 0 if the two strings
5696 ** are equal IGNORING case differences.  Otherwise returns 1 or -1
5697 ** depending on relative comparison.
5698 */
strCaseCmp(char * str1,char * str2)5699 static int strCaseCmp(char *str1, char *str2)
5700 {
5701     char *c1, *c2;
5702 
5703     for (c1 = str1, c2 = str2;
5704             (*c1 != '\0' && *c2 != '\0')
5705                     && toupper((unsigned char)*c1) == toupper((unsigned char)*c2);
5706             ++c1, ++c2)
5707     {
5708     }
5709 
5710     if (((unsigned char)toupper((unsigned char)*c1))
5711             > ((unsigned char)toupper((unsigned char)*c2)))
5712     {
5713         return(1);
5714     } else if (((unsigned char)toupper((unsigned char)*c1))
5715             < ((unsigned char)toupper((unsigned char)*c2)))
5716     {
5717         return(-1);
5718     } else
5719     {
5720         return(0);
5721     }
5722 }
5723 
5724 /*
5725 ** Get an integer value from a tagged DataValue structure.  Return True
5726 ** if conversion succeeded, and store result in *result, otherwise
5727 ** return False with an error message in *errMsg.
5728 */
readIntArg(DataValue dv,int * result,char ** errMsg)5729 static int readIntArg(DataValue dv, int *result, char **errMsg)
5730 {
5731     char *c;
5732 
5733     if (dv.tag == INT_TAG) {
5734     	*result = dv.val.n;
5735     	return True;
5736     } else if (dv.tag == STRING_TAG) {
5737 	for (c=dv.val.str.rep; *c != '\0'; c++) {
5738     	    if (!(isdigit((unsigned char)*c) || *c == ' ' || *c == '\t')) {
5739     		goto typeError;
5740     	    }
5741     	}
5742 	sscanf(dv.val.str.rep, "%d", result);
5743 	return True;
5744     }
5745 
5746 typeError:
5747     *errMsg = "%s called with non-integer argument";
5748     return False;
5749 }
5750 
5751 /*
5752 ** Get an string value from a tagged DataValue structure.  Return True
5753 ** if conversion succeeded, and store result in *result, otherwise
5754 ** return False with an error message in *errMsg.  If an integer value
5755 ** is converted, write the string in the space provided by "stringStorage",
5756 ** which must be large enough to handle ints of the maximum size.
5757 */
readStringArg(DataValue dv,char ** result,char * stringStorage,char ** errMsg)5758 static int readStringArg(DataValue dv, char **result, char *stringStorage,
5759     	char **errMsg)
5760 {
5761     if (dv.tag == STRING_TAG) {
5762     	*result = dv.val.str.rep;
5763     	return True;
5764     } else if (dv.tag == INT_TAG) {
5765 	sprintf(stringStorage, "%d", dv.val.n);
5766 	*result = stringStorage;
5767 	return True;
5768     }
5769     *errMsg = "%s called with unknown object";
5770     return False;
5771 }
5772