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 ©Start, ©End, &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