Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / source / macro.c
blob496b7037069ecdccb839e648f906423b63d91130
1 static const char CVSID[] = "$Id: macro.c,v 1.116 2008/08/20 14:57:35 lebert Exp $";
2 /*******************************************************************************
3 * *
4 * macro.c -- Macro file processing, learn/replay, and built-in macro *
5 * subroutines *
6 * *
7 * Copyright (C) 1999 Mark Edel *
8 * *
9 * This is free software; you can redistribute it and/or modify it under the *
10 * terms of the GNU General Public License as published by the Free Software *
11 * Foundation; either version 2 of the License, or (at your option) any later *
12 * version. In addition, you may distribute versions of this program linked to *
13 * Motif or Open Motif. See README for details. *
14 * *
15 * This software is distributed in the hope that it will be useful, but WITHOUT *
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
18 * for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License along with *
21 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
22 * Place, Suite 330, Boston, MA 02111-1307 USA *
23 * *
24 * Nirvana Text Editor *
25 * April, 1997 *
26 * *
27 * Written by Mark Edel *
28 * *
29 *******************************************************************************/
31 #ifdef HAVE_CONFIG_H
32 #include "../config.h"
33 #endif
35 #include "macro.h"
36 #include "textBuf.h"
37 #include "text.h"
38 #include "nedit.h"
39 #include "window.h"
40 #include "preferences.h"
41 #include "interpret.h"
42 #include "parse.h"
43 #include "search.h"
44 #include "server.h"
45 #include "shell.h"
46 #include "smartIndent.h"
47 #include "userCmds.h"
48 #include "selection.h"
49 #include "rbTree.h"
50 #include "tags.h"
51 #include "calltips.h"
52 #include "../util/DialogF.h"
53 #include "../util/misc.h"
54 #include "../util/fileUtils.h"
55 #include "../util/utils.h"
56 #include "../util/getfiles.h"
57 #include "highlight.h"
58 #include "highlightData.h"
59 #include "rangeset.h"
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*/
81 #include <X11/Intrinsic.h>
82 #include <X11/keysym.h>
83 #include <Xm/Xm.h>
84 #include <Xm/CutPaste.h>
85 #include <Xm/Form.h>
86 #include <Xm/RowColumn.h>
87 #include <Xm/LabelG.h>
88 #include <Xm/List.h>
89 #include <Xm/ToggleB.h>
90 #include <Xm/DialogS.h>
91 #include <Xm/MessageB.h>
92 #include <Xm/SelectioB.h>
93 #include <Xm/PushB.h>
94 #include <Xm/Text.h>
95 #include <Xm/Separator.h>
97 #ifdef HAVE_DEBUG_H
98 #include "../debug.h"
99 #endif
101 /* Maximum number of actions in a macro and args in
102 an action (to simplify the reader) */
103 #define MAX_MACRO_ACTIONS 1024
104 #define MAX_ACTION_ARGS 40
106 /* How long to wait (msec) before putting up Macro Command banner */
107 #define BANNER_WAIT_TIME 6000
109 /* The following definitions cause an exit from the macro with a message */
110 /* added if (1) to remove compiler warnings on solaris */
111 #define M_FAILURE(s) do { *errMsg = s; if (1) return False; } while (0)
112 #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)
113 #define M_ARRAY_INSERT_FAILURE() M_FAILURE("array element failed to insert: %s")
115 /* Data attached to window during shell command execution with
116 information for controling and communicating with the process */
117 typedef struct {
118 XtIntervalId bannerTimeoutID;
119 XtWorkProcId continueWorkProcID;
120 char bannerIsUp;
121 char closeOnCompletion;
122 Program *program;
123 RestartData *context;
124 Widget dialog;
125 } macroCmdInfo;
127 /* Widgets and global data for Repeat dialog */
128 typedef struct {
129 WindowInfo *forWindow;
130 char *lastCommand;
131 Widget shell, repeatText, lastCmdToggle;
132 Widget inSelToggle, toEndToggle;
133 } repeatDialog;
135 static void cancelLearn(void);
136 static void runMacro(WindowInfo *window, Program *prog);
137 static void finishMacroCmdExecution(WindowInfo *window);
138 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData);
139 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData);
140 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event);
141 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData);
142 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
143 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
144 XEvent *event, String *params, Cardinal *numParams);
145 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
146 XEvent *event, String *params, Cardinal *numParams);
147 static char *actionToString(Widget w, char *actionName, XEvent *event,
148 String *params, Cardinal numParams);
149 static int isMouseAction(const char *action);
150 static int isRedundantAction(const char *action);
151 static int isIgnoredAction(const char *action);
152 static int readCheckMacroString(Widget dialogParent, char *string,
153 WindowInfo *runWindow, const char *errIn, char **errPos);
154 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id);
155 static Boolean continueWorkProc(XtPointer clientData);
156 static int escapeStringChars(char *fromString, char *toString);
157 static int escapedStringLength(char *string);
158 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
159 DataValue *result, char **errMsg);
160 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
161 DataValue *result, char **errMsg);
162 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
163 DataValue *result, char **errMsg);
164 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
165 DataValue *result, char **errMsg);
166 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
167 DataValue *result, char **errMsg);
168 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
169 DataValue *result, char **errMsg);
170 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
171 DataValue *result, char **errMsg);
172 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
173 DataValue *result, char **errMsg);
174 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
175 DataValue *result, char **errMsg);
176 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
177 DataValue *result, char **errMsg);
178 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
179 DataValue *result, char **errMsg);
180 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
181 DataValue *result, char **errMsg);
182 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
183 DataValue *result, char **errMsg);
184 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
185 DataValue *result, char **errMsg);
186 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
187 DataValue *result, char **errMsg);
188 static int writeOrAppendFile(int append, WindowInfo *window,
189 DataValue *argList, int nArgs, DataValue *result, char **errMsg);
190 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
191 DataValue *result, char **errMsg);
192 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
193 DataValue *result, char **errMsg);
194 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
195 DataValue *result, char **errMsg);
196 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
197 DataValue *result, char **errMsg);
198 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
199 DataValue *result, char **errMsg);
200 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
201 DataValue *result, char **errMsg);
202 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
203 DataValue *result, char **errMsg);
204 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
205 DataValue *result, char **errMsg);
206 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
207 DataValue *result, char **errMsg);
208 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
209 DataValue *result, char **errMsg);
210 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
211 DataValue *result, char **errMsg);
212 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
213 DataValue *result, char **errMsg);
214 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
215 DataValue *result, char **errMsg);
216 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
217 DataValue *result, char **errMsg);
218 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
219 DataValue *result, char **errMsg);
220 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData);
221 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData);
222 #ifdef LESSTIF_VERSION
223 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
224 Boolean *cont);
225 #endif /* LESSTIF_VERSION */
226 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
227 DataValue *result, char **errMsg);
228 static void stringDialogBtnCB(Widget w, XtPointer clientData,
229 XtPointer callData);
230 static void stringDialogCloseCB(Widget w, XtPointer clientData,
231 XtPointer callData);
232 #ifdef LESSTIF_VERSION
233 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
234 Boolean *cont);
235 #endif /* LESSTIF_VERSION */
236 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
237 DataValue *result, char **errMsg);
238 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
239 DataValue *result, char **errMsg);
240 /* T Balinski */
241 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
242 DataValue *result, char **errMsg);
243 static void listDialogBtnCB(Widget w, XtPointer clientData,
244 XtPointer callData);
245 static void listDialogCloseCB(Widget w, XtPointer clientData,
246 XtPointer callData);
247 /* T Balinski End */
248 #ifdef LESSTIF_VERSION
249 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
250 Boolean *cont);
251 #endif /* LESSTIF_VERSION */
252 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
253 DataValue *result, char **errMsg);
254 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
255 DataValue *result, char **errMsg);
256 /* DISASBLED for 5.4
257 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
258 int nArgs, DataValue *result, char **errMsg);
260 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
261 DataValue *result, char **errMsg);
262 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
263 DataValue *result, char **errMsg);
264 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
265 DataValue *result, char **errMsg);
266 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
267 DataValue *result, char **errMsg);
268 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
269 DataValue *result, char **errMsg);
270 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
271 DataValue *result, char **errMsg);
272 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
273 DataValue *result, char **errMsg);
274 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
275 DataValue *result, char **errMsg);
276 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
277 DataValue *result, char **errMsg);
278 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
279 DataValue *result, char **errMsg);
280 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
281 DataValue *result, char **errMsg);
282 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
283 DataValue *result, char **errMsg);
284 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
285 DataValue *result, char **errMsg);
286 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
287 DataValue *result, char **errMsg);
288 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
289 DataValue *result, char **errMsg);
290 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
291 DataValue *result, char **errMsg);
292 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
293 DataValue *result, char **errMsg);
294 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
295 DataValue *result, char **errMsg);
296 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
297 DataValue *result, char **errMsg);
298 static int matchSyntaxBasedMV(WindowInfo *window, DataValue *argList, int nArgs,
299 DataValue *result, char **errMsg);
300 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
301 DataValue *result, char **errMsg);
302 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
303 DataValue *result, char **errMsg);
304 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
305 DataValue *result, char **errMsg);
306 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
307 DataValue *result, char **errMsg);
308 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
309 DataValue *result, char **errMsg);
310 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
311 DataValue *result, char **errMsg);
312 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
313 DataValue *result, char **errMsg);
314 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
315 DataValue *result, char **errMsg);
316 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
317 DataValue *result, char **errMsg);
318 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
319 DataValue *result, char **errMsg);
320 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
321 DataValue *result, char **errMsg);
322 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
323 DataValue *result, char **errMsg);
324 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
325 DataValue *result, char **errMsg);
326 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
327 DataValue *result, char **errMsg);
328 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
329 DataValue *result, char **errMsg);
330 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
331 DataValue *result, char **errMsg);
332 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
333 DataValue *result, char **errMsg);
334 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
335 DataValue *result, char **errMsg);
336 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
337 DataValue *result, char **errMsg);
338 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
339 DataValue *result, char **errMsg);
340 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
341 DataValue *result, char **errMsg);
342 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
343 DataValue *result, char **errMsg);
344 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
345 DataValue *result, char **errMsg);
346 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
347 DataValue *result, char **errMsg);
348 static int calltipIDMV(WindowInfo *window, DataValue *argList, int nArgs,
349 DataValue *result, char **errMsg);
350 static int readSearchArgs(DataValue *argList, int nArgs, int*searchDirection,
351 int *searchType, int *wrap, char **errMsg);
352 static int wrongNArgsErr(char **errMsg);
353 static int tooFewArgsErr(char **errMsg);
354 static int strCaseCmp(char *str1, char *str2);
355 static int readIntArg(DataValue dv, int *result, char **errMsg);
356 static int readStringArg(DataValue dv, char **result, char *stringStorage,
357 char **errMsg);
358 /* DISABLED FOR 5.4
359 static int backlightStringMV(WindowInfo *window, DataValue *argList,
360 int nArgs, DataValue *result, char **errMsg);
362 static int rangesetListMV(WindowInfo *window, DataValue *argList,
363 int nArgs, DataValue *result, char **errMsg);
364 static int versionMV(WindowInfo* window, DataValue* argList, int nArgs,
365 DataValue* result, char** errMsg);
366 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
367 DataValue *result, char **errMsg);
368 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
369 DataValue *result, char **errMsg);
370 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
371 DataValue *result, char **errMsg);
372 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
373 DataValue *result, char **errMsg);
374 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
375 DataValue *result, char **errMsg);
376 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
377 DataValue *result, char **errMsg);
378 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
379 DataValue *result, char **errMsg);
380 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
381 DataValue *result, char **errMsg);
382 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
383 int nArgs, DataValue *result, char **errMsg);
384 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
385 int nArgs, DataValue *result, char **errMsg);
386 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
387 int nArgs, DataValue *result, char **errMsg);
388 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
389 int nArgs, DataValue *result, char **errMsg);
391 static int fillPatternResult(DataValue *result, char **errMsg, WindowInfo *window,
392 char *patternName, Boolean preallocatedPatternName, Boolean includeName,
393 char *styleName, int bufferPos);
394 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
395 DataValue *result, char **errMsg);
396 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
397 DataValue *result, char **errMsg);
399 static int fillStyleResult(DataValue *result, char **errMsg,
400 WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
401 Boolean includeName, int patCode, int bufferPos);
402 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
403 DataValue *result, char **errMsg);
404 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
405 DataValue *result, char **errMsg);
406 static int filenameDialogMS(WindowInfo* window, DataValue* argList, int nArgs,
407 DataValue* result, char** errMsg);
409 /* Built-in subroutines and variables for the macro language */
410 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS,
411 dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS,
412 setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS,
413 searchStringMS, substringMS, replaceSubstringMS, readFileMS,
414 writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS,
415 replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS,
416 shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS,
417 tolowerMS, listDialogMS, getenvMS,
418 stringCompareMS, splitMS, calltipMS, killCalltipMS,
419 /* DISABLED for 5.4 setBacklightStringMS,*/
420 rangesetCreateMS, rangesetDestroyMS,
421 rangesetAddMS, rangesetSubtractMS, rangesetInvertMS,
422 rangesetInfoMS, rangesetRangeMS, rangesetIncludesPosMS,
423 rangesetSetColorMS, rangesetSetNameMS, rangesetSetModeMS,
424 rangesetGetByNameMS,
425 getPatternByNameMS, getPatternAtPosMS,
426 getStyleByNameMS, getStyleAtPosMS, filenameDialogMS
428 #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs)
429 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print",
430 "dialog", "string_dialog", "replace_range", "replace_selection",
431 "set_cursor_pos", "get_character", "min", "max", "search",
432 "search_string", "substring", "replace_substring", "read_file",
433 "write_file", "append_file", "beep", "get_selection", "valid_number",
434 "replace_in_string", "select", "select_rectangle", "focus_window",
435 "shell_command", "string_to_clipboard", "clipboard_to_string",
436 "toupper", "tolower", "list_dialog", "getenv",
437 "string_compare", "split", "calltip", "kill_calltip",
438 /* DISABLED for 5.4 "set_backlight_string", */
439 "rangeset_create", "rangeset_destroy",
440 "rangeset_add", "rangeset_subtract", "rangeset_invert",
441 "rangeset_info", "rangeset_range", "rangeset_includes",
442 "rangeset_set_color", "rangeset_set_name", "rangeset_set_mode",
443 "rangeset_get_by_name",
444 "get_pattern_by_name", "get_pattern_at_pos",
445 "get_style_by_name", "get_style_at_pos", "filename_dialog"
447 static BuiltInSubr SpecialVars[] = {cursorMV, lineMV, columnMV,
448 fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV,
449 selectionLeftMV, selectionRightMV, wrapMarginMV, tabDistMV,
450 emTabDistMV, useTabsMV, languageModeMV, modifiedMV,
451 statisticsLineMV, incSearchLineMV, showLineNumbersMV,
452 autoIndentMV, wrapTextMV, highlightSyntaxMV,
453 makeBackupCopyMV, incBackupMV, showMatchingMV, matchSyntaxBasedMV,
454 overTypeModeMV, readOnlyMV, lockedMV, fileFormatMV,
455 fontNameMV, fontNameItalicMV,
456 fontNameBoldMV, fontNameBoldItalicMV, subscriptSepMV,
457 minFontWidthMV, maxFontWidthMV, topLineMV, numDisplayLinesMV,
458 displayWidthMV, activePaneMV, nPanesMV, emptyArrayMV,
459 serverNameMV, calltipIDMV,
460 /* DISABLED for 5.4 backlightStringMV, */
461 rangesetListMV, versionMV
463 #define N_SPECIAL_VARS (sizeof SpecialVars/sizeof *SpecialVars)
464 static const char *SpecialVarNames[N_SPECIAL_VARS] = {"$cursor", "$line", "$column",
465 "$file_name", "$file_path", "$text_length", "$selection_start",
466 "$selection_end", "$selection_left", "$selection_right",
467 "$wrap_margin", "$tab_dist", "$em_tab_dist", "$use_tabs",
468 "$language_mode", "$modified",
469 "$statistics_line", "$incremental_search_line", "$show_line_numbers",
470 "$auto_indent", "$wrap_text", "$highlight_syntax",
471 "$make_backup_copy", "$incremental_backup", "$show_matching", "$match_syntax_based",
472 "$overtype_mode", "$read_only", "$locked", "$file_format",
473 "$font_name", "$font_name_italic",
474 "$font_name_bold", "$font_name_bold_italic", "$sub_sep",
475 "$min_font_width", "$max_font_width", "$top_line", "$n_display_lines",
476 "$display_width", "$active_pane", "$n_panes", "$empty_array",
477 "$server_name", "$calltip_ID",
478 /* DISABLED for 5.4 "$backlight_string", */
479 "$rangeset_list", "$VERSION"
482 /* Global symbols for returning values from built-in functions */
483 #define N_RETURN_GLOBALS 5
484 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS,
485 SHELL_CMD_STATUS, LIST_DIALOG_BUTTON};
486 static const char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button",
487 "$search_end", "$read_status", "$shell_cmd_status",
488 "$list_dialog_button"};
489 static Symbol *ReturnGlobals[N_RETURN_GLOBALS];
491 /* List of actions not useful when learning a macro sequence (also see below) */
492 static char* IgnoredActions[] = {"focusIn", "focusOut"};
494 /* List of actions intended to be attached to mouse buttons, which the user
495 must be warned can't be recorded in a learn/replay sequence */
496 static const char* MouseActions[] = {"grab_focus", "extend_adjust", "extend_start",
497 "extend_end", "secondary_or_drag_adjust", "secondary_adjust",
498 "secondary_or_drag_start", "secondary_start", "move_destination",
499 "move_to", "move_to_or_end_drag", "copy_to", "copy_to_or_end_drag",
500 "exchange", "process_bdrag", "mouse_pan"};
502 /* List of actions to not record because they
503 generate further actions, more suitable for recording */
504 static const char* RedundantActions[] = {"open_dialog", "save_as_dialog",
505 "revert_to_saved_dialog", "include_file_dialog", "load_macro_file_dialog",
506 "load_tags_file_dialog", "find_dialog", "replace_dialog",
507 "goto_line_number_dialog", "mark_dialog", "goto_mark_dialog",
508 "control_code_dialog", "filter_selection_dialog", "execute_command_dialog",
509 "repeat_dialog", "start_incremental_find"};
511 /* The last command executed (used by the Repeat command) */
512 static char *LastCommand = NULL;
514 /* The current macro to execute on Replay command */
515 static char *ReplayMacro = NULL;
517 /* Buffer where macro commands are recorded in Learn mode */
518 static textBuffer *MacroRecordBuf = NULL;
520 /* Action Hook id for recording actions for Learn mode */
521 static XtActionHookId MacroRecordActionHook = 0;
523 /* Window where macro recording is taking place */
524 static WindowInfo *MacroRecordWindow = NULL;
526 /* Arrays for translating escape characters in escapeStringChars */
527 static char ReplaceChars[] = "\\\"ntbrfav";
528 static char EscapeChars[] = "\\\"\n\t\b\r\f\a\v";
531 ** Install built-in macro subroutines and special variables for accessing
532 ** editor information
534 void RegisterMacroSubroutines(void)
536 static DataValue subrPtr = {NO_TAG, {0}}, noValue = {NO_TAG, {0}};
537 unsigned i;
539 /* Install symbols for built-in routines and variables, with pointers
540 to the appropriate c routines to do the work */
541 for (i=0; i<N_MACRO_SUBRS; i++) {
542 subrPtr.val.subr = MacroSubrs[i];
543 InstallSymbol(MacroSubrNames[i], C_FUNCTION_SYM, subrPtr);
545 for (i=0; i<N_SPECIAL_VARS; i++) {
546 subrPtr.val.subr = SpecialVars[i];
547 InstallSymbol(SpecialVarNames[i], PROC_VALUE_SYM, subrPtr);
550 /* Define global variables used for return values, remember their
551 locations so they can be set without a LookupSymbol call */
552 for (i=0; i<N_RETURN_GLOBALS; i++)
553 ReturnGlobals[i] = InstallSymbol(ReturnGlobalNames[i], GLOBAL_SYM,
554 noValue);
557 #define MAX_LEARN_MSG_LEN ((2 * MAX_ACCEL_LEN) + 60)
558 void BeginLearn(WindowInfo *window)
560 WindowInfo *win;
561 XmString s;
562 XmString xmFinish;
563 XmString xmCancel;
564 char *cFinish;
565 char *cCancel;
566 char message[MAX_LEARN_MSG_LEN];
568 /* If we're already in learn mode, return */
569 if (MacroRecordActionHook != 0)
570 return;
572 /* dim the inappropriate menus and items, and undim finish and cancel */
573 for (win=WindowList; win!=NULL; win=win->next) {
574 if (!IsTopDocument(win))
575 continue;
576 XtSetSensitive(win->learnItem, False);
578 SetSensitive(window, window->finishLearnItem, True);
579 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
580 s=XmStringCreateSimple("Cancel Learn"), NULL);
581 XmStringFree(s);
582 SetSensitive(window, window->cancelMacroItem, True);
584 /* Mark the window where learn mode is happening */
585 MacroRecordWindow = window;
587 /* Allocate a text buffer for accumulating the macro strings */
588 MacroRecordBuf = BufCreate();
590 /* Add the action hook for recording the actions */
591 MacroRecordActionHook =
592 XtAppAddActionHook(XtWidgetToApplicationContext(window->shell),
593 learnActionHook, window);
595 /* Extract accelerator texts from menu PushButtons */
596 XtVaGetValues(window->finishLearnItem, XmNacceleratorText, &xmFinish, NULL);
597 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
599 /* Translate Motif strings to char* */
600 cFinish = GetXmStringText(xmFinish);
601 cCancel = GetXmStringText(xmCancel);
603 /* Free Motif Strings */
604 XmStringFree(xmFinish);
605 XmStringFree(xmCancel);
607 /* Create message */
608 if (cFinish[0] == '\0') {
609 if (cCancel[0] == '\0') {
610 strncpy(message, "Learn Mode -- Use menu to finish or cancel",
611 MAX_LEARN_MSG_LEN);
612 message[MAX_LEARN_MSG_LEN - 1] = '\0';
614 else {
615 sprintf(message,
616 "Learn Mode -- Use menu to finish, press %s to cancel",
617 cCancel);
620 else {
621 if (cCancel[0] == '\0') {
622 sprintf(message,
623 "Learn Mode -- Press %s to finish, use menu to cancel",
624 cFinish);
627 else {
628 sprintf(message,
629 "Learn Mode -- Press %s to finish, %s to cancel",
630 cFinish,
631 cCancel);
635 /* Free C-strings */
636 XtFree(cFinish);
637 XtFree(cCancel);
639 /* Put up the learn-mode banner */
640 SetModeMessage(window, message);
643 void AddLastCommandActionHook(XtAppContext context)
645 XtAppAddActionHook(context, lastActionHook, NULL);
648 void FinishLearn(void)
650 WindowInfo *win;
652 /* If we're not in learn mode, return */
653 if (MacroRecordActionHook == 0)
654 return;
656 /* Remove the action hook */
657 XtRemoveActionHook(MacroRecordActionHook);
658 MacroRecordActionHook = 0;
660 /* Free the old learn/replay sequence */
661 XtFree(ReplayMacro);
663 /* Store the finished action for the replay menu item */
664 ReplayMacro = BufGetAll(MacroRecordBuf);
666 /* Free the buffer used to accumulate the macro sequence */
667 BufFree(MacroRecordBuf);
669 /* Undim the menu items dimmed during learn */
670 for (win=WindowList; win!=NULL; win=win->next) {
671 if (!IsTopDocument(win))
672 continue;
673 XtSetSensitive(win->learnItem, True);
675 if (IsTopDocument(MacroRecordWindow)) {
676 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
677 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
680 /* Undim the replay and paste-macro buttons */
681 for (win=WindowList; win!=NULL; win=win->next) {
682 if (!IsTopDocument(win))
683 continue;
684 XtSetSensitive(win->replayItem, True);
686 DimPasteReplayBtns(True);
688 /* Clear learn-mode banner */
689 ClearModeMessage(MacroRecordWindow);
693 ** Cancel Learn mode, or macro execution (they're bound to the same menu item)
695 void CancelMacroOrLearn(WindowInfo *window)
697 if (MacroRecordActionHook != 0)
698 cancelLearn();
699 else if (window->macroCmdData != NULL)
700 AbortMacroCommand(window);
703 static void cancelLearn(void)
705 WindowInfo *win;
707 /* If we're not in learn mode, return */
708 if (MacroRecordActionHook == 0)
709 return;
711 /* Remove the action hook */
712 XtRemoveActionHook(MacroRecordActionHook);
713 MacroRecordActionHook = 0;
715 /* Free the macro under construction */
716 BufFree(MacroRecordBuf);
718 /* Undim the menu items dimmed during learn */
719 for (win=WindowList; win!=NULL; win=win->next) {
720 if (!IsTopDocument(win))
721 continue;
722 XtSetSensitive(win->learnItem, True);
724 if (IsTopDocument(MacroRecordWindow)) {
725 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
726 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
729 /* Clear learn-mode banner */
730 ClearModeMessage(MacroRecordWindow);
734 ** Execute the learn/replay sequence stored in "window"
736 void Replay(WindowInfo *window)
738 Program *prog;
739 char *errMsg, *stoppedAt;
741 /* Verify that a replay macro exists and it's not empty and that */
742 /* we're not already running a macro */
743 if (ReplayMacro != NULL &&
744 ReplayMacro[0] != 0 &&
745 window->macroCmdData == NULL) {
746 /* Parse the replay macro (it's stored in text form) and compile it into
747 an executable program "prog" */
748 prog = ParseMacro(ReplayMacro, &errMsg, &stoppedAt);
749 if (prog == NULL) {
750 fprintf(stderr,
751 "NEdit internal error, learn/replay macro syntax error: %s\n",
752 errMsg);
753 return;
756 /* run the executable program */
757 runMacro(window, prog);
762 ** Read the initial NEdit macro file if one exists.
764 void ReadMacroInitFile(WindowInfo *window)
766 const char* autoloadName = GetRCFileName(AUTOLOAD_NM);
767 static int initFileLoaded = False;
769 /* GetRCFileName() might return NULL if an error occurs during
770 creation of the preference file directory. */
771 if (autoloadName != NULL && !initFileLoaded)
773 ReadMacroFile(window, autoloadName, False);
774 initFileLoaded = True;
779 ** Read an NEdit macro file. Extends the syntax of the macro parser with
780 ** define keyword, and allows intermixing of defines with immediate actions.
782 int ReadMacroFile(WindowInfo *window, const char *fileName, int warnNotExist)
784 int result;
785 char *fileString;
787 /* read-in macro file and force a terminating \n, to prevent syntax
788 ** errors with statements on the last line
790 fileString = ReadAnyTextFile(fileName, True);
791 if (fileString == NULL){
792 if (errno != ENOENT || warnNotExist)
794 DialogF(DF_ERR, window->shell, 1, "Read Macro",
795 "Error reading macro file %s: %s", "OK", fileName,
796 #ifdef VMS
797 strerror(errno, vaxc$errno));
798 #else
799 strerror(errno));
800 #endif
802 return False;
805 /* Parse fileString */
806 result = readCheckMacroString(window->shell, fileString, window, fileName,
807 NULL);
808 XtFree(fileString);
809 return result;
813 ** Parse and execute a macro string including macro definitions. Report
814 ** parsing errors in a dialog posted over window->shell.
816 int ReadMacroString(WindowInfo *window, char *string, const char *errIn)
818 return readCheckMacroString(window->shell, string, window, errIn, NULL);
822 ** Check a macro string containing definitions for errors. Returns True
823 ** if macro compiled successfully. Returns False and puts up
824 ** a dialog explaining if macro did not compile successfully.
826 int CheckMacroString(Widget dialogParent, char *string, const char *errIn,
827 char **errPos)
829 return readCheckMacroString(dialogParent, string, NULL, errIn, errPos);
833 ** Parse and optionally execute a macro string including macro definitions.
834 ** Report parsing errors in a dialog posted over dialogParent, using the
835 ** string errIn to identify the entity being parsed (filename, macro string,
836 ** etc.). If runWindow is specified, runs the macro against the window. If
837 ** runWindow is passed as NULL, does parse only. If errPos is non-null,
838 ** returns a pointer to the error location in the string.
840 static int readCheckMacroString(Widget dialogParent, char *string,
841 WindowInfo *runWindow, const char *errIn, char **errPos)
843 char *stoppedAt, *inPtr, *namePtr, *errMsg;
844 char subrName[MAX_SYM_LEN];
845 Program *prog;
846 Symbol *sym;
847 DataValue subrPtr;
848 Stack* progStack = (Stack*) XtMalloc(sizeof(Stack));
849 progStack->top = NULL;
850 progStack->size = 0;
852 inPtr = string;
853 while (*inPtr != '\0') {
855 /* skip over white space and comments */
856 while (*inPtr==' ' || *inPtr=='\t' || *inPtr=='\n'|| *inPtr=='#') {
857 if (*inPtr == '#')
858 while (*inPtr != '\n' && *inPtr != '\0') inPtr++;
859 else
860 inPtr++;
862 if (*inPtr == '\0')
863 break;
865 /* look for define keyword, and compile and store defined routines */
866 if (!strncmp(inPtr, "define", 6) && (inPtr[6]==' ' || inPtr[6]=='\t')) {
867 inPtr += 6;
868 inPtr += strspn(inPtr, " \t\n");
869 namePtr = subrName;
870 while ((namePtr < &subrName[MAX_SYM_LEN - 1])
871 && (isalnum((unsigned char)*inPtr) || *inPtr == '_')) {
872 *namePtr++ = *inPtr++;
874 *namePtr = '\0';
875 if (isalnum((unsigned char)*inPtr) || *inPtr == '_') {
876 return ParseError(dialogParent, string, inPtr, errIn,
877 "subroutine name too long");
879 inPtr += strspn(inPtr, " \t\n");
880 if (*inPtr != '{') {
881 if (errPos != NULL) *errPos = stoppedAt;
882 return ParseError(dialogParent, string, inPtr,
883 errIn, "expected '{'");
885 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
886 if (prog == NULL) {
887 if (errPos != NULL) *errPos = stoppedAt;
888 return ParseError(dialogParent, string, stoppedAt,
889 errIn, errMsg);
891 if (runWindow != NULL) {
892 sym = LookupSymbol(subrName);
893 if (sym == NULL) {
894 subrPtr.val.prog = prog;
895 subrPtr.tag = NO_TAG;
896 sym = InstallSymbol(subrName, MACRO_FUNCTION_SYM, subrPtr);
897 } else {
898 if (sym->type == MACRO_FUNCTION_SYM)
899 FreeProgram(sym->value.val.prog);
900 else
901 sym->type = MACRO_FUNCTION_SYM;
902 sym->value.val.prog = prog;
905 inPtr = stoppedAt;
907 /* Parse and execute immediate (outside of any define) macro commands
908 and WAIT for them to finish executing before proceeding. Note that
909 the code below is not perfect. If you interleave code blocks with
910 definitions in a file which is loaded from another macro file, it
911 will probably run the code blocks in reverse order! */
912 } else {
913 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
914 if (prog == NULL) {
915 if (errPos != NULL) {
916 *errPos = stoppedAt;
919 return ParseError(dialogParent, string, stoppedAt,
920 errIn, errMsg);
923 if (runWindow != NULL) {
924 XEvent nextEvent;
925 if (runWindow->macroCmdData == NULL) {
926 runMacro(runWindow, prog);
927 while (runWindow->macroCmdData != NULL) {
928 XtAppNextEvent(XtWidgetToApplicationContext(
929 runWindow->shell), &nextEvent);
930 ServerDispatchEvent(&nextEvent);
932 } else {
933 /* If we come here this means that the string was parsed
934 from within another macro via load_macro_file(). In
935 this case, plain code segments outside of define
936 blocks are rolled into one Program each and put on
937 the stack. At the end, the stack is unrolled, so the
938 plain Programs would be executed in the wrong order.
940 So we don't hand the Programs over to the interpreter
941 just yet (via RunMacroAsSubrCall()), but put it on a
942 stack of our own, reversing order once again. */
943 Push(progStack, (void*) prog);
946 inPtr = stoppedAt;
950 /* Unroll reversal stack for macros loaded from macros. */
951 while (NULL != (prog = (Program*) Pop(progStack))) {
952 RunMacroAsSubrCall(prog);
955 /* This stack is empty, so just free it without checking the members. */
956 XtFree((char*) progStack);
958 return True;
962 ** Run a pre-compiled macro, changing the interface state to reflect that
963 ** a macro is running, and handling preemption, resumption, and cancellation.
964 ** frees prog when macro execution is complete;
966 static void runMacro(WindowInfo *window, Program *prog)
968 DataValue result;
969 char *errMsg;
970 int stat;
971 macroCmdInfo *cmdData;
972 XmString s;
974 /* If a macro is already running, just call the program as a subroutine,
975 instead of starting a new one, so we don't have to keep a separate
976 context, and the macros will serialize themselves automatically */
977 if (window->macroCmdData != NULL) {
978 RunMacroAsSubrCall(prog);
979 return;
982 /* put up a watch cursor over the waiting window */
983 BeginWait(window->shell);
985 /* enable the cancel menu item */
986 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
987 s=XmStringCreateSimple("Cancel Macro"), NULL);
988 XmStringFree(s);
989 SetSensitive(window, window->cancelMacroItem, True);
991 /* Create a data structure for passing macro execution information around
992 amongst the callback routines which will process i/o and completion */
993 cmdData = (macroCmdInfo *)XtMalloc(sizeof(macroCmdInfo));
994 window->macroCmdData = cmdData;
995 cmdData->bannerIsUp = False;
996 cmdData->closeOnCompletion = False;
997 cmdData->program = prog;
998 cmdData->context = NULL;
999 cmdData->continueWorkProcID = 0;
1000 cmdData->dialog = NULL;
1002 /* Set up timer proc for putting up banner when macro takes too long */
1003 cmdData->bannerTimeoutID = XtAppAddTimeOut(
1004 XtWidgetToApplicationContext(window->shell), BANNER_WAIT_TIME,
1005 bannerTimeoutProc, window);
1007 /* Begin macro execution */
1008 stat = ExecuteMacro(window, prog, 0, NULL, &result, &cmdData->context,
1009 &errMsg);
1011 if (stat == MACRO_ERROR)
1013 finishMacroCmdExecution(window);
1014 DialogF(DF_ERR, window->shell, 1, "Macro Error",
1015 "Error executing macro: %s", "OK", errMsg);
1016 return;
1019 if (stat == MACRO_DONE) {
1020 finishMacroCmdExecution(window);
1021 return;
1023 if (stat == MACRO_TIME_LIMIT) {
1024 ResumeMacroExecution(window);
1025 return;
1027 /* (stat == MACRO_PREEMPT) Macro was preempted */
1031 ** Continue with macro execution after preemption. Called by the routines
1032 ** whose actions cause preemption when they have completed their lengthy tasks.
1033 ** Re-establishes macro execution work proc. Window must be the window in
1034 ** which the macro is executing (the window to which macroCmdData is attached),
1035 ** and not the window to which operations are focused.
1037 void ResumeMacroExecution(WindowInfo *window)
1039 macroCmdInfo *cmdData = (macroCmdInfo *)window->macroCmdData;
1041 if (cmdData != NULL)
1042 cmdData->continueWorkProcID = XtAppAddWorkProc(
1043 XtWidgetToApplicationContext(window->shell),
1044 continueWorkProc, window);
1048 ** Cancel the macro command in progress (user cancellation via GUI)
1050 void AbortMacroCommand(WindowInfo *window)
1052 if (window->macroCmdData == NULL)
1053 return;
1055 /* If there's both a macro and a shell command executing, the shell command
1056 must have been called from the macro. When called from a macro, shell
1057 commands don't put up cancellation controls of their own, but rely
1058 instead on the macro cancellation mechanism (here) */
1059 #ifndef VMS
1060 if (window->shellCmdData != NULL)
1061 AbortShellCommand(window);
1062 #endif
1064 /* Free the continuation */
1065 FreeRestartData(((macroCmdInfo *)window->macroCmdData)->context);
1067 /* Kill the macro command */
1068 finishMacroCmdExecution(window);
1072 ** Call this before closing a window, to clean up macro references to the
1073 ** window, stop any macro which might be running from it, free associated
1074 ** memory, and check that a macro is not attempting to close the window from
1075 ** which it is run. If this is being called from a macro, and the window
1076 ** this routine is examining is the window from which the macro was run, this
1077 ** routine will return False, and the caller must NOT CLOSE THE WINDOW.
1078 ** Instead, empty it and make it Untitled, and let the macro completion
1079 ** process close the window when the macro is finished executing.
1081 int MacroWindowCloseActions(WindowInfo *window)
1083 macroCmdInfo *mcd, *cmdData = window->macroCmdData;
1084 WindowInfo *w;
1086 if (MacroRecordActionHook != 0 && MacroRecordWindow == window) {
1087 FinishLearn();
1090 /* If no macro is executing in the window, allow the close, but check
1091 if macros executing in other windows have it as focus. If so, set
1092 their focus back to the window from which they were originally run */
1093 if (cmdData == NULL) {
1094 for (w=WindowList; w!=NULL; w=w->next) {
1095 mcd = (macroCmdInfo *)w->macroCmdData;
1096 if (w == MacroRunWindow() && MacroFocusWindow() == window)
1097 SetMacroFocusWindow(MacroRunWindow());
1098 else if (mcd != NULL && mcd->context->focusWindow == window)
1099 mcd->context->focusWindow = mcd->context->runWindow;
1101 return True;
1104 /* If the macro currently running (and therefore calling us, because
1105 execution must otherwise return to the main loop to execute any
1106 commands), is running in this window, tell the caller not to close,
1107 and schedule window close on completion of macro */
1108 if (window == MacroRunWindow()) {
1109 cmdData->closeOnCompletion = True;
1110 return False;
1113 /* Free the continuation */
1114 FreeRestartData(cmdData->context);
1116 /* Kill the macro command */
1117 finishMacroCmdExecution(window);
1118 return True;
1122 ** Clean up after the execution of a macro command: free memory, and restore
1123 ** the user interface state.
1125 static void finishMacroCmdExecution(WindowInfo *window)
1127 macroCmdInfo *cmdData = window->macroCmdData;
1128 int closeOnCompletion = cmdData->closeOnCompletion;
1129 XmString s;
1130 XClientMessageEvent event;
1132 /* Cancel pending timeout and work proc */
1133 if (cmdData->bannerTimeoutID != 0)
1134 XtRemoveTimeOut(cmdData->bannerTimeoutID);
1135 if (cmdData->continueWorkProcID != 0)
1136 XtRemoveWorkProc(cmdData->continueWorkProcID);
1138 /* Clean up waiting-for-macro-command-to-complete mode */
1139 EndWait(window->shell);
1140 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
1141 s=XmStringCreateSimple("Cancel Learn"), NULL);
1142 XmStringFree(s);
1143 SetSensitive(window, window->cancelMacroItem, False);
1144 if (cmdData->bannerIsUp)
1145 ClearModeMessage(window);
1147 /* If a dialog was up, get rid of it */
1148 if (cmdData->dialog != NULL)
1149 XtDestroyWidget(XtParent(cmdData->dialog));
1151 /* Free execution information */
1152 FreeProgram(cmdData->program);
1153 XtFree((char *)cmdData);
1154 window->macroCmdData = NULL;
1156 /* If macro closed its own window, window was made empty and untitled,
1157 but close was deferred until completion. This is completion, so if
1158 the window is still empty, do the close */
1159 if (closeOnCompletion && !window->filenameSet && !window->fileChanged) {
1160 CloseWindow(window);
1161 window = NULL;
1164 /* If no other macros are executing, do garbage collection */
1165 SafeGC();
1167 /* In processing the .neditmacro file (and possibly elsewhere), there
1168 is an event loop which waits for macro completion. Send an event
1169 to wake up that loop, otherwise execution will stall until the user
1170 does something to the window. */
1171 if (!closeOnCompletion) {
1172 event.format = 8;
1173 event.type = ClientMessage;
1174 XSendEvent(XtDisplay(window->shell), XtWindow(window->shell), False,
1175 NoEventMask, (XEvent *)&event);
1180 ** Do garbage collection of strings if there are no macros currently
1181 ** executing. NEdit's macro language GC strategy is to call this routine
1182 ** whenever a macro completes. If other macros are still running (preempted
1183 ** or waiting for a shell command or dialog), this does nothing and therefore
1184 ** defers GC to the completion of the last macro out.
1186 void SafeGC(void)
1188 WindowInfo *win;
1190 for (win=WindowList; win!=NULL; win=win->next)
1191 if (win->macroCmdData != NULL || InSmartIndentMacros(win))
1192 return;
1193 GarbageCollectStrings();
1197 ** Executes macro string "macro" using the lastFocus pane in "window".
1198 ** Reports errors via a dialog posted over "window", integrating the name
1199 ** "errInName" into the message to help identify the source of the error.
1201 void DoMacro(WindowInfo *window, const char *macro, const char *errInName)
1203 Program *prog;
1204 char *errMsg, *stoppedAt, *tMacro;
1205 int macroLen;
1207 /* Add a terminating newline (which command line users are likely to omit
1208 since they are typically invoking a single routine) */
1209 macroLen = strlen(macro);
1210 tMacro = XtMalloc(strlen(macro)+2);
1211 strncpy(tMacro, macro, macroLen);
1212 tMacro[macroLen] = '\n';
1213 tMacro[macroLen+1] = '\0';
1215 /* Parse the macro and report errors if it fails */
1216 prog = ParseMacro(tMacro, &errMsg, &stoppedAt);
1217 if (prog == NULL) {
1218 ParseError(window->shell, tMacro, stoppedAt, errInName, errMsg);
1219 XtFree(tMacro);
1220 return;
1222 XtFree(tMacro);
1224 /* run the executable program (prog is freed upon completion) */
1225 runMacro(window, prog);
1229 ** Get the current Learn/Replay macro in text form. Returned string is a
1230 ** pointer to the stored macro and should not be freed by the caller (and
1231 ** will cease to exist when the next replay macro is installed)
1233 char *GetReplayMacro(void)
1235 return ReplayMacro;
1239 ** Present the user a dialog for "Repeat" command
1241 void RepeatDialog(WindowInfo *window)
1243 Widget form, selBox, radioBox, timesForm;
1244 repeatDialog *rd;
1245 Arg selBoxArgs[1];
1246 char *lastCmdLabel, *parenChar;
1247 XmString s1;
1248 int cmdNameLen;
1250 if (LastCommand == NULL)
1252 DialogF(DF_WARN, window->shell, 1, "Repeat Macro",
1253 "No previous commands or learn/\nreplay sequences to repeat",
1254 "OK");
1255 return;
1258 /* Remeber the last command, since the user is allowed to work in the
1259 window while the dialog is up */
1260 rd = (repeatDialog *)XtMalloc(sizeof(repeatDialog));
1261 rd->lastCommand = XtNewString(LastCommand);
1263 /* make a label for the Last command item of the dialog, which includes
1264 the last executed action name */
1265 parenChar = strchr(LastCommand, '(');
1266 if (parenChar == NULL)
1267 return;
1268 cmdNameLen = parenChar-LastCommand;
1269 lastCmdLabel = XtMalloc(16 + cmdNameLen);
1270 strcpy(lastCmdLabel, "Last Command (");
1271 strncpy(&lastCmdLabel[14], LastCommand, cmdNameLen);
1272 strcpy(&lastCmdLabel[14 + cmdNameLen], ")");
1274 XtSetArg(selBoxArgs[0], XmNautoUnmanage, False);
1275 selBox = CreatePromptDialog(window->shell, "repeat", selBoxArgs, 1);
1276 rd->shell = XtParent(selBox);
1277 XtAddCallback(rd->shell, XmNdestroyCallback, repeatDestroyCB, rd);
1278 XtAddCallback(selBox, XmNokCallback, repeatOKCB, rd);
1279 XtAddCallback(selBox, XmNapplyCallback, repeatApplyCB, rd);
1280 XtAddCallback(selBox, XmNcancelCallback, repeatCancelCB, rd);
1281 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_TEXT));
1282 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_SELECTION_LABEL));
1283 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_HELP_BUTTON));
1284 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_APPLY_BUTTON));
1285 XtVaSetValues(XtParent(selBox), XmNtitle, "Repeat Macro", NULL);
1286 AddMotifCloseCallback(XtParent(selBox), repeatCancelCB, rd);
1288 form = XtVaCreateManagedWidget("form", xmFormWidgetClass, selBox, NULL);
1290 radioBox = XtVaCreateManagedWidget("cmdSrc", xmRowColumnWidgetClass, form,
1291 XmNradioBehavior, True,
1292 XmNorientation, XmHORIZONTAL,
1293 XmNpacking, XmPACK_TIGHT,
1294 XmNtopAttachment, XmATTACH_FORM,
1295 XmNleftAttachment, XmATTACH_FORM, NULL);
1296 rd->lastCmdToggle = XtVaCreateManagedWidget("lastCmdToggle",
1297 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1298 XmNlabelString, s1=XmStringCreateSimple(lastCmdLabel),
1299 XmNmnemonic, 'C', NULL);
1300 XmStringFree(s1);
1301 XtFree(lastCmdLabel);
1302 XtVaCreateManagedWidget("learnReplayToggle",
1303 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1304 XmNlabelString,
1305 s1=XmStringCreateSimple("Learn/Replay"),
1306 XmNmnemonic, 'L',
1307 XmNsensitive, ReplayMacro != NULL, NULL);
1308 XmStringFree(s1);
1310 timesForm = XtVaCreateManagedWidget("form", xmFormWidgetClass, form,
1311 XmNtopAttachment, XmATTACH_WIDGET,
1312 XmNtopWidget, radioBox,
1313 XmNtopOffset, 10,
1314 XmNleftAttachment, XmATTACH_FORM, NULL);
1315 radioBox = XtVaCreateManagedWidget("method", xmRowColumnWidgetClass,
1316 timesForm,
1317 XmNradioBehavior, True,
1318 XmNorientation, XmHORIZONTAL,
1319 XmNpacking, XmPACK_TIGHT,
1320 XmNtopAttachment, XmATTACH_FORM,
1321 XmNbottomAttachment, XmATTACH_FORM,
1322 XmNleftAttachment, XmATTACH_FORM, NULL);
1323 rd->inSelToggle = XtVaCreateManagedWidget("inSelToggle",
1324 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1325 XmNlabelString, s1=XmStringCreateSimple("In Selection"),
1326 XmNmnemonic, 'I', NULL);
1327 XmStringFree(s1);
1328 rd->toEndToggle = XtVaCreateManagedWidget("toEndToggle",
1329 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1330 XmNlabelString, s1=XmStringCreateSimple("To End"),
1331 XmNmnemonic, 'T', NULL);
1332 XmStringFree(s1);
1333 XtVaCreateManagedWidget("nTimesToggle",
1334 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1335 XmNlabelString, s1=XmStringCreateSimple("N Times"),
1336 XmNmnemonic, 'N',
1337 XmNset, True, NULL);
1338 XmStringFree(s1);
1339 rd->repeatText = XtVaCreateManagedWidget("repeatText", xmTextWidgetClass,
1340 timesForm,
1341 XmNcolumns, 5,
1342 XmNtopAttachment, XmATTACH_FORM,
1343 XmNbottomAttachment, XmATTACH_FORM,
1344 XmNleftAttachment, XmATTACH_WIDGET,
1345 XmNleftWidget, radioBox, NULL);
1346 RemapDeleteKey(rd->repeatText);
1348 /* Handle mnemonic selection of buttons and focus to dialog */
1349 AddDialogMnemonicHandler(form, FALSE);
1351 /* Set initial focus */
1352 #if XmVersion >= 1002
1353 XtVaSetValues(form, XmNinitialFocus, timesForm, NULL);
1354 XtVaSetValues(timesForm, XmNinitialFocus, rd->repeatText, NULL);
1355 #endif
1357 /* put up dialog */
1358 rd->forWindow = window;
1359 ManageDialogCenteredOnPointer(selBox);
1362 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData)
1364 repeatDialog *rd = (repeatDialog *)clientData;
1366 if (doRepeatDialogAction(rd, ((XmAnyCallbackStruct *)callData)->event))
1367 XtDestroyWidget(rd->shell);
1370 /* Note that the apply button is not managed in the repeat dialog. The dialog
1371 itself is capable of non-modal operation, but to be complete, it needs
1372 to dynamically update last command, dimming of learn/replay, possibly a
1373 stop button for the macro, and possibly in-selection with selection */
1374 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData)
1376 doRepeatDialogAction((repeatDialog *)clientData,
1377 ((XmAnyCallbackStruct *)callData)->event);
1380 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event)
1382 int nTimes;
1383 char nTimesStr[TYPE_INT_STR_SIZE(int)];
1384 char *params[2];
1386 /* Find out from the dialog how to repeat the command */
1387 if (XmToggleButtonGetState(rd->inSelToggle))
1389 if (!rd->forWindow->buffer->primary.selected)
1391 DialogF(DF_WARN, rd->shell, 1, "Repeat Macro",
1392 "No selection in window to repeat within", "OK");
1393 XmProcessTraversal(rd->inSelToggle, XmTRAVERSE_CURRENT);
1394 return False;
1396 params[0] = "in_selection";
1397 } else if (XmToggleButtonGetState(rd->toEndToggle))
1399 params[0] = "to_end";
1400 } else
1402 if (GetIntTextWarn(rd->repeatText, &nTimes, "number of times", True)
1403 != TEXT_READ_OK)
1405 XmProcessTraversal(rd->repeatText, XmTRAVERSE_CURRENT);
1406 return False;
1408 sprintf(nTimesStr, "%d", nTimes);
1409 params[0] = nTimesStr;
1412 /* Figure out which command user wants to repeat */
1413 if (XmToggleButtonGetState(rd->lastCmdToggle))
1414 params[1] = XtNewString(rd->lastCommand);
1415 else {
1416 if (ReplayMacro == NULL)
1417 return False;
1418 params[1] = XtNewString(ReplayMacro);
1421 /* call the action routine repeat_macro to do the work */
1422 XtCallActionProc(rd->forWindow->lastFocus, "repeat_macro", event, params,2);
1423 XtFree(params[1]);
1424 return True;
1427 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData)
1429 repeatDialog *rd = (repeatDialog *)clientData;
1431 XtDestroyWidget(rd->shell);
1434 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1436 repeatDialog *rd = (repeatDialog *)clientData;
1438 XtFree(rd->lastCommand);
1439 XtFree((char *)rd);
1443 ** Dispatches a macro to which repeats macro command in "command", either
1444 ** an integer number of times ("how" == positive integer), or within a
1445 ** selected range ("how" == REPEAT_IN_SEL), or to the end of the window
1446 ** ("how == REPEAT_TO_END).
1448 ** Note that as with most macro routines, this returns BEFORE the macro is
1449 ** finished executing
1451 void RepeatMacro(WindowInfo *window, const char *command, int how)
1453 Program *prog;
1454 char *errMsg, *stoppedAt, *loopMacro, *loopedCmd;
1456 if (command == NULL)
1457 return;
1459 /* Wrap a for loop and counter/tests around the command */
1460 if (how == REPEAT_TO_END)
1461 loopMacro = "lastCursor=-1\nstartPos=$cursor\n\
1462 while($cursor>=startPos&&$cursor!=lastCursor){\nlastCursor=$cursor\n%s\n}\n";
1463 else if (how == REPEAT_IN_SEL)
1464 loopMacro = "selStart = $selection_start\nif (selStart == -1)\nreturn\n\
1465 selEnd = $selection_end\nset_cursor_pos(selStart)\nselect(0,0)\n\
1466 boundText = get_range(selEnd, selEnd+10)\n\
1467 while($cursor >= selStart && $cursor < selEnd && \\\n\
1468 get_range(selEnd, selEnd+10) == boundText) {\n\
1469 startLength = $text_length\n%s\n\
1470 selEnd += $text_length - startLength\n}\n";
1471 else
1472 loopMacro = "for(i=0;i<%d;i++){\n%s\n}\n";
1473 loopedCmd = XtMalloc(strlen(command) + strlen(loopMacro) + 25);
1474 if (how == REPEAT_TO_END || how == REPEAT_IN_SEL)
1475 sprintf(loopedCmd, loopMacro, command);
1476 else
1477 sprintf(loopedCmd, loopMacro, how, command);
1479 /* Parse the resulting macro into an executable program "prog" */
1480 prog = ParseMacro(loopedCmd, &errMsg, &stoppedAt);
1481 if (prog == NULL) {
1482 fprintf(stderr, "NEdit internal error, repeat macro syntax wrong: %s\n",
1483 errMsg);
1484 return;
1486 XtFree(loopedCmd);
1488 /* run the executable program */
1489 runMacro(window, prog);
1493 ** Macro recording action hook for Learn/Replay, added temporarily during
1494 ** learn.
1496 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
1497 XEvent *event, String *params, Cardinal *numParams)
1499 WindowInfo *window;
1500 int i;
1501 char *actionString;
1503 /* Select only actions in text panes in the window for which this
1504 action hook is recording macros (from clientData). */
1505 for (window=WindowList; window!=NULL; window=window->next) {
1506 if (window->textArea == w)
1507 break;
1508 for (i=0; i<window->nPanes; i++) {
1509 if (window->textPanes[i] == w)
1510 break;
1512 if (i < window->nPanes)
1513 break;
1515 if (window == NULL || window != (WindowInfo *)clientData)
1516 return;
1518 /* beep on un-recordable operations which require a mouse position, to
1519 remind the user that the action was not recorded */
1520 if (isMouseAction(actionName)) {
1521 XBell(XtDisplay(w), 0);
1522 return;
1525 /* Record the action and its parameters */
1526 actionString = actionToString(w, actionName, event, params, *numParams);
1527 if (actionString != NULL) {
1528 BufInsert(MacroRecordBuf, MacroRecordBuf->length, actionString);
1529 XtFree(actionString);
1534 ** Permanent action hook for remembering last action for possible replay
1536 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
1537 XEvent *event, String *params, Cardinal *numParams)
1539 WindowInfo *window;
1540 int i;
1541 char *actionString;
1543 /* Find the window to which this action belongs */
1544 for (window=WindowList; window!=NULL; window=window->next) {
1545 if (window->textArea == w)
1546 break;
1547 for (i=0; i<window->nPanes; i++) {
1548 if (window->textPanes[i] == w)
1549 break;
1551 if (i < window->nPanes)
1552 break;
1554 if (window == NULL)
1555 return;
1557 /* The last action is recorded for the benefit of repeating the last
1558 action. Don't record repeat_macro and wipe out the real action */
1559 if (!strcmp(actionName, "repeat_macro"))
1560 return;
1562 /* Record the action and its parameters */
1563 actionString = actionToString(w, actionName, event, params, *numParams);
1564 if (actionString != NULL) {
1565 XtFree(LastCommand);
1566 LastCommand = actionString;
1571 ** Create a macro string to represent an invocation of an action routine.
1572 ** Returns NULL for non-operational or un-recordable actions.
1574 static char *actionToString(Widget w, char *actionName, XEvent *event,
1575 String *params, Cardinal numParams)
1577 char chars[20], *charList[1], *outStr, *outPtr;
1578 KeySym keysym;
1579 int i, nChars, nParams, length, nameLength;
1580 #ifndef NO_XMIM
1581 int status;
1582 #endif
1584 if (isIgnoredAction(actionName) || isRedundantAction(actionName) ||
1585 isMouseAction(actionName))
1586 return NULL;
1588 /* Convert self_insert actions, to insert_string */
1589 if (!strcmp(actionName, "self_insert") ||
1590 !strcmp(actionName, "self-insert")) {
1591 actionName = "insert_string";
1592 #ifdef NO_XMIM
1593 nChars = XLookupString((XKeyEvent *)event, chars, 19, &keysym, NULL);
1594 if (nChars == 0)
1595 return NULL;
1596 #else
1598 nChars = XmImMbLookupString(w, (XKeyEvent *)event,
1599 chars, 19, &keysym, &status);
1600 if (nChars == 0 || status == XLookupNone ||
1601 status == XLookupKeySym || status == XBufferOverflow)
1602 return NULL;
1603 #endif
1604 chars[nChars] = '\0';
1605 charList[0] = chars;
1606 params = charList;
1607 nParams = 1;
1608 } else
1609 nParams = numParams;
1611 /* Figure out the length of string required */
1612 nameLength = strlen(actionName);
1613 length = nameLength + 3;
1614 for (i=0; i<nParams; i++)
1615 length += escapedStringLength(params[i]) + 4;
1617 /* Allocate the string and copy the information to it */
1618 outPtr = outStr = XtMalloc(length + 1);
1619 strcpy(outPtr, actionName);
1620 outPtr += nameLength;
1621 *outPtr++ = '(';
1622 for (i=0; i<nParams; i++) {
1623 *outPtr++ = '\"';
1624 outPtr += escapeStringChars(params[i], outPtr);
1625 *outPtr++ = '\"'; *outPtr++ = ','; *outPtr++ = ' ';
1627 if (nParams != 0)
1628 outPtr -= 2;
1629 *outPtr++ = ')'; *outPtr++ = '\n'; *outPtr++ = '\0';
1630 return outStr;
1633 static int isMouseAction(const char *action)
1635 int i;
1637 for (i=0; i<(int)XtNumber(MouseActions); i++)
1638 if (!strcmp(action, MouseActions[i]))
1639 return True;
1640 return False;
1643 static int isRedundantAction(const char *action)
1645 int i;
1647 for (i=0; i<(int)XtNumber(RedundantActions); i++)
1648 if (!strcmp(action, RedundantActions[i]))
1649 return True;
1650 return False;
1653 static int isIgnoredAction(const char *action)
1655 int i;
1657 for (i=0; i<(int)XtNumber(IgnoredActions); i++)
1658 if (!strcmp(action, IgnoredActions[i]))
1659 return True;
1660 return False;
1664 ** Timer proc for putting up the "Macro Command in Progress" banner if
1665 ** the process is taking too long.
1667 #define MAX_TIMEOUT_MSG_LEN (MAX_ACCEL_LEN + 60)
1668 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id)
1670 WindowInfo *window = (WindowInfo *)clientData;
1671 macroCmdInfo *cmdData = window->macroCmdData;
1672 XmString xmCancel;
1673 char *cCancel = "\0";
1674 char message[MAX_TIMEOUT_MSG_LEN];
1676 cmdData->bannerIsUp = True;
1678 /* Extract accelerator text from menu PushButtons */
1679 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
1681 if (!XmStringEmpty(xmCancel))
1683 /* Translate Motif string to char* */
1684 cCancel = GetXmStringText(xmCancel);
1686 /* Free Motif String */
1687 XmStringFree(xmCancel);
1690 /* Create message */
1691 if (cCancel[0] == '\0') {
1692 strncpy(message, "Macro Command in Progress", MAX_TIMEOUT_MSG_LEN);
1693 message[MAX_TIMEOUT_MSG_LEN - 1] = '\0';
1695 else {
1696 sprintf(message,
1697 "Macro Command in Progress -- Press %s to Cancel",
1698 cCancel);
1701 /* Free C-string */
1702 XtFree(cCancel);
1704 SetModeMessage(window, message);
1705 cmdData->bannerTimeoutID = 0;
1709 ** Work proc for continuing execution of a preempted macro.
1711 ** Xt WorkProcs are designed to run first-in first-out, which makes them
1712 ** very bad at sharing time between competing tasks. For this reason, it's
1713 ** usually bad to use work procs anywhere where their execution is likely to
1714 ** overlap. Using a work proc instead of a timer proc (which I usually
1715 ** prefer) here means macros will probably share time badly, but we're more
1716 ** interested in making the macros cancelable, and in continuing other work
1717 ** than having users run a bunch of them at once together.
1719 static Boolean continueWorkProc(XtPointer clientData)
1721 WindowInfo *window = (WindowInfo *)clientData;
1722 macroCmdInfo *cmdData = window->macroCmdData;
1723 char *errMsg;
1724 int stat;
1725 DataValue result;
1727 stat = ContinueMacro(cmdData->context, &result, &errMsg);
1728 if (stat == MACRO_ERROR)
1730 finishMacroCmdExecution(window);
1731 DialogF(DF_ERR, window->shell, 1, "Macro Error",
1732 "Error executing macro: %s", "OK", errMsg);
1733 return True;
1734 } else if (stat == MACRO_DONE)
1736 finishMacroCmdExecution(window);
1737 return True;
1738 } else if (stat == MACRO_PREEMPT)
1740 cmdData->continueWorkProcID = 0;
1741 return True;
1744 /* Macro exceeded time slice, re-schedule it */
1745 if (stat != MACRO_TIME_LIMIT)
1746 return True; /* shouldn't happen */
1747 return False;
1751 ** Copy fromString to toString replacing special characters in strings, such
1752 ** that they can be read back by the macro parser's string reader. i.e. double
1753 ** quotes are replaced by \", backslashes are replaced with \\, C-std control
1754 ** characters like \n are replaced with their backslash counterparts. This
1755 ** routine should be kept reasonably in sync with yylex in parse.y. Companion
1756 ** routine escapedStringLength predicts the length needed to write the string
1757 ** when it is expanded with the additional characters. Returns the number
1758 ** of characters to which the string expanded.
1760 static int escapeStringChars(char *fromString, char *toString)
1762 char *e, *c, *outPtr = toString;
1764 /* substitute escape sequences */
1765 for (c=fromString; *c!='\0'; c++) {
1766 for (e=EscapeChars; *e!='\0'; e++) {
1767 if (*c == *e) {
1768 *outPtr++ = '\\';
1769 *outPtr++ = ReplaceChars[e-EscapeChars];
1770 break;
1773 if (*e == '\0')
1774 *outPtr++ = *c;
1776 *outPtr = '\0';
1777 return outPtr - toString;
1781 ** Predict the length of a string needed to hold a copy of "string" with
1782 ** special characters replaced with escape sequences by escapeStringChars.
1784 static int escapedStringLength(char *string)
1786 char *c, *e;
1787 int length = 0;
1789 /* calculate length and allocate returned string */
1790 for (c=string; *c!='\0'; c++) {
1791 for (e=EscapeChars; *e!='\0'; e++) {
1792 if (*c == *e) {
1793 length++;
1794 break;
1797 length++;
1799 return length;
1803 ** Built-in macro subroutine for getting the length of a string
1805 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
1806 DataValue *result, char **errMsg)
1808 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
1810 if (nArgs != 1)
1811 return wrongNArgsErr(errMsg);
1812 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1813 return False;
1814 result->tag = INT_TAG;
1815 result->val.n = strlen(string);
1816 return True;
1820 ** Built-in macro subroutines for min and max
1822 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
1823 DataValue *result, char **errMsg)
1825 int minVal, value, i;
1827 if (nArgs == 1)
1828 return tooFewArgsErr(errMsg);
1829 if (!readIntArg(argList[0], &minVal, errMsg))
1830 return False;
1831 for (i=0; i<nArgs; i++) {
1832 if (!readIntArg(argList[i], &value, errMsg))
1833 return False;
1834 minVal = value < minVal ? value : minVal;
1836 result->tag = INT_TAG;
1837 result->val.n = minVal;
1838 return True;
1840 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
1841 DataValue *result, char **errMsg)
1843 int maxVal, value, i;
1845 if (nArgs == 1)
1846 return tooFewArgsErr(errMsg);
1847 if (!readIntArg(argList[0], &maxVal, errMsg))
1848 return False;
1849 for (i=0; i<nArgs; i++) {
1850 if (!readIntArg(argList[i], &value, errMsg))
1851 return False;
1852 maxVal = value > maxVal ? value : maxVal;
1854 result->tag = INT_TAG;
1855 result->val.n = maxVal;
1856 return True;
1859 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
1860 DataValue *result, char **errMsg)
1862 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1863 WindowInfo *w;
1864 char fullname[MAXPATHLEN];
1865 char normalizedString[MAXPATHLEN];
1867 /* Read the argument representing the window to focus to, and translate
1868 it into a pointer to a real WindowInfo */
1869 if (nArgs != 1)
1870 return wrongNArgsErr(errMsg);
1872 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
1873 return False;
1874 } else if (!strcmp(string, "last")) {
1875 w = WindowList;
1876 } else if (!strcmp(string, "next")) {
1877 w = window->next;
1878 } else if (strlen(string) >= MAXPATHLEN) {
1879 *errMsg = "Pathname too long in focus_window()";
1880 return False;
1881 } else {
1882 /* just use the plain name as supplied */
1883 for (w=WindowList; w != NULL; w = w->next) {
1884 sprintf(fullname, "%s%s", w->path, w->filename);
1885 if (!strcmp(string, fullname)) {
1886 break;
1889 /* didn't work? try normalizing the string passed in */
1890 if (w == NULL) {
1891 strncpy(normalizedString, string, MAXPATHLEN);
1892 normalizedString[MAXPATHLEN-1] = '\0';
1893 if (1 == NormalizePathname(normalizedString)) {
1894 /* Something is broken with the input pathname. */
1895 *errMsg = "Pathname too long in focus_window()";
1896 return False;
1898 for (w=WindowList; w != NULL; w = w->next) {
1899 sprintf(fullname, "%s%s", w->path, w->filename);
1900 if (!strcmp(normalizedString, fullname))
1901 break;
1906 /* If no matching window was found, return empty string and do nothing */
1907 if (w == NULL) {
1908 result->tag = STRING_TAG;
1909 result->val.str.rep = PERM_ALLOC_STR("");
1910 result->val.str.len = 0;
1911 return True;
1914 /* Change the focused window to the requested one */
1915 SetMacroFocusWindow(w);
1917 /* turn on syntax highlight that might have been deferred */
1918 if (w->highlightSyntax && w->highlightData==NULL)
1919 StartHighlighting(w, False);
1921 /* Return the name of the window */
1922 result->tag = STRING_TAG;
1923 AllocNString(&result->val.str, strlen(w->path)+strlen(w->filename)+1);
1924 sprintf(result->val.str.rep, "%s%s", w->path, w->filename);
1925 return True;
1929 ** Built-in macro subroutine for getting text from the current window's text
1930 ** buffer
1932 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1933 DataValue *result, char **errMsg)
1935 int from, to;
1936 textBuffer *buf = window->buffer;
1937 char *rangeText;
1939 /* Validate arguments and convert to int */
1940 if (nArgs != 2)
1941 return wrongNArgsErr(errMsg);
1942 if (!readIntArg(argList[0], &from, errMsg))
1943 return False;
1944 if (!readIntArg(argList[1], &to, errMsg))
1945 return False;
1946 if (from < 0) from = 0;
1947 if (from > buf->length) from = buf->length;
1948 if (to < 0) to = 0;
1949 if (to > buf->length) to = buf->length;
1950 if (from > to) {int temp = from; from = to; to = temp;}
1952 /* Copy text from buffer (this extra copy could be avoided if textBuf.c
1953 provided a routine for writing into a pre-allocated string) */
1954 result->tag = STRING_TAG;
1955 AllocNString(&result->val.str, to - from + 1);
1956 rangeText = BufGetRange(buf, from, to);
1957 BufUnsubstituteNullChars(rangeText, buf);
1958 strcpy(result->val.str.rep, rangeText);
1959 /* Note: after the un-substitution, it is possible that strlen() != len,
1960 but that's because strlen() can't deal with 0-characters. */
1961 XtFree(rangeText);
1962 return True;
1966 ** Built-in macro subroutine for getting a single character at the position
1967 ** given, from the current window
1969 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
1970 DataValue *result, char **errMsg)
1972 int pos;
1973 textBuffer *buf = window->buffer;
1975 /* Validate argument and convert it to int */
1976 if (nArgs != 1)
1977 return wrongNArgsErr(errMsg);
1978 if (!readIntArg(argList[0], &pos, errMsg))
1979 return False;
1980 if (pos < 0) pos = 0;
1981 if (pos > buf->length) pos = buf->length;
1983 /* Return the character in a pre-allocated string) */
1984 result->tag = STRING_TAG;
1985 AllocNString(&result->val.str, 2);
1986 result->val.str.rep[0] = BufGetCharacter(buf, pos);
1987 BufUnsubstituteNullChars(result->val.str.rep, buf);
1988 /* Note: after the un-substitution, it is possible that strlen() != len,
1989 but that's because strlen() can't deal with 0-characters. */
1990 return True;
1994 ** Built-in macro subroutine for replacing text in the current window's text
1995 ** buffer
1997 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1998 DataValue *result, char **errMsg)
2000 int from, to;
2001 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2002 textBuffer *buf = window->buffer;
2004 /* Validate arguments and convert to int */
2005 if (nArgs != 3)
2006 return wrongNArgsErr(errMsg);
2007 if (!readIntArg(argList[0], &from, errMsg))
2008 return False;
2009 if (!readIntArg(argList[1], &to, errMsg))
2010 return False;
2011 if (!readStringArg(argList[2], &string, stringStorage, errMsg))
2012 return False;
2013 if (from < 0) from = 0;
2014 if (from > buf->length) from = buf->length;
2015 if (to < 0) to = 0;
2016 if (to > buf->length) to = buf->length;
2017 if (from > to) {int temp = from; from = to; to = temp;}
2019 /* Don't allow modifications if the window is read-only */
2020 if (IS_ANY_LOCKED(window->lockReasons)) {
2021 XBell(XtDisplay(window->shell), 0);
2022 result->tag = NO_TAG;
2023 return True;
2026 /* There are no null characters in the string (because macro strings
2027 still have null termination), but if the string contains the
2028 character used by the buffer for null substitution, it could
2029 theoretically become a null. In the highly unlikely event that
2030 all of the possible substitution characters in the buffer are used
2031 up, stop the macro and tell the user of the failure */
2032 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
2033 *errMsg = "Too much binary data in file";
2034 return False;
2037 /* Do the replace */
2038 BufReplace(buf, from, to, string);
2039 result->tag = NO_TAG;
2040 return True;
2044 ** Built-in macro subroutine for replacing the primary-selection selected
2045 ** text in the current window's text buffer
2047 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2048 DataValue *result, char **errMsg)
2050 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2052 /* Validate argument and convert to string */
2053 if (nArgs != 1)
2054 return wrongNArgsErr(errMsg);
2055 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2056 return False;
2058 /* Don't allow modifications if the window is read-only */
2059 if (IS_ANY_LOCKED(window->lockReasons)) {
2060 XBell(XtDisplay(window->shell), 0);
2061 result->tag = NO_TAG;
2062 return True;
2065 /* There are no null characters in the string (because macro strings
2066 still have null termination), but if the string contains the
2067 character used by the buffer for null substitution, it could
2068 theoretically become a null. In the highly unlikely event that
2069 all of the possible substitution characters in the buffer are used
2070 up, stop the macro and tell the user of the failure */
2071 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
2072 *errMsg = "Too much binary data in file";
2073 return False;
2076 /* Do the replace */
2077 BufReplaceSelected(window->buffer, string);
2078 result->tag = NO_TAG;
2079 return True;
2083 ** Built-in macro subroutine for getting the text currently selected by
2084 ** the primary selection in the current window's text buffer, or in any
2085 ** part of screen if "any" argument is given
2087 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2088 DataValue *result, char **errMsg)
2090 char *selText;
2092 /* Read argument list to check for "any" keyword, and get the appropriate
2093 selection */
2094 if (nArgs != 0 && nArgs != 1)
2095 return wrongNArgsErr(errMsg);
2096 if (nArgs == 1) {
2097 if (argList[0].tag != STRING_TAG || strcmp(argList[0].val.str.rep, "any")) {
2098 *errMsg = "Unrecognized argument to %s";
2099 return False;
2101 selText = GetAnySelection(window);
2102 if (selText == NULL)
2103 selText = XtNewString("");
2104 } else {
2105 selText = BufGetSelectionText(window->buffer);
2106 BufUnsubstituteNullChars(selText, window->buffer);
2109 /* Return the text as an allocated string */
2110 result->tag = STRING_TAG;
2111 AllocNStringCpy(&result->val.str, selText);
2112 XtFree(selText);
2113 return True;
2117 ** Built-in macro subroutine for determining if implicit conversion of
2118 ** a string to number will succeed or fail
2120 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
2121 DataValue *result, char **errMsg)
2123 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
2125 if (nArgs != 1) {
2126 return wrongNArgsErr(errMsg);
2128 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
2129 return False;
2132 result->tag = INT_TAG;
2133 result->val.n = StringToNum(string, NULL);
2135 return True;
2139 ** Built-in macro subroutine for replacing a substring within another string
2141 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
2142 DataValue *result, char **errMsg)
2144 int from, to, length, replaceLen, outLen;
2145 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *replStr;
2147 /* Validate arguments and convert to int */
2148 if (nArgs != 4)
2149 return wrongNArgsErr(errMsg);
2150 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2151 return False;
2152 if (!readIntArg(argList[1], &from, errMsg))
2153 return False;
2154 if (!readIntArg(argList[2], &to, errMsg))
2155 return False;
2156 if (!readStringArg(argList[3], &replStr, stringStorage[1], errMsg))
2157 return False;
2158 length = strlen(string);
2159 if (from < 0) from = 0;
2160 if (from > length) from = length;
2161 if (to < 0) to = 0;
2162 if (to > length) to = length;
2163 if (from > to) {int temp = from; from = to; to = temp;}
2165 /* Allocate a new string and do the replacement */
2166 replaceLen = strlen(replStr);
2167 outLen = length - (to - from) + replaceLen;
2168 result->tag = STRING_TAG;
2169 AllocNString(&result->val.str, outLen+1);
2170 strncpy(result->val.str.rep, string, from);
2171 strncpy(&result->val.str.rep[from], replStr, replaceLen);
2172 strncpy(&result->val.str.rep[from + replaceLen], &string[to], length - to);
2173 return True;
2177 ** Built-in macro subroutine for getting a substring of a string.
2178 ** Called as substring(string, from [, to])
2180 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
2181 DataValue *result, char **errMsg)
2183 int from, to, length;
2184 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2186 /* Validate arguments and convert to int */
2187 if (nArgs != 2 && nArgs != 3)
2188 return wrongNArgsErr(errMsg);
2189 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2190 return False;
2191 if (!readIntArg(argList[1], &from, errMsg))
2192 return False;
2193 length = to = strlen(string);
2194 if (nArgs == 3)
2195 if (!readIntArg(argList[2], &to, errMsg))
2196 return False;
2197 if (from < 0) from += length;
2198 if (from < 0) from = 0;
2199 if (from > length) from = length;
2200 if (to < 0) to += length;
2201 if (to < 0) to = 0;
2202 if (to > length) to = length;
2203 if (from > to) to = from;
2205 /* Allocate a new string and copy the sub-string into it */
2206 result->tag = STRING_TAG;
2207 AllocNStringNCpy(&result->val.str, &string[from], to - from);
2208 return True;
2211 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
2212 DataValue *result, char **errMsg)
2214 int i, length;
2215 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2217 /* Validate arguments and convert to int */
2218 if (nArgs != 1)
2219 return wrongNArgsErr(errMsg);
2220 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2221 return False;
2222 length = strlen(string);
2224 /* Allocate a new string and copy an uppercased version of the string it */
2225 result->tag = STRING_TAG;
2226 AllocNString(&result->val.str, length + 1);
2227 for (i=0; i<length; i++)
2228 result->val.str.rep[i] = toupper((unsigned char)string[i]);
2229 return True;
2232 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
2233 DataValue *result, char **errMsg)
2235 int i, length;
2236 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2238 /* Validate arguments and convert to int */
2239 if (nArgs != 1)
2240 return wrongNArgsErr(errMsg);
2241 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2242 return False;
2243 length = strlen(string);
2245 /* Allocate a new string and copy an lowercased version of the string it */
2246 result->tag = STRING_TAG;
2247 AllocNString(&result->val.str, length + 1);
2248 for (i=0; i<length; i++)
2249 result->val.str.rep[i] = tolower((unsigned char)string[i]);
2250 return True;
2253 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
2254 DataValue *result, char **errMsg)
2256 long itemID = 0;
2257 XmString s;
2258 int stat;
2259 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2261 /* Get the string argument */
2262 if (nArgs != 1)
2263 return wrongNArgsErr(errMsg);
2264 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2265 return False;
2267 /* Use the XmClipboard routines to copy the text to the clipboard.
2268 If errors occur, just give up. */
2269 result->tag = NO_TAG;
2270 stat = SpinClipboardStartCopy(TheDisplay, XtWindow(window->textArea),
2271 s=XmStringCreateSimple("NEdit"), XtLastTimestampProcessed(TheDisplay),
2272 window->textArea, NULL, &itemID);
2273 XmStringFree(s);
2274 if (stat != ClipboardSuccess)
2275 return True;
2276 if (SpinClipboardCopy(TheDisplay, XtWindow(window->textArea), itemID, "STRING",
2277 string, strlen(string), 0, NULL) != ClipboardSuccess) {
2278 SpinClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2279 return True;
2281 SpinClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2282 return True;
2285 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2286 DataValue *result, char **errMsg)
2288 unsigned long length, retLength;
2289 long id = 0;
2291 /* Should have no arguments */
2292 if (nArgs != 0)
2293 return wrongNArgsErr(errMsg);
2295 /* Ask if there's a string in the clipboard, and get its length */
2296 if (SpinClipboardInquireLength(TheDisplay, XtWindow(window->shell), "STRING",
2297 &length) != ClipboardSuccess) {
2298 result->tag = STRING_TAG;
2299 result->val.str.rep = PERM_ALLOC_STR("");
2300 result->val.str.len = 0;
2302 * Possibly, the clipboard can remain in a locked state after
2303 * a failure, so we try to remove the lock, just to be sure.
2305 SpinClipboardUnlock(TheDisplay, XtWindow(window->shell));
2306 return True;
2309 /* Allocate a new string to hold the data */
2310 result->tag = STRING_TAG;
2311 AllocNString(&result->val.str, (int)length + 1);
2313 /* Copy the clipboard contents to the string */
2314 if (SpinClipboardRetrieve(TheDisplay, XtWindow(window->shell), "STRING",
2315 result->val.str.rep, length, &retLength, &id) != ClipboardSuccess) {
2316 retLength = 0;
2318 * Possibly, the clipboard can remain in a locked state after
2319 * a failure, so we try to remove the lock, just to be sure.
2321 SpinClipboardUnlock(TheDisplay, XtWindow(window->shell));
2323 result->val.str.rep[retLength] = '\0';
2324 result->val.str.len = retLength;
2326 return True;
2331 ** Built-in macro subroutine for reading the contents of a text file into
2332 ** a string. On success, returns 1 in $readStatus, and the contents of the
2333 ** file as a string in the subroutine return value. On failure, returns
2334 ** the empty string "" and an 0 $readStatus.
2336 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2337 DataValue *result, char **errMsg)
2339 char stringStorage[TYPE_INT_STR_SIZE(int)], *name;
2340 struct stat statbuf;
2341 FILE *fp;
2342 int readLen;
2344 /* Validate arguments and convert to int */
2345 if (nArgs != 1)
2346 return wrongNArgsErr(errMsg);
2347 if (!readStringArg(argList[0], &name, stringStorage, errMsg))
2348 return False;
2350 /* Read the whole file into an allocated string */
2351 if ((fp = fopen(name, "r")) == NULL)
2352 goto errorNoClose;
2353 if (fstat(fileno(fp), &statbuf) != 0)
2354 goto error;
2355 result->tag = STRING_TAG;
2356 AllocNString(&result->val.str, statbuf.st_size+1);
2357 readLen = fread(result->val.str.rep, sizeof(char), statbuf.st_size+1, fp);
2358 if (ferror(fp))
2359 goto error;
2360 if(!feof(fp)){
2361 /* Couldn't trust file size. Use slower but more general method */
2362 int chunkSize = 1024;
2363 char *buffer;
2365 buffer = XtMalloc(readLen * sizeof(char));
2366 memcpy(buffer, result->val.str.rep, readLen * sizeof(char));
2367 while (!feof(fp)){
2368 buffer = XtRealloc(buffer, (readLen+chunkSize)*sizeof(char));
2369 readLen += fread(&buffer[readLen], sizeof(char), chunkSize, fp);
2370 if (ferror(fp)){
2371 XtFree(buffer);
2372 goto error;
2375 AllocNString(&result->val.str, readLen + 1);
2376 memcpy(result->val.str.rep, buffer, readLen * sizeof(char));
2377 XtFree(buffer);
2379 fclose(fp);
2381 /* Return the results */
2382 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2383 ReturnGlobals[READ_STATUS]->value.val.n = True;
2384 return True;
2386 error:
2387 fclose(fp);
2389 errorNoClose:
2390 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2391 ReturnGlobals[READ_STATUS]->value.val.n = False;
2392 result->tag = STRING_TAG;
2393 result->val.str.rep = PERM_ALLOC_STR("");
2394 result->val.str.len = 0;
2395 return True;
2399 ** Built-in macro subroutines for writing or appending a string (parameter $1)
2400 ** to a file named in parameter $2. Returns 1 on successful write, or 0 if
2401 ** unsuccessful.
2403 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2404 DataValue *result, char **errMsg)
2406 return writeOrAppendFile(False, window, argList, nArgs, result, errMsg);
2409 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2410 DataValue *result, char **errMsg)
2412 return writeOrAppendFile(True, window, argList, nArgs, result, errMsg);
2415 static int writeOrAppendFile(int append, WindowInfo *window,
2416 DataValue *argList, int nArgs, DataValue *result, char **errMsg)
2418 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *name, *string;
2419 FILE *fp;
2421 /* Validate argument */
2422 if (nArgs != 2)
2423 return wrongNArgsErr(errMsg);
2424 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2425 return False;
2426 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg))
2427 return False;
2429 /* open the file */
2430 if ((fp = fopen(name, append ? "a" : "w")) == NULL) {
2431 result->tag = INT_TAG;
2432 result->val.n = False;
2433 return True;
2436 /* write the string to the file */
2437 fwrite(string, sizeof(char), strlen(string), fp);
2438 if (ferror(fp)) {
2439 fclose(fp);
2440 result->tag = INT_TAG;
2441 result->val.n = False;
2442 return True;
2444 fclose(fp);
2446 /* return the status */
2447 result->tag = INT_TAG;
2448 result->val.n = True;
2449 return True;
2453 ** Built-in macro subroutine for searching silently in a window without
2454 ** dialogs, beeps, or changes to the selection. Arguments are: $1: string to
2455 ** search for, $2: starting position. Optional arguments may include the
2456 ** strings: "wrap" to make the search wrap around the beginning or end of the
2457 ** string, "backward" or "forward" to change the search direction ("forward" is
2458 ** the default), "literal", "case" or "regex" to change the search type
2459 ** (default is "literal").
2461 ** Returns the starting position of the match, or -1 if nothing matched.
2462 ** also returns the ending position of the match in $searchEndPos
2464 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
2465 DataValue *result, char **errMsg)
2467 DataValue newArgList[9];
2469 /* Use the search string routine, by adding the buffer contents as
2470 the string argument */
2471 if (nArgs > 8)
2472 return wrongNArgsErr(errMsg);
2474 /* we remove constness from BufAsString() result since we know
2475 searchStringMS will not modify the result */
2476 newArgList[0].tag = STRING_TAG;
2477 newArgList[0].val.str.rep = (char *)BufAsString(window->buffer);
2478 newArgList[0].val.str.len = window->buffer->length;
2480 /* copy other arguments to the new argument list */
2481 memcpy(&newArgList[1], argList, nArgs * sizeof(DataValue));
2483 return searchStringMS(window, newArgList, nArgs+1, result, errMsg);
2487 ** Built-in macro subroutine for searching a string. Arguments are $1:
2488 ** string to search in, $2: string to search for, $3: starting position.
2489 ** Optional arguments may include the strings: "wrap" to make the search
2490 ** wrap around the beginning or end of the string, "backward" or "forward"
2491 ** to change the search direction ("forward" is the default), "literal",
2492 ** "case" or "regex" to change the search type (default is "literal").
2494 ** Returns the starting position of the match, or -1 if nothing matched.
2495 ** also returns the ending position of the match in $searchEndPos
2497 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2498 DataValue *result, char **errMsg)
2500 int beginPos, wrap, direction, found = False, foundStart, foundEnd, type;
2501 int skipSearch = False, len;
2502 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *searchStr;
2504 /* Validate arguments and convert to proper types */
2505 if (nArgs < 3)
2506 return tooFewArgsErr(errMsg);
2507 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2508 return False;
2509 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2510 return False;
2511 if (!readIntArg(argList[2], &beginPos, errMsg))
2512 return False;
2513 if (!readSearchArgs(&argList[3], nArgs-3, &direction, &type, &wrap, errMsg))
2514 return False;
2516 len = argList[0].val.str.len;
2517 if (beginPos > len) {
2518 if (direction == SEARCH_FORWARD) {
2519 if (wrap) {
2520 beginPos = 0; /* Wrap immediately */
2521 } else {
2522 found = False;
2523 skipSearch = True;
2525 } else {
2526 beginPos = len;
2528 } else if (beginPos < 0) {
2529 if (direction == SEARCH_BACKWARD) {
2530 if (wrap) {
2531 beginPos = len; /* Wrap immediately */
2532 } else {
2533 found = False;
2534 skipSearch = True;
2536 } else {
2537 beginPos = 0;
2541 if (!skipSearch)
2542 found = SearchString(string, searchStr, direction, type, wrap, beginPos,
2543 &foundStart, &foundEnd, NULL, NULL, GetWindowDelimiters(window));
2545 /* Return the results */
2546 ReturnGlobals[SEARCH_END]->value.tag = INT_TAG;
2547 ReturnGlobals[SEARCH_END]->value.val.n = found ? foundEnd : 0;
2548 result->tag = INT_TAG;
2549 result->val.n = found ? foundStart : -1;
2550 return True;
2554 ** Built-in macro subroutine for replacing all occurences of a search string in
2555 ** a string with a replacement string. Arguments are $1: string to search in,
2556 ** $2: string to search for, $3: replacement string. Also takes an optional
2557 ** search type: one of "literal", "case" or "regex" (default is "literal"), and
2558 ** an optional "copy" argument.
2560 ** Returns a new string with all of the replacements done. If no replacements
2561 ** were performed and "copy" was specified, returns a copy of the original
2562 ** string. Otherwise returns an empty string ("").
2564 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2565 DataValue *result, char **errMsg)
2567 char stringStorage[3][TYPE_INT_STR_SIZE(int)], *string, *searchStr, *replaceStr;
2568 char *argStr, *replacedStr;
2569 int searchType = SEARCH_LITERAL, copyStart, copyEnd;
2570 int replacedLen, replaceEnd, force=False, i;
2572 /* Validate arguments and convert to proper types */
2573 if (nArgs < 3 || nArgs > 5)
2574 return wrongNArgsErr(errMsg);
2575 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2576 return False;
2577 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2578 return False;
2579 if (!readStringArg(argList[2], &replaceStr, stringStorage[2], errMsg))
2580 return False;
2581 for (i = 3; i < nArgs; i++) {
2582 /* Read the optional search type and force arguments */
2583 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
2584 return False;
2585 if (!StringToSearchType(argStr, &searchType)) {
2586 /* It's not a search type. is it "copy"? */
2587 if (!strcmp(argStr, "copy")) {
2588 force = True;
2589 } else {
2590 *errMsg = "unrecognized argument to %s";
2591 return False;
2596 /* Do the replace */
2597 replacedStr = ReplaceAllInString(string, searchStr, replaceStr, searchType,
2598 &copyStart, &copyEnd, &replacedLen, GetWindowDelimiters(window));
2600 /* Return the results */
2601 result->tag = STRING_TAG;
2602 if (replacedStr == NULL) {
2603 if (force) {
2604 /* Just copy the original DataValue */
2605 if (argList[0].tag == STRING_TAG) {
2606 result->val.str.rep = argList[0].val.str.rep;
2607 result->val.str.len = argList[0].val.str.len;
2609 else {
2610 AllocNStringCpy(&result->val.str, string);
2613 else {
2614 result->val.str.rep = PERM_ALLOC_STR("");
2615 result->val.str.len = 0;
2618 else {
2619 size_t remainder = strlen(&string[copyEnd]);
2620 replaceEnd = copyStart + replacedLen;
2621 AllocNString(&result->val.str, replaceEnd + remainder + 1);
2622 strncpy(result->val.str.rep, string, copyStart);
2623 strcpy(&result->val.str.rep[copyStart], replacedStr);
2624 strcpy(&result->val.str.rep[replaceEnd], &string[copyEnd]);
2625 XtFree(replacedStr);
2627 return True;
2630 static int readSearchArgs(DataValue *argList, int nArgs, int *searchDirection,
2631 int *searchType, int *wrap, char **errMsg)
2633 int i;
2634 char *argStr, stringStorage[TYPE_INT_STR_SIZE(int)];
2636 *wrap = False;
2637 *searchDirection = SEARCH_FORWARD;
2638 *searchType = SEARCH_LITERAL;
2639 for (i=0; i<nArgs; i++) {
2640 if (!readStringArg(argList[i], &argStr, stringStorage, errMsg))
2641 return False;
2642 else if (!strcmp(argStr, "wrap"))
2643 *wrap = True;
2644 else if (!strcmp(argStr, "nowrap"))
2645 *wrap = False;
2646 else if (!strcmp(argStr, "backward"))
2647 *searchDirection = SEARCH_BACKWARD;
2648 else if (!strcmp(argStr, "forward"))
2649 *searchDirection = SEARCH_FORWARD;
2650 else if (!StringToSearchType(argStr, searchType)) {
2651 *errMsg = "Unrecognized argument to %s";
2652 return False;
2655 return True;
2658 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
2659 DataValue *result, char **errMsg)
2661 int pos;
2663 /* Get argument and convert to int */
2664 if (nArgs != 1)
2665 return wrongNArgsErr(errMsg);
2666 if (!readIntArg(argList[0], &pos, errMsg))
2667 return False;
2669 /* Set the position */
2670 TextSetCursorPos(window->lastFocus, pos);
2671 result->tag = NO_TAG;
2672 return True;
2675 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
2676 DataValue *result, char **errMsg)
2678 int start, end, startTmp;
2680 /* Get arguments and convert to int */
2681 if (nArgs != 2)
2682 return wrongNArgsErr(errMsg);
2683 if (!readIntArg(argList[0], &start, errMsg))
2684 return False;
2685 if (!readIntArg(argList[1], &end, errMsg))
2686 return False;
2688 /* Verify integrity of arguments */
2689 if (start > end) {
2690 startTmp = start;
2691 start = end;
2692 end = startTmp;
2694 if (start < 0) start = 0;
2695 if (start > window->buffer->length) start = window->buffer->length;
2696 if (end < 0) end = 0;
2697 if (end > window->buffer->length) end = window->buffer->length;
2699 /* Make the selection */
2700 BufSelect(window->buffer, start, end);
2701 result->tag = NO_TAG;
2702 return True;
2705 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
2706 DataValue *result, char **errMsg)
2708 int start, end, left, right;
2710 /* Get arguments and convert to int */
2711 if (nArgs != 4)
2712 return wrongNArgsErr(errMsg);
2713 if (!readIntArg(argList[0], &start, errMsg))
2714 return False;
2715 if (!readIntArg(argList[1], &end, errMsg))
2716 return False;
2717 if (!readIntArg(argList[2], &left, errMsg))
2718 return False;
2719 if (!readIntArg(argList[3], &right, errMsg))
2720 return False;
2722 /* Make the selection */
2723 BufRectSelect(window->buffer, start, end, left, right);
2724 result->tag = NO_TAG;
2725 return True;
2729 ** Macro subroutine to ring the bell
2731 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
2732 DataValue *result, char **errMsg)
2734 if (nArgs != 0)
2735 return wrongNArgsErr(errMsg);
2736 XBell(XtDisplay(window->shell), 0);
2737 result->tag = NO_TAG;
2738 return True;
2741 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
2742 DataValue *result, char **errMsg)
2744 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2745 int i;
2747 if (nArgs == 0)
2748 return tooFewArgsErr(errMsg);
2749 for (i=0; i<nArgs; i++) {
2750 if (!readStringArg(argList[i], &string, stringStorage, errMsg))
2751 return False;
2752 printf("%s%s", string, i==nArgs-1 ? "" : " ");
2754 fflush( stdout );
2755 result->tag = NO_TAG;
2756 return True;
2760 ** Built-in macro subroutine for getting the value of an environment variable
2762 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
2763 DataValue *result, char **errMsg)
2765 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
2766 char *name;
2767 char *value;
2769 /* Get name of variable to get */
2770 if (nArgs != 1)
2771 return wrongNArgsErr(errMsg);
2772 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
2773 *errMsg = "argument to %s must be a string";
2774 return False;
2776 value = getenv(name);
2777 if (value == NULL)
2778 value = "";
2780 /* Return the text as an allocated string */
2781 result->tag = STRING_TAG;
2782 AllocNStringCpy(&result->val.str, value);
2783 return True;
2786 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
2787 DataValue *result, char **errMsg)
2789 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *cmdString, *inputString;
2791 if (nArgs != 2)
2792 return wrongNArgsErr(errMsg);
2793 if (!readStringArg(argList[0], &cmdString, stringStorage[0], errMsg))
2794 return False;
2795 if (!readStringArg(argList[1], &inputString, stringStorage[1], errMsg))
2796 return False;
2798 /* Shell command execution requires that the macro be suspended, so
2799 this subroutine can't be run if macro execution can't be interrupted */
2800 if (MacroRunWindow()->macroCmdData == NULL) {
2801 *errMsg = "%s can't be called from non-suspendable context";
2802 return False;
2805 #ifdef VMS
2806 *errMsg = "Shell commands not supported under VMS";
2807 return False;
2808 #else
2809 ShellCmdToMacroString(window, cmdString, inputString);
2810 result->tag = INT_TAG;
2811 result->val.n = 0;
2812 return True;
2813 #endif /*VMS*/
2817 ** Method used by ShellCmdToMacroString (called by shellCmdMS), for returning
2818 ** macro string and exit status after the execution of a shell command is
2819 ** complete. (Sorry about the poor modularity here, it's just not worth
2820 ** teaching other modules about macro return globals, since other than this,
2821 ** they're not used outside of macro.c)
2823 void ReturnShellCommandOutput(WindowInfo *window, const char *outText, int status)
2825 DataValue retVal;
2826 macroCmdInfo *cmdData = window->macroCmdData;
2828 if (cmdData == NULL)
2829 return;
2830 retVal.tag = STRING_TAG;
2831 AllocNStringCpy(&retVal.val.str, outText);
2832 ModifyReturnedValue(cmdData->context, retVal);
2833 ReturnGlobals[SHELL_CMD_STATUS]->value.tag = INT_TAG;
2834 ReturnGlobals[SHELL_CMD_STATUS]->value.val.n = status;
2837 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2838 DataValue *result, char **errMsg)
2840 macroCmdInfo *cmdData;
2841 char stringStorage[TYPE_INT_STR_SIZE(int)];
2842 char btnStorage[TYPE_INT_STR_SIZE(int)];
2843 char *btnLabel;
2844 char *message;
2845 Arg al[20];
2846 int ac;
2847 Widget dialog, btn;
2848 int i, nBtns;
2849 XmString s1, s2;
2851 /* Ignore the focused window passed as the function argument and put
2852 the dialog up over the window which is executing the macro */
2853 window = MacroRunWindow();
2854 cmdData = window->macroCmdData;
2856 /* Dialogs require macro to be suspended and interleaved with other macros.
2857 This subroutine can't be run if macro execution can't be interrupted */
2858 if (!cmdData) {
2859 *errMsg = "%s can't be called from non-suspendable context";
2860 return False;
2863 /* Read and check the arguments. The first being the dialog message,
2864 and the rest being the button labels */
2865 if (nArgs == 0) {
2866 *errMsg = "%s subroutine called with no arguments";
2867 return False;
2869 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
2870 return False;
2873 /* check that all button labels can be read */
2874 for (i=1; i<nArgs; i++) {
2875 if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg)) {
2876 return False;
2880 /* pick up the first button */
2881 if (nArgs == 1) {
2882 btnLabel = "OK";
2883 nBtns = 1;
2885 else {
2886 nBtns = nArgs - 1;
2887 argList++;
2888 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
2891 /* Create the message box dialog widget and its dialog shell parent */
2892 ac = 0;
2893 XtSetArg(al[ac], XmNtitle, " "); ac++;
2894 XtSetArg(al[ac], XmNmessageString, s1=MKSTRING(message)); ac++;
2895 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
2896 dialog = CreateMessageDialog(window->shell, "macroDialog", al, ac);
2897 if (1 == nArgs)
2899 /* Only set margin width for the default OK button */
2900 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2901 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
2902 NULL);
2905 XmStringFree(s1);
2906 XmStringFree(s2);
2907 AddMotifCloseCallback(XtParent(dialog), dialogCloseCB, window);
2908 XtAddCallback(dialog, XmNokCallback, dialogBtnCB, window);
2909 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2910 XmNuserData, (XtPointer)1, NULL);
2911 cmdData->dialog = dialog;
2913 /* Unmanage default buttons, except for "OK" */
2914 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2915 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2917 /* Make callback for the unmanaged cancel button (which can
2918 still get executed via the esc key) activate close box action */
2919 XtAddCallback(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2920 XmNactivateCallback, dialogCloseCB, window);
2922 /* Add user specified buttons (1st is already done) */
2923 for (i=1; i<nBtns; i++) {
2924 readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
2925 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2926 XmNlabelString, s1=XmStringCreateSimple(btnLabel),
2927 XmNuserData, (XtPointer)(i+1), NULL);
2928 XtAddCallback(btn, XmNactivateCallback, dialogBtnCB, window);
2929 XmStringFree(s1);
2932 #ifdef LESSTIF_VERSION
2933 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2934 the escape key for closing the dialog (probably because the
2935 cancel button is not managed). */
2936 XtAddEventHandler(dialog, KeyPressMask, False, dialogEscCB,
2937 (XtPointer)window);
2938 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
2939 True, GrabModeAsync, GrabModeAsync);
2940 #endif /* LESSTIF_VERSION */
2942 /* Put up the dialog */
2943 ManageDialogCenteredOnPointer(dialog);
2945 /* Stop macro execution until the dialog is complete */
2946 PreemptMacro();
2948 /* Return placeholder result. Value will be changed by button callback */
2949 result->tag = INT_TAG;
2950 result->val.n = 0;
2951 return True;
2954 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData)
2956 WindowInfo *window = (WindowInfo *)clientData;
2957 macroCmdInfo *cmdData = window->macroCmdData;
2958 XtPointer userData;
2959 DataValue retVal;
2961 /* Return the index of the button which was pressed (stored in the userData
2962 field of the button widget). The 1st button, being a gadget, is not
2963 returned in w. */
2964 if (cmdData == NULL)
2965 return; /* shouldn't happen */
2966 if (XtClass(w) == xmPushButtonWidgetClass) {
2967 XtVaGetValues(w, XmNuserData, &userData, NULL);
2968 retVal.val.n = (int)userData;
2969 } else
2970 retVal.val.n = 1;
2971 retVal.tag = INT_TAG;
2972 ModifyReturnedValue(cmdData->context, retVal);
2974 /* Pop down the dialog */
2975 XtDestroyWidget(XtParent(cmdData->dialog));
2976 cmdData->dialog = NULL;
2978 /* Continue preempted macro execution */
2979 ResumeMacroExecution(window);
2982 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData)
2984 WindowInfo *window = (WindowInfo *)clientData;
2985 macroCmdInfo *cmdData = window->macroCmdData;
2986 DataValue retVal;
2988 /* Return 0 to show that the dialog was closed via the window close box */
2989 retVal.val.n = 0;
2990 retVal.tag = INT_TAG;
2991 ModifyReturnedValue(cmdData->context, retVal);
2993 /* Pop down the dialog */
2994 XtDestroyWidget(XtParent(cmdData->dialog));
2995 cmdData->dialog = NULL;
2997 /* Continue preempted macro execution */
2998 ResumeMacroExecution(window);
3001 #ifdef LESSTIF_VERSION
3002 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3003 Boolean *cont)
3005 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3006 return;
3007 if (clientData != NULL) {
3008 dialogCloseCB(w, (WindowInfo *)clientData, NULL);
3010 *cont = False;
3012 #endif /* LESSTIF_VERSION */
3014 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3015 DataValue *result, char **errMsg)
3017 macroCmdInfo *cmdData;
3018 char stringStorage[TYPE_INT_STR_SIZE(int)];
3019 char btnStorage[TYPE_INT_STR_SIZE(int)];
3020 char *btnLabel;
3021 char *message;
3022 Widget dialog, btn;
3023 int i, nBtns;
3024 XmString s1, s2;
3025 Arg al[20];
3026 int ac;
3028 /* Ignore the focused window passed as the function argument and put
3029 the dialog up over the window which is executing the macro */
3030 window = MacroRunWindow();
3031 cmdData = window->macroCmdData;
3033 /* Dialogs require macro to be suspended and interleaved with other macros.
3034 This subroutine can't be run if macro execution can't be interrupted */
3035 if (!cmdData) {
3036 *errMsg = "%s can't be called from non-suspendable context";
3037 return False;
3040 /* Read and check the arguments. The first being the dialog message,
3041 and the rest being the button labels */
3042 if (nArgs == 0) {
3043 *errMsg = "%s subroutine called with no arguments";
3044 return False;
3046 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
3047 return False;
3049 /* check that all button labels can be read */
3050 for (i=1; i<nArgs; i++) {
3051 if (!readStringArg(argList[i], &btnLabel, stringStorage, errMsg)) {
3052 return False;
3055 if (nArgs == 1) {
3056 btnLabel = "OK";
3057 nBtns = 1;
3059 else {
3060 nBtns = nArgs - 1;
3061 argList++;
3062 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
3065 /* Create the selection box dialog widget and its dialog shell parent */
3066 ac = 0;
3067 XtSetArg(al[ac], XmNtitle, " "); ac++;
3068 XtSetArg(al[ac], XmNselectionLabelString, s1=MKSTRING(message)); ac++;
3069 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
3070 dialog = CreatePromptDialog(window->shell, "macroStringDialog", al, ac);
3071 if (1 == nArgs)
3073 /* Only set margin width for the default OK button */
3074 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3075 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
3076 NULL);
3079 XmStringFree(s1);
3080 XmStringFree(s2);
3081 AddMotifCloseCallback(XtParent(dialog), stringDialogCloseCB, window);
3082 XtAddCallback(dialog, XmNokCallback, stringDialogBtnCB, window);
3083 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3084 XmNuserData, (XtPointer)1, NULL);
3085 cmdData->dialog = dialog;
3087 /* Unmanage unneded widgets */
3088 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3089 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3091 /* Make callback for the unmanaged cancel button (which can
3092 still get executed via the esc key) activate close box action */
3093 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3094 XmNactivateCallback, stringDialogCloseCB, window);
3096 /* Add user specified buttons (1st is already done). Selection box
3097 requires a place-holder widget to be added before buttons can be
3098 added, that's what the separator below is for */
3099 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3100 for (i=1; i<nBtns; i++) {
3101 readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
3102 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3103 XmNlabelString, s1=XmStringCreateSimple(btnLabel),
3104 XmNuserData, (XtPointer)(i+1), NULL);
3105 XtAddCallback(btn, XmNactivateCallback, stringDialogBtnCB, window);
3106 XmStringFree(s1);
3109 #ifdef LESSTIF_VERSION
3110 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3111 the escape key for closing the dialog (probably because the
3112 cancel button is not managed). */
3113 XtAddEventHandler(dialog, KeyPressMask, False, stringDialogEscCB,
3114 (XtPointer)window);
3115 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3116 True, GrabModeAsync, GrabModeAsync);
3117 #endif /* LESSTIF_VERSION */
3119 /* Put up the dialog */
3120 ManageDialogCenteredOnPointer(dialog);
3122 /* Stop macro execution until the dialog is complete */
3123 PreemptMacro();
3125 /* Return placeholder result. Value will be changed by button callback */
3126 result->tag = INT_TAG;
3127 result->val.n = 0;
3128 return True;
3131 static void stringDialogBtnCB(Widget w, XtPointer clientData,
3132 XtPointer callData)
3134 WindowInfo *window = (WindowInfo *)clientData;
3135 macroCmdInfo *cmdData = window->macroCmdData;
3136 XtPointer userData;
3137 DataValue retVal;
3138 char *text;
3139 int btnNum;
3141 /* shouldn't happen, but would crash if it did */
3142 if (cmdData == NULL)
3143 return;
3145 /* Return the string entered in the selection text area */
3146 text = XmTextGetString(XmSelectionBoxGetChild(cmdData->dialog,
3147 XmDIALOG_TEXT));
3148 retVal.tag = STRING_TAG;
3149 AllocNStringCpy(&retVal.val.str, text);
3150 XtFree(text);
3151 ModifyReturnedValue(cmdData->context, retVal);
3153 /* Find the index of the button which was pressed (stored in the userData
3154 field of the button widget). The 1st button, being a gadget, is not
3155 returned in w. */
3156 if (XtClass(w) == xmPushButtonWidgetClass) {
3157 XtVaGetValues(w, XmNuserData, &userData, NULL);
3158 btnNum = (int)userData;
3159 } else
3160 btnNum = 1;
3162 /* Return the button number in the global variable $string_dialog_button */
3163 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3164 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = btnNum;
3166 /* Pop down the dialog */
3167 XtDestroyWidget(XtParent(cmdData->dialog));
3168 cmdData->dialog = NULL;
3170 /* Continue preempted macro execution */
3171 ResumeMacroExecution(window);
3174 static void stringDialogCloseCB(Widget w, XtPointer clientData,
3175 XtPointer callData)
3177 WindowInfo *window = (WindowInfo *)clientData;
3178 macroCmdInfo *cmdData = window->macroCmdData;
3179 DataValue retVal;
3181 /* shouldn't happen, but would crash if it did */
3182 if (cmdData == NULL)
3183 return;
3185 /* Return an empty string */
3186 retVal.tag = STRING_TAG;
3187 retVal.val.str.rep = PERM_ALLOC_STR("");
3188 retVal.val.str.len = 0;
3189 ModifyReturnedValue(cmdData->context, retVal);
3191 /* Return button number 0 in the global variable $string_dialog_button */
3192 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3193 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = 0;
3195 /* Pop down the dialog */
3196 XtDestroyWidget(XtParent(cmdData->dialog));
3197 cmdData->dialog = NULL;
3199 /* Continue preempted macro execution */
3200 ResumeMacroExecution(window);
3203 #ifdef LESSTIF_VERSION
3204 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3205 Boolean *cont)
3207 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3208 return;
3209 if (clientData != NULL) {
3210 stringDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3212 *cont = False;
3214 #endif /* LESSTIF_VERSION */
3217 ** A subroutine to put up a calltip
3218 ** First arg is either text to be displayed or a key for tip/tag lookup.
3219 ** Optional second arg is the buffer position beneath which to display the
3220 ** upper-left corner of the tip. Default (or -1) puts it under the cursor.
3221 ** Additional optional arguments:
3222 ** "tipText": (default) Indicates first arg is text to be displayed in tip.
3223 ** "tipKey": Indicates first arg is key in calltips database. If key
3224 ** is not found in tip database then the tags database is also
3225 ** searched.
3226 ** "tagKey": Indicates first arg is key in tags database. (Skips
3227 ** search in calltips database.)
3228 ** "center": Horizontally center the calltip at the position
3229 ** "right": Put the right edge of the calltip at the position
3230 ** "center" and "right" cannot both be specified.
3231 ** "above": Place the calltip above the position
3232 ** "strict": Don't move the calltip to keep it on-screen and away
3233 ** from the cursor's line.
3235 ** Returns the new calltip's ID on success, 0 on failure.
3237 ** Does this need to go on IgnoredActions? I don't think so, since
3238 ** showing a calltip may be part of the action you want to learn.
3240 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3241 DataValue *result, char **errMsg)
3243 char stringStorage[TYPE_INT_STR_SIZE(int)], *tipText, *txtArg;
3244 Boolean anchored = False, lookup = True;
3245 int mode = -1, i;
3246 int anchorPos, hAlign = TIP_LEFT, vAlign = TIP_BELOW,
3247 alignMode = TIP_SLOPPY;
3249 /* Read and check the string */
3250 if (nArgs < 1) {
3251 *errMsg = "%s subroutine called with too few arguments";
3252 return False;
3254 if (nArgs > 6) {
3255 *errMsg = "%s subroutine called with too many arguments";
3256 return False;
3259 /* Read the tip text or key */
3260 if (!readStringArg(argList[0], &tipText, stringStorage, errMsg))
3261 return False;
3263 /* Read the anchor position (-1 for unanchored) */
3264 if (nArgs > 1) {
3265 if (!readIntArg(argList[1], &anchorPos, errMsg))
3266 return False;
3267 } else {
3268 anchorPos = -1;
3270 if (anchorPos >= 0) anchored = True;
3272 /* Any further args are directives for relative positioning */
3273 for (i = 2; i < nArgs; ++i) {
3274 if (!readStringArg(argList[i], &txtArg, stringStorage, errMsg)){
3275 return False;
3277 switch( txtArg[0] ) {
3278 case 'c':
3279 if (strcmp(txtArg, "center"))
3280 goto bad_arg;
3281 hAlign = TIP_CENTER;
3282 break;
3283 case 'r':
3284 if (strcmp(txtArg, "right"))
3285 goto bad_arg;
3286 hAlign = TIP_RIGHT;
3287 break;
3288 case 'a':
3289 if (strcmp(txtArg, "above"))
3290 goto bad_arg;
3291 vAlign = TIP_ABOVE;
3292 break;
3293 case 's':
3294 if (strcmp(txtArg, "strict"))
3295 goto bad_arg;
3296 alignMode = TIP_STRICT;
3297 break;
3298 case 't':
3299 if (!strcmp(txtArg, "tipText"))
3300 mode = -1;
3301 else if (!strcmp(txtArg, "tipKey"))
3302 mode = TIP;
3303 else if (!strcmp(txtArg, "tagKey"))
3304 mode = TIP_FROM_TAG;
3305 else
3306 goto bad_arg;
3307 break;
3308 default:
3309 goto bad_arg;
3313 result->tag = INT_TAG;
3314 if (mode < 0) lookup = False;
3315 /* Look up (maybe) a calltip and display it */
3316 result->val.n = ShowTipString( window, tipText, anchored, anchorPos, lookup,
3317 mode, hAlign, vAlign, alignMode );
3319 return True;
3321 bad_arg:
3322 /* This is how the (more informative) global var. version would work,
3323 assuming there was a global buffer called msg. */
3324 /* sprintf(msg, "unrecognized argument to %%s: \"%s\"", txtArg);
3325 *errMsg = msg; */
3326 *errMsg = "unrecognized argument to %s";
3327 return False;
3331 ** A subroutine to kill the current calltip
3333 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3334 DataValue *result, char **errMsg)
3336 int calltipID = 0;
3338 if (nArgs > 1) {
3339 *errMsg = "%s subroutine called with too many arguments";
3340 return False;
3342 if (nArgs > 0) {
3343 if (!readIntArg(argList[0], &calltipID, errMsg))
3344 return False;
3347 KillCalltip( window, calltipID );
3349 result->tag = NO_TAG;
3350 return True;
3354 * A subroutine to get the ID of the current calltip, or 0 if there is none.
3356 static int calltipIDMV(WindowInfo *window, DataValue *argList,
3357 int nArgs, DataValue *result, char **errMsg)
3359 result->tag = INT_TAG;
3360 result->val.n = GetCalltipID(window, 0);
3361 return True;
3365 ** filename_dialog([title[, mode[, defaultPath[, filter[, defaultName]]]]])
3367 ** Presents a FileSelectionDialog to the user prompting for a new file.
3369 ** Options are:
3370 ** title - will be the title of the dialog, defaults to "Choose file".
3371 ** mode - if set to "exist" (default), the "New File Name" TextField
3372 ** of the FSB will be unmanaged. If "new", the TextField will
3373 ** be managed.
3374 ** defaultPath - is the default path to use. Default (or "") will use the
3375 ** active document's directory.
3376 ** filter - the file glob which determines which files to display.
3377 ** Is set to "*" if filter is "" and by default.
3378 ** defaultName - is the default filename that is filled in automatically.
3380 ** Returns "" if the user cancelled the dialog, otherwise returns the path to
3381 ** the file that was selected
3383 ** Note that defaultName doesn't work on all *tifs. :-(
3385 static int filenameDialogMS(WindowInfo* window, DataValue* argList, int nArgs,
3386 DataValue* result, char** errMsg)
3388 char stringStorage[5][TYPE_INT_STR_SIZE(int)];
3389 char filename[MAXPATHLEN + 1];
3390 char* title = "Choose Filename";
3391 char* mode = "exist";
3392 char* defaultPath = "";
3393 char* filter = "";
3394 char* defaultName = "";
3395 char* orgDefaultPath;
3396 char* orgFilter;
3397 int gfnResult;
3399 /* Ignore the focused window passed as the function argument and put
3400 the dialog up over the window which is executing the macro */
3401 window = MacroRunWindow();
3403 /* Dialogs require macro to be suspended and interleaved with other macros.
3404 This subroutine can't be run if macro execution can't be interrupted */
3405 if (NULL == window->macroCmdData) {
3406 M_FAILURE("%s can't be called from non-suspendable context");
3409 /* Get the argument list. */
3410 if (nArgs > 0 && !readStringArg(argList[0], &title, stringStorage[0],
3411 errMsg)) {
3412 return False;
3415 if (nArgs > 1 && !readStringArg(argList[1], &mode, stringStorage[1],
3416 errMsg)) {
3417 return False;
3419 if (0 != strcmp(mode, "exist") && 0 != strcmp(mode, "new")) {
3420 M_FAILURE("Invalid value for mode in %s");
3423 if (nArgs > 2 && !readStringArg(argList[2], &defaultPath, stringStorage[2],
3424 errMsg)) {
3425 return False;
3428 if (nArgs > 3 && !readStringArg(argList[3], &filter, stringStorage[3],
3429 errMsg)) {
3430 return False;
3433 if (nArgs > 4 && !readStringArg(argList[4], &defaultName, stringStorage[4],
3434 errMsg)) {
3435 return False;
3438 if (nArgs > 5) {
3439 M_FAILURE("%s called with too many arguments. Expects at most 5 arguments.");
3442 /* Set default directory (saving original for later) */
3443 orgDefaultPath = GetFileDialogDefaultDirectory();
3444 if ('\0' != defaultPath[0]) {
3445 SetFileDialogDefaultDirectory(defaultPath);
3446 } else {
3447 SetFileDialogDefaultDirectory(window->path);
3450 /* Set filter (saving original for later) */
3451 orgFilter = GetFileDialogDefaultPattern();
3452 if ('\0' != filter[0]) {
3453 SetFileDialogDefaultPattern(filter);
3456 /* Fork to one of the worker methods from util/getfiles.c.
3457 (This should obviously be refactored.) */
3458 if (0 == strcmp(mode, "exist")) {
3459 gfnResult = GetExistingFilename(window->shell, title, filename);
3460 } else {
3461 gfnResult = GetNewFilename(window->shell, title, filename, defaultName);
3462 } /* Invalid values are weeded out above. */
3464 /* Reset original values and free temps */
3465 SetFileDialogDefaultDirectory(orgDefaultPath);
3466 SetFileDialogDefaultPattern(orgFilter);
3467 XtFree(orgDefaultPath);
3468 XtFree(orgFilter);
3470 result->tag = STRING_TAG;
3471 if (GFN_OK == gfnResult) {
3472 /* Got a string, copy it to the result */
3473 if (!AllocNStringNCpy(&result->val.str, filename, MAXPATHLEN)) {
3474 M_FAILURE("failed to allocate return value: %s");
3476 } else {
3477 /* User cancelled. Return "" */
3478 result->val.str.rep = PERM_ALLOC_STR("");
3479 result->val.str.len = 0;
3482 return True;
3485 /* T Balinski */
3486 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3487 DataValue *result, char **errMsg)
3489 macroCmdInfo *cmdData;
3490 char stringStorage[TYPE_INT_STR_SIZE(int)];
3491 char textStorage[TYPE_INT_STR_SIZE(int)];
3492 char btnStorage[TYPE_INT_STR_SIZE(int)];
3493 char *btnLabel;
3494 char *message, *text;
3495 Widget dialog, btn;
3496 int i, nBtns;
3497 XmString s1, s2;
3498 long nlines = 0;
3499 char *p, *old_p, **text_lines, *tmp;
3500 int tmp_len;
3501 int n, is_last;
3502 XmString *test_strings;
3503 int tabDist;
3504 Arg al[20];
3505 int ac;
3508 /* Ignore the focused window passed as the function argument and put
3509 the dialog up over the window which is executing the macro */
3510 window = MacroRunWindow();
3511 cmdData = window->macroCmdData;
3513 /* Dialogs require macro to be suspended and interleaved with other macros.
3514 This subroutine can't be run if macro execution can't be interrupted */
3515 if (!cmdData) {
3516 *errMsg = "%s can't be called from non-suspendable context";
3517 return False;
3520 /* Read and check the arguments. The first being the dialog message,
3521 and the rest being the button labels */
3522 if (nArgs < 2) {
3523 *errMsg = "%s subroutine called with no message, string or arguments";
3524 return False;
3527 if (!readStringArg(argList[0], &message, stringStorage, errMsg))
3528 return False;
3530 if (!readStringArg(argList[1], &text, textStorage, errMsg))
3531 return False;
3533 if (!text || text[0] == '\0') {
3534 *errMsg = "%s subroutine called with empty list data";
3535 return False;
3538 /* check that all button labels can be read */
3539 for (i=2; i<nArgs; i++)
3540 if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg))
3541 return False;
3543 /* pick up the first button */
3544 if (nArgs == 2) {
3545 btnLabel = "OK";
3546 nBtns = 1;
3548 else {
3549 nBtns = nArgs - 2;
3550 argList += 2;
3551 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
3554 /* count the lines in the text - add one for unterminated last line */
3555 nlines = 1;
3556 for (p = text; *p; p++)
3557 if (*p == '\n')
3558 nlines++;
3560 /* now set up arrays of pointers to lines */
3561 /* test_strings to hold the display strings (tab expanded) */
3562 /* text_lines to hold the original text lines (without the '\n's) */
3563 test_strings = (XmString *) XtMalloc(sizeof(XmString) * nlines);
3564 text_lines = (char **)XtMalloc(sizeof(char *) * (nlines + 1));
3565 for (n = 0; n < nlines; n++) {
3566 test_strings[n] = (XmString)0;
3567 text_lines[n] = (char *)0;
3569 text_lines[n] = (char *)0; /* make sure this is a null-terminated table */
3571 /* pick up the tabDist value */
3572 tabDist = window->buffer->tabDist;
3574 /* load the table */
3575 n = 0;
3576 is_last = 0;
3577 p = old_p = text;
3578 tmp_len = 0; /* current allocated size of temporary buffer tmp */
3579 tmp = malloc(1); /* temporary buffer into which to expand tabs */
3580 do {
3581 is_last = (*p == '\0');
3582 if (*p == '\n' || is_last) {
3583 *p = '\0';
3584 if (strlen(old_p) > 0) { /* only include non-empty lines */
3585 char *s, *t;
3586 int l;
3588 /* save the actual text line in text_lines[n] */
3589 text_lines[n] = (char *)XtMalloc(strlen(old_p) + 1);
3590 strcpy(text_lines[n], old_p);
3592 /* work out the tabs expanded length */
3593 for (s = old_p, l = 0; *s; s++)
3594 l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
3596 /* verify tmp is big enough then tab-expand old_p into tmp */
3597 if (l > tmp_len)
3598 tmp = realloc(tmp, (tmp_len = l) + 1);
3599 for (s = old_p, t = tmp, l = 0; *s; s++) {
3600 if (*s == '\t') {
3601 for (i = tabDist - (l % tabDist); i--; l++)
3602 *t++ = ' ';
3604 else {
3605 *t++ = *s;
3606 l++;
3609 *t = '\0';
3610 /* that's it: tmp is the tab-expanded version of old_p */
3611 test_strings[n] = MKSTRING(tmp);
3612 n++;
3614 old_p = p + 1;
3615 if (!is_last)
3616 *p = '\n'; /* put back our newline */
3618 p++;
3619 } while (!is_last);
3621 free(tmp); /* don't need this anymore */
3622 nlines = n;
3623 if (nlines == 0) {
3624 test_strings[0] = MKSTRING("");
3625 nlines = 1;
3628 /* Create the selection box dialog widget and its dialog shell parent */
3629 ac = 0;
3630 XtSetArg(al[ac], XmNtitle, " "); ac++;
3631 XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++;
3632 XtSetArg(al[ac], XmNlistItems, test_strings); ac++;
3633 XtSetArg(al[ac], XmNlistItemCount, nlines); ac++;
3634 XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++;
3635 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
3636 dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac);
3637 if (2 == nArgs)
3639 /* Only set margin width for the default OK button */
3640 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3641 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
3642 NULL);
3645 AddMotifCloseCallback(XtParent(dialog), listDialogCloseCB, window);
3646 XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window);
3647 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3648 XmNuserData, (XtPointer)1, NULL);
3649 XmStringFree(s1);
3650 XmStringFree(s2);
3651 cmdData->dialog = dialog;
3653 /* forget lines stored in list */
3654 while (n--)
3655 XmStringFree(test_strings[n]);
3656 XtFree((char *)test_strings);
3658 /* modify the list */
3659 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST),
3660 XmNselectionPolicy, XmSINGLE_SELECT,
3661 XmNuserData, (XtPointer)text_lines, NULL);
3663 /* Unmanage unneeded widgets */
3664 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
3665 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3666 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3667 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
3668 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));
3670 /* Make callback for the unmanaged cancel button (which can
3671 still get executed via the esc key) activate close box action */
3672 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3673 XmNactivateCallback, listDialogCloseCB, window);
3675 /* Add user specified buttons (1st is already done). Selection box
3676 requires a place-holder widget to be added before buttons can be
3677 added, that's what the separator below is for */
3678 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3679 for (i=1; i<nBtns; i++) {
3680 readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
3681 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3682 XmNlabelString, s1=XmStringCreateSimple(btnLabel),
3683 XmNuserData, (XtPointer)(i+1), NULL);
3684 XtAddCallback(btn, XmNactivateCallback, listDialogBtnCB, window);
3685 XmStringFree(s1);
3688 #ifdef LESSTIF_VERSION
3689 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3690 the escape key for closing the dialog. */
3691 XtAddEventHandler(dialog, KeyPressMask, False, listDialogEscCB,
3692 (XtPointer)window);
3693 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3694 True, GrabModeAsync, GrabModeAsync);
3695 #endif /* LESSTIF_VERSION */
3697 /* Put up the dialog */
3698 ManageDialogCenteredOnPointer(dialog);
3700 /* Stop macro execution until the dialog is complete */
3701 PreemptMacro();
3703 /* Return placeholder result. Value will be changed by button callback */
3704 result->tag = INT_TAG;
3705 result->val.n = 0;
3706 return True;
3709 static void listDialogBtnCB(Widget w, XtPointer clientData,
3710 XtPointer callData)
3712 WindowInfo *window = (WindowInfo *)clientData;
3713 macroCmdInfo *cmdData = window->macroCmdData;
3714 XtPointer userData;
3715 DataValue retVal;
3716 char *text;
3717 char **text_lines;
3718 int btnNum;
3719 int n_sel, *seltable, sel_index = 0;
3720 Widget theList;
3721 size_t length;
3723 /* shouldn't happen, but would crash if it did */
3724 if (cmdData == NULL)
3725 return;
3727 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3728 /* Return the string selected in the selection list area */
3729 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3730 if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) {
3731 n_sel = 0;
3733 else {
3734 sel_index = seltable[0] - 1;
3735 XtFree((XtPointer)seltable);
3738 if (!n_sel) {
3739 text = PERM_ALLOC_STR("");
3740 length = 0;
3742 else {
3743 length = strlen((char *)text_lines[sel_index]);
3744 text = AllocString(length + 1);
3745 strcpy(text, text_lines[sel_index]);
3748 /* don't need text_lines anymore: free it */
3749 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3750 XtFree((XtPointer)text_lines[sel_index]);
3751 XtFree((XtPointer)text_lines);
3753 retVal.tag = STRING_TAG;
3754 retVal.val.str.rep = text;
3755 retVal.val.str.len = length;
3756 ModifyReturnedValue(cmdData->context, retVal);
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)userData;
3764 } else
3765 btnNum = 1;
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;
3771 /* Pop down the dialog */
3772 XtDestroyWidget(XtParent(cmdData->dialog));
3773 cmdData->dialog = NULL;
3775 /* Continue preempted macro execution */
3776 ResumeMacroExecution(window);
3779 static void listDialogCloseCB(Widget w, XtPointer clientData,
3780 XtPointer callData)
3782 WindowInfo *window = (WindowInfo *)clientData;
3783 macroCmdInfo *cmdData = window->macroCmdData;
3784 DataValue retVal;
3785 char **text_lines;
3786 int sel_index;
3787 Widget theList;
3789 /* shouldn't happen, but would crash if it did */
3790 if (cmdData == NULL)
3791 return;
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 XtFree((XtPointer)text_lines[sel_index]);
3798 XtFree((XtPointer)text_lines);
3800 /* Return an empty string */
3801 retVal.tag = STRING_TAG;
3802 retVal.val.str.rep = PERM_ALLOC_STR("");
3803 retVal.val.str.len = 0;
3804 ModifyReturnedValue(cmdData->context, retVal);
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;
3810 /* Pop down the dialog */
3811 XtDestroyWidget(XtParent(cmdData->dialog));
3812 cmdData->dialog = NULL;
3814 /* Continue preempted macro execution */
3815 ResumeMacroExecution(window);
3817 /* T Balinski End */
3819 #ifdef LESSTIF_VERSION
3820 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3821 Boolean *cont)
3823 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3824 return;
3825 if (clientData != NULL) {
3826 listDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3828 *cont = False;
3830 #endif /* LESSTIF_VERSION */
3833 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
3834 DataValue *result, char **errMsg)
3836 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3837 char *leftStr, *rightStr, *argStr;
3838 int considerCase = True;
3839 int i;
3840 int compareResult;
3842 if (nArgs < 2) {
3843 return(wrongNArgsErr(errMsg));
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;
3861 if (considerCase) {
3862 compareResult = strcmp(leftStr, rightStr);
3863 compareResult = (compareResult > 0) ? 1 : ((compareResult < 0) ? -1 : 0);
3865 else {
3866 compareResult = strCaseCmp(leftStr, rightStr);
3868 result->tag = INT_TAG;
3869 result->val.n = compareResult;
3870 return True;
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] = ""
3880 ** This behavior is specifically important when used to break up
3881 ** array sub-scripts
3884 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
3885 DataValue *result, char **errMsg)
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;
3895 if (nArgs < 2) {
3896 return(wrongNArgsErr(errMsg));
3898 if (!readStringArg(argList[0], &sourceStr, stringStorage[0], errMsg)) {
3899 *errMsg = "first argument must be a string: %s";
3900 return(False);
3902 if (!readStringArg(argList[1], &splitStr, stringStorage[1], errMsg)) {
3903 splitStr = NULL;
3905 else {
3906 if (splitStr[0] == 0) {
3907 splitStr = NULL;
3910 if (splitStr == NULL) {
3911 *errMsg = "second argument must be a non-empty string: %s";
3912 return(False);
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);
3920 else {
3921 searchType = SEARCH_LITERAL;
3924 result->tag = ARRAY_TAG;
3925 result->val.arrayPtr = ArrayNew();
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);
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);
3951 if (!ArrayInsert(result, allocIndexStr, &element)) {
3952 M_ARRAY_INSERT_FAILURE();
3955 if (found) {
3956 if (foundStart == foundEnd) {
3957 beginPos = foundEnd + 1; /* Avoid endless loop for 0-width match */
3958 } else {
3959 beginPos = foundEnd;
3961 } else {
3962 beginPos = strLength; /* Break the loop */
3964 lastEnd = foundEnd;
3965 ++indexNum;
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);
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;
3981 if (!ArrayInsert(result, allocIndexStr, &element)) {
3982 M_ARRAY_INSERT_FAILURE();
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);
3993 if (!ArrayInsert(result, allocIndexStr, &element)) {
3994 M_ARRAY_INSERT_FAILURE();
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);
4016 strcpy(allocIndexStr, indexStr);
4017 element.tag = STRING_TAG;
4018 element.val.str.rep = PERM_ALLOC_STR("");
4019 element.val.str.len = 0;
4021 if (!ArrayInsert(result, allocIndexStr, &element)) {
4022 M_ARRAY_INSERT_FAILURE();
4027 return(True);
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.
4036 /* DISABLED for 5.4
4037 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
4038 int nArgs, DataValue *result, char **errMsg)
4040 char *backlightString;
4042 if (nArgs == 0) {
4043 backlightString = GetPrefBacklightCharTypes();
4045 else if (nArgs == 1) {
4046 if (argList[0].tag != STRING_TAG) {
4047 *errMsg = "%s not called with a string parameter";
4048 return False;
4050 backlightString = argList[0].val.str.rep;
4052 else
4053 return wrongNArgsErr(errMsg);
4055 if (strcmp(backlightString, "default") == 0)
4056 backlightString = GetPrefBacklightCharTypes();
4057 if (backlightString && *backlightString == '\0') / * empty string param * /
4058 backlightString = NULL; / * turns of backlighting * /
4060 SetBacklightChars(window, backlightString);
4061 return True;
4062 } */
4064 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
4065 DataValue *result, char **errMsg)
4067 result->tag = INT_TAG;
4068 result->val.n = TextGetCursorPos(window->lastFocus);
4069 return True;
4072 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
4073 DataValue *result, char **errMsg)
4075 int line, cursorPos, colNum;
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;
4085 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
4086 DataValue *result, char **errMsg)
4088 textBuffer *buf = window->buffer;
4089 int cursorPos;
4091 result->tag = INT_TAG;
4092 cursorPos = TextGetCursorPos(window->lastFocus);
4093 result->val.n = BufCountDispChars(buf, BufStartOfLine(buf, cursorPos),
4094 cursorPos);
4095 return True;
4098 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4099 DataValue *result, char **errMsg)
4101 result->tag = STRING_TAG;
4102 AllocNStringCpy(&result->val.str, window->filename);
4103 return True;
4106 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
4107 DataValue *result, char **errMsg)
4109 result->tag = STRING_TAG;
4110 AllocNStringCpy(&result->val.str, window->path);
4111 return True;
4114 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
4115 DataValue *result, char **errMsg)
4117 result->tag = INT_TAG;
4118 result->val.n = window->buffer->length;
4119 return True;
4122 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
4123 DataValue *result, char **errMsg)
4125 result->tag = INT_TAG;
4126 result->val.n = window->buffer->primary.selected ?
4127 window->buffer->primary.start : -1;
4128 return True;
4131 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
4132 DataValue *result, char **errMsg)
4134 result->tag = INT_TAG;
4135 result->val.n = window->buffer->primary.selected ?
4136 window->buffer->primary.end : -1;
4137 return True;
4140 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
4141 DataValue *result, char **errMsg)
4143 selection *sel = &window->buffer->primary;
4145 result->tag = INT_TAG;
4146 result->val.n = sel->selected && sel->rectangular ? sel->rectStart : -1;
4147 return True;
4150 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
4151 DataValue *result, char **errMsg)
4153 selection *sel = &window->buffer->primary;
4155 result->tag = INT_TAG;
4156 result->val.n = sel->selected && sel->rectangular ? sel->rectEnd : -1;
4157 return True;
4160 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
4161 DataValue *result, char **errMsg)
4163 int margin, nCols;
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;
4172 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4173 DataValue *result, char **errMsg)
4175 result->tag = INT_TAG;
4176 result->val.n = window->showStats ? 1 : 0;
4177 return True;
4180 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4181 DataValue *result, char **errMsg)
4183 result->tag = INT_TAG;
4184 result->val.n = window->showISearchLine ? 1 : 0;
4185 return True;
4188 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
4189 DataValue *result, char **errMsg)
4191 result->tag = INT_TAG;
4192 result->val.n = window->showLineNumbers ? 1 : 0;
4193 return True;
4196 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
4197 DataValue *result, char **errMsg)
4199 char *res = NULL;
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;
4216 result->tag = STRING_TAG;
4217 result->val.str.rep = res;
4218 result->val.str.len = strlen(res);
4219 return True;
4222 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
4223 DataValue *result, char **errMsg)
4225 char *res = NULL;
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;
4242 result->tag = STRING_TAG;
4243 result->val.str.rep = res;
4244 result->val.str.len = strlen(res);
4245 return True;
4248 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
4249 DataValue *result, char **errMsg)
4251 result->tag = INT_TAG;
4252 result->val.n = window->highlightSyntax ? 1 : 0;
4253 return True;
4256 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
4257 DataValue *result, char **errMsg)
4259 result->tag = INT_TAG;
4260 result->val.n = window->saveOldVersion ? 1 : 0;
4261 return True;
4264 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
4265 DataValue *result, char **errMsg)
4267 result->tag = INT_TAG;
4268 result->val.n = window->autoSave ? 1 : 0;
4269 return True;
4272 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
4273 DataValue *result, char **errMsg)
4275 char *res = NULL;
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;
4292 result->tag = STRING_TAG;
4293 result->val.str.rep = res;
4294 result->val.str.len = strlen(res);
4295 return True;
4298 static int matchSyntaxBasedMV(WindowInfo *window, DataValue *argList, int nArgs,
4299 DataValue *result, char **errMsg)
4301 result->tag = INT_TAG;
4302 result->val.n = window->matchSyntaxBased ? 1 : 0;
4303 return True;
4308 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4309 DataValue *result, char **errMsg)
4311 result->tag = INT_TAG;
4312 result->val.n = window->overstrike ? 1 : 0;
4313 return True;
4316 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
4317 DataValue *result, char **errMsg)
4319 result->tag = INT_TAG;
4320 result->val.n = (IS_ANY_LOCKED(window->lockReasons)) ? 1 : 0;
4321 return True;
4324 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
4325 DataValue *result, char **errMsg)
4327 result->tag = INT_TAG;
4328 result->val.n = (IS_USER_LOCKED(window->lockReasons)) ? 1 : 0;
4329 return True;
4332 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
4333 DataValue *result, char **errMsg)
4335 char *res = NULL;
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;
4351 result->tag = STRING_TAG;
4352 result->val.str.rep = res;
4353 result->val.str.len = strlen(res);
4354 return True;
4357 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4358 DataValue *result, char **errMsg)
4360 result->tag = STRING_TAG;
4361 AllocNStringCpy(&result->val.str, window->fontName);
4362 return True;
4365 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4366 DataValue *result, char **errMsg)
4368 result->tag = STRING_TAG;
4369 AllocNStringCpy(&result->val.str, window->italicFontName);
4370 return True;
4373 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
4374 DataValue *result, char **errMsg)
4376 result->tag = STRING_TAG;
4377 AllocNStringCpy(&result->val.str, window->boldFontName);
4378 return True;
4381 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4382 DataValue *result, char **errMsg)
4384 result->tag = STRING_TAG;
4385 AllocNStringCpy(&result->val.str, window->boldItalicFontName);
4386 return True;
4389 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
4390 DataValue *result, char **errMsg)
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;
4398 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4399 DataValue *result, char **errMsg)
4401 result->tag = INT_TAG;
4402 result->val.n = TextGetMinFontWidth(window->textArea, window->highlightSyntax);
4403 return True;
4406 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4407 DataValue *result, char **errMsg)
4409 result->tag = INT_TAG;
4410 result->val.n = TextGetMaxFontWidth(window->textArea, window->highlightSyntax);
4411 return True;
4414 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4415 DataValue *result, char **errMsg)
4417 result->tag = INT_TAG;
4418 result->val.n = TextFirstVisibleLine(window->lastFocus);
4419 return True;
4422 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
4423 DataValue *result, char **errMsg)
4425 result->tag = INT_TAG;
4426 result->val.n = TextNumVisibleLines(window->lastFocus);
4427 return True;
4430 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4431 DataValue *result, char **errMsg)
4433 result->tag = INT_TAG;
4434 result->val.n = TextVisibleWidth(window->lastFocus);
4435 return True;
4438 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
4439 DataValue *result, char **errMsg)
4441 result->tag = INT_TAG;
4442 result->val.n = WidgetToPaneIndex(window, window->lastFocus) + 1;
4443 return True;
4446 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
4447 DataValue *result, char **errMsg)
4449 result->tag = INT_TAG;
4450 result->val.n = window->nPanes + 1;
4451 return True;
4454 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
4455 DataValue *result, char **errMsg)
4457 result->tag = ARRAY_TAG;
4458 result->val.arrayPtr = NULL;
4459 return True;
4462 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4463 DataValue *result, char **errMsg)
4465 result->tag = STRING_TAG;
4466 AllocNStringCpy(&result->val.str, GetPrefServerName());
4467 return True;
4470 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4471 DataValue *result, char **errMsg)
4473 result->tag = INT_TAG;
4474 result->val.n = window->buffer->tabDist;
4475 return True;
4478 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4479 DataValue *result, char **errMsg)
4481 int dist;
4483 XtVaGetValues(window->textArea, textNemulateTabs, &dist, NULL);
4484 result->tag = INT_TAG;
4485 result->val.n = dist;
4486 return True;
4489 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
4490 DataValue *result, char **errMsg)
4492 result->tag = INT_TAG;
4493 result->val.n = window->buffer->useTabs;
4494 return True;
4497 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
4498 DataValue *result, char **errMsg)
4500 result->tag = INT_TAG;
4501 result->val.n = window->fileChanged;
4502 return True;
4505 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4506 DataValue *result, char **errMsg)
4508 char *lmName = LanguageModeName(window->languageMode);
4510 if (lmName == NULL)
4511 lmName = "Plain";
4512 result->tag = STRING_TAG;
4513 AllocNStringCpy(&result->val.str, lmName);
4514 return True;
4517 /* DISABLED for 5.4
4518 static int backlightStringMV(WindowInfo *window, DataValue *argList,
4519 int nArgs, DataValue *result, char **errMsg)
4521 char *backlightString = window->backlightCharTypes;
4523 result->tag = STRING_TAG;
4524 if (!backlightString || !window->backlightChars)
4525 backlightString = "";
4526 AllocNStringCpy(&result->val.str, backlightString);
4527 return True;
4528 } */
4530 /* -------------------------------------------------------------------------- */
4533 ** Range set macro variables and functions
4535 static int rangesetListMV(WindowInfo *window, DataValue *argList, int nArgs,
4536 DataValue *result, char **errMsg)
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;
4545 result->tag = ARRAY_TAG;
4546 result->val.arrayPtr = ArrayNew();
4548 if (rangesetTable == NULL) {
4549 return True;
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];
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);
4564 if (!ArrayInsert(result, allocIndexStr, &element))
4565 M_FAILURE("Failed to insert array element in %s");
4568 return True;
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.
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.
4581 static int versionMV(WindowInfo* window, DataValue* argList, int nArgs,
4582 DataValue* result, char** errMsg)
4584 static unsigned version = NEDIT_VERSION * 1000 + NEDIT_REVISION;
4586 result->tag = INT_TAG;
4587 result->val.n = version;
4588 return True;
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.
4599 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
4600 DataValue *result, char **errMsg)
4602 int label;
4603 int i, nRangesetsRequired;
4604 DataValue element;
4605 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
4607 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4609 if (nArgs > 1)
4610 return wrongNArgsErr(errMsg);
4612 if (rangesetTable == NULL) {
4613 window->buffer->rangesetTable = rangesetTable =
4614 RangesetTableAlloc(window->buffer);
4617 if (nArgs == 0) {
4618 label = RangesetCreate(rangesetTable);
4620 result->tag = INT_TAG;
4621 result->val.n = label;
4622 return True;
4624 else {
4625 if (!readIntArg(argList[0], &nRangesetsRequired, errMsg))
4626 return False;
4628 result->tag = ARRAY_TAG;
4629 result->val.arrayPtr = ArrayNew();
4631 if (nRangesetsRequired > nRangesetsAvailable(rangesetTable))
4632 return True;
4634 for (i = 0; i < nRangesetsRequired; i++) {
4635 element.tag = INT_TAG;
4636 element.val.n = RangesetCreate(rangesetTable);
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);
4644 strcpy(allocIndexStr, indexStr);
4645 ArrayInsert(result, allocIndexStr, &element);
4648 return True;
4653 ** Built-in macro subroutine for forgetting a range set.
4655 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
4656 DataValue *result, char **errMsg)
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;
4666 if (nArgs != 1) {
4667 return wrongNArgsErr(errMsg);
4670 if (argList[0].tag == ARRAY_TAG) {
4671 array = &argList[0];
4672 arraySize = ArraySize(array);
4674 if (arraySize > N_RANGESETS) {
4675 M_FAILURE("Too many elements in array in %s");
4678 for (i = 0; i < arraySize; i++) {
4679 sprintf(keyString, "%d", i);
4681 if (!ArrayGet(array, keyString, &element)) {
4682 M_FAILURE("Invalid key in array in %s");
4685 if (!readIntArg(element, &label, errMsg)
4686 || !RangesetLabelOK(label)) {
4687 M_FAILURE("Invalid rangeset label in array in %s");
4690 deleteLabels[i] = label;
4693 for (i = 0; i < arraySize; i++) {
4694 RangesetForget(rangesetTable, deleteLabels[i]);
4696 } else {
4697 if (!readIntArg(argList[0], &label, errMsg)
4698 || !RangesetLabelOK(label)) {
4699 M_FAILURE("Invalid rangeset label in %s");
4702 if(rangesetTable != NULL) {
4703 RangesetForget(rangesetTable, label);
4707 /* set up result */
4708 result->tag = NO_TAG;
4709 return True;
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;
4718 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
4719 DataValue *result, char **errMsg)
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;
4732 if (nArgs != 1) {
4733 return wrongNArgsErr(errMsg);
4736 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
4737 M_FAILURE("First parameter is not a name string in %s");
4740 result->tag = ARRAY_TAG;
4741 result->val.arrayPtr = ArrayNew();
4743 if (rangesetTable == NULL) {
4744 return True;
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;
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");
4763 strcpy(allocIndexStr, indexStr);
4765 if (!ArrayInsert(result, allocIndexStr, &element))
4766 M_FAILURE("Failed to insert array element in %s");
4768 ++insertIndex;
4773 return True;
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).
4783 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
4784 DataValue *result, char **errMsg)
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;
4792 if (nArgs < 1 || nArgs > 3)
4793 return wrongNArgsErr(errMsg);
4795 if (!readIntArg(argList[0], &label, errMsg)
4796 || !RangesetLabelOK(label)) {
4797 M_FAILURE("First parameter is an invalid rangeset label in %s");
4800 if (rangesetTable == NULL) {
4801 M_FAILURE("Rangeset does not exist in %s");
4804 targetRangeset = RangesetFetch(rangesetTable, label);
4806 if (targetRangeset == NULL) {
4807 M_FAILURE("Rangeset does not exist in %s");
4810 start = end = -1;
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");
4818 if (!RangesetAddBetween(targetRangeset, start, end)) {
4819 M_FAILURE("Failure to add selection in %s");
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");
4830 sourceRangeset = RangesetFetch(rangesetTable, label);
4831 if (sourceRangeset == NULL) {
4832 M_FAILURE("Second rangeset does not exist in %s");
4835 RangesetAdd(targetRangeset, sourceRangeset);
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;
4843 if (!readIntArg(argList[2], &end, errMsg)) {
4844 return False;
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;}
4855 if ((start != end) && !RangesetAddBetween(targetRangeset, start, end)) {
4856 M_FAILURE("Failed to add range in %s");
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);
4865 else {
4866 index = 0;
4869 /* set up result */
4870 result->tag = INT_TAG;
4871 result->val.n = index;
4872 return True;
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.
4882 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
4883 DataValue *result, char **errMsg)
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;
4891 if (nArgs < 1 || nArgs > 3) {
4892 return wrongNArgsErr(errMsg);
4895 if (!readIntArg(argList[0], &label, errMsg)
4896 || !RangesetLabelOK(label)) {
4897 M_FAILURE("First parameter is an invalid rangeset label in %s");
4900 if (rangesetTable == NULL) {
4901 M_FAILURE("Rangeset does not exist in %s");
4904 targetRangeset = RangesetFetch(rangesetTable, label);
4905 if (targetRangeset == NULL) {
4906 M_FAILURE("Rangeset does not exist in %s");
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");
4915 RangesetRemoveBetween(targetRangeset, start, end);
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");
4925 sourceRangeset = RangesetFetch(rangesetTable, label);
4926 if (sourceRangeset == NULL) {
4927 M_FAILURE("Second rangeset does not exist in %s");
4929 RangesetRemove(targetRangeset, sourceRangeset);
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;
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;}
4947 RangesetRemoveBetween(targetRangeset, start, end);
4950 /* set up result */
4951 result->tag = NO_TAG;
4952 return True;
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.
4961 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
4962 DataValue *result, char **errMsg)
4965 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4966 Rangeset *rangeset;
4967 int label = 0;
4969 if (nArgs != 1)
4970 return wrongNArgsErr(errMsg);
4972 if (!readIntArg(argList[0], &label, errMsg)
4973 || !RangesetLabelOK(label)) {
4974 M_FAILURE("First parameter is an invalid rangeset label in %s");
4977 if (rangesetTable == NULL) {
4978 M_FAILURE("Rangeset does not exist in %s");
4981 rangeset = RangesetFetch(rangesetTable, label);
4982 if (rangeset == NULL) {
4983 M_FAILURE("Rangeset does not exist in %s");
4986 if (RangesetInverse(rangeset) < 0) {
4987 M_FAILURE("Problem inverting rangeset in %s");
4990 /* set up result */
4991 result->tag = NO_TAG;
4992 return True;
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.
5001 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
5002 DataValue *result, char **errMsg)
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;
5011 if (nArgs != 1)
5012 return wrongNArgsErr(errMsg);
5014 if (!readIntArg(argList[0], &label, errMsg)
5015 || !RangesetLabelOK(label)) {
5016 M_FAILURE("First parameter is an invalid rangeset label in %s");
5019 if (rangesetTable != NULL) {
5020 rangeset = RangesetFetch(rangesetTable, label);
5023 RangesetGetInfo(rangeset, &defined, &label, &count, &color, &name, &mode);
5025 /* set up result */
5026 result->tag = ARRAY_TAG;
5027 result->val.arrayPtr = ArrayNew();
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");
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");
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");
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");
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");
5058 return True;
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
5067 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
5068 DataValue *result, char **errMsg)
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;
5077 if (nArgs < 1 || nArgs > 2) {
5078 return wrongNArgsErr(errMsg);
5081 if (!readIntArg(argList[0], &label, errMsg)
5082 || !RangesetLabelOK(label)) {
5083 M_FAILURE("First parameter is an invalid rangeset label in %s");
5086 if (rangesetTable == NULL) {
5087 M_FAILURE("Rangeset does not exist in %s");
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;
5099 else if (nArgs == 2) {
5100 if (!readIntArg(argList[1], &rangeIndex, errMsg)) {
5101 return False;
5103 ok = RangesetFindRangeNo(rangeset, rangeIndex-1, &start, &end);
5107 /* set up result */
5108 result->tag = ARRAY_TAG;
5109 result->val.arrayPtr = ArrayNew();
5111 if (!ok)
5112 return True;
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");
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");
5124 return True;
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.
5133 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
5134 int nArgs, DataValue *result, char **errMsg)
5136 textBuffer *buffer = window->buffer;
5137 RangesetTable *rangesetTable = buffer->rangesetTable;
5138 Rangeset *rangeset;
5139 int pos, rangeIndex, maxpos;
5140 int label = 0;
5142 if (nArgs < 1 || nArgs > 2) {
5143 return wrongNArgsErr(errMsg);
5146 if (!readIntArg(argList[0], &label, errMsg)
5147 || !RangesetLabelOK(label)) {
5148 M_FAILURE("First parameter is an invalid rangeset label in %s");
5151 if (rangesetTable == NULL) {
5152 M_FAILURE("Rangeset does not exist in %s");
5155 rangeset = RangesetFetch(rangesetTable, label);
5156 if (rangeset == NULL) {
5157 M_FAILURE("Rangeset does not exist in %s");
5160 if (nArgs == 1) {
5161 pos = TextGetCursorPos(window->lastFocus);
5163 else if (nArgs == 2) {
5164 if (!readIntArg(argList[1], &pos, errMsg))
5165 return False;
5168 maxpos = buffer->length;
5169 if (pos < 0 || pos > maxpos) {
5170 rangeIndex = 0;
5172 else {
5173 rangeIndex = RangesetFindRangeOfPos(rangeset, pos, False) + 1;
5176 /* set up result */
5177 result->tag = INT_TAG;
5178 result->val.n = rangeIndex;
5179 return True;
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.
5187 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
5188 int nArgs, DataValue *result, char **errMsg)
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;
5197 if (nArgs != 2) {
5198 return wrongNArgsErr(errMsg);
5201 if (!readIntArg(argList[0], &label, errMsg)
5202 || !RangesetLabelOK(label)) {
5203 M_FAILURE("First parameter is an invalid rangeset label in %s");
5206 if (rangesetTable == NULL) {
5207 M_FAILURE("Rangeset does not exist in %s");
5210 rangeset = RangesetFetch(rangesetTable, label);
5211 if (rangeset == NULL) {
5212 M_FAILURE("Rangeset does not exist in %s");
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");
5222 RangesetAssignColorName(rangeset, color_name);
5224 /* set up result */
5225 result->tag = NO_TAG;
5226 return True;
5230 ** Set the name of a range set's ranges. Returns
5231 ** true if the rangeset is valid.
5233 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
5234 int nArgs, DataValue *result, char **errMsg)
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;
5243 if (nArgs != 2) {
5244 return wrongNArgsErr(errMsg);
5247 if (!readIntArg(argList[0], &label, errMsg)
5248 || !RangesetLabelOK(label)) {
5249 M_FAILURE("First parameter is an invalid rangeset label in %s");
5252 if (rangesetTable == NULL) {
5253 M_FAILURE("Rangeset does not exist in %s");
5256 rangeset = RangesetFetch(rangesetTable, label);
5257 if (rangeset == NULL) {
5258 M_FAILURE("Rangeset does not exist in %s");
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");
5268 RangesetAssignName(rangeset, name);
5270 /* set up result */
5271 result->tag = NO_TAG;
5272 return True;
5276 ** Change a range's modification response. Returns true if the rangeset is
5277 ** valid and the response type name is valid.
5279 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
5280 int nArgs, DataValue *result, char **errMsg)
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;
5290 if (nArgs < 1 || nArgs > 2) {
5291 return wrongNArgsErr(errMsg);
5294 if (!readIntArg(argList[0], &label, errMsg)
5295 || !RangesetLabelOK(label)) {
5296 M_FAILURE("First parameter is an invalid rangeset label in %s");
5299 if (rangesetTable == NULL) {
5300 M_FAILURE("Rangeset does not exist in %s");
5303 rangeset = RangesetFetch(rangesetTable, label);
5304 if (rangeset == NULL) {
5305 M_FAILURE("Rangeset does not exist in %s");
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");
5317 ok = RangesetChangeModifyResponse(rangeset, update_fn_name);
5319 if (!ok) {
5320 M_FAILURE("Second parameter is not a valid mode in %s");
5323 /* set up result */
5324 result->tag = NO_TAG;
5325 return True;
5328 /* -------------------------------------------------------------------------- */
5332 ** Routines to get details directly from the window.
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
5352 static int fillStyleResult(DataValue *result, char **errMsg,
5353 WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
5354 Boolean includeName, int patCode, int bufferPos)
5356 DataValue DV;
5357 char colorValue[20];
5358 int r, g, b;
5360 /* initialize array */
5361 result->tag = ARRAY_TAG;
5362 result->val.arrayPtr = ArrayNew();
5364 /* the following array entries will be strings */
5365 DV.tag = STRING_TAG;
5367 if (includeName) {
5368 /* insert style name */
5369 if (preallocatedStyleName) {
5370 DV.val.str.rep = styleName;
5371 DV.val.str.len = strlen(styleName);
5373 else {
5374 AllocNStringCpy(&DV.val.str, styleName);
5376 M_STR_ALLOC_ASSERT(DV);
5377 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5378 M_ARRAY_INSERT_FAILURE();
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();
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();
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();
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();
5422 /* the following array entries will be integers */
5423 DV.tag = INT_TAG;
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();
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();
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();
5445 return True;
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
5456 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5457 DataValue *result, char **errMsg)
5459 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5460 char *styleName;
5462 /* Validate number of arguments */
5463 if (nArgs != 1) {
5464 return wrongNArgsErr(errMsg);
5467 /* Prepare result */
5468 result->tag = ARRAY_TAG;
5469 result->val.arrayPtr = NULL;
5471 if (!readStringArg(argList[0], &styleName, stringStorage[0], errMsg)) {
5472 M_FAILURE("First parameter is not a string in %s");
5475 if (!NamedStyleExists(styleName)) {
5476 /* if the given name is invalid we just return an empty array. */
5477 return True;
5480 return fillStyleResult(result, errMsg, window,
5481 styleName, (argList[0].tag == STRING_TAG), False, 0, -1);
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
5496 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5497 DataValue *result, char **errMsg)
5499 int patCode;
5500 int bufferPos;
5501 textBuffer *buf = window->buffer;
5503 /* Validate number of arguments */
5504 if (nArgs != 1) {
5505 return wrongNArgsErr(errMsg);
5508 /* Prepare result */
5509 result->tag = ARRAY_TAG;
5510 result->val.arrayPtr = NULL;
5512 if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5513 return False;
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;
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;
5530 return fillStyleResult(result, errMsg, window,
5531 HighlightStyleOfCode(window, patCode), False, True, patCode, bufferPos);
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
5544 static int fillPatternResult(DataValue *result, char **errMsg,
5545 WindowInfo *window, char *patternName, Boolean preallocatedPatternName,
5546 Boolean includeName, char* styleName, int bufferPos)
5548 DataValue DV;
5550 /* initialize array */
5551 result->tag = ARRAY_TAG;
5552 result->val.arrayPtr = ArrayNew();
5554 /* the following array entries will be strings */
5555 DV.tag = STRING_TAG;
5557 if (includeName) {
5558 /* insert pattern name */
5559 if (preallocatedPatternName) {
5560 DV.val.str.rep = patternName;
5561 DV.val.str.len = strlen(patternName);
5563 else {
5564 AllocNStringCpy(&DV.val.str, patternName);
5566 M_STR_ALLOC_ASSERT(DV);
5567 if (!ArrayInsert(result, PERM_ALLOC_STR("pattern"), &DV)) {
5568 M_ARRAY_INSERT_FAILURE();
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();
5579 /* the following array entries will be integers */
5580 DV.tag = INT_TAG;
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();
5591 return True;
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
5601 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5602 DataValue *result, char **errMsg)
5604 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5605 char *patternName = NULL;
5606 highlightPattern *pattern;
5608 /* Begin of building the result. */
5609 result->tag = ARRAY_TAG;
5610 result->val.arrayPtr = NULL;
5612 /* Validate number of arguments */
5613 if (nArgs != 1) {
5614 return wrongNArgsErr(errMsg);
5617 if (!readStringArg(argList[0], &patternName, stringStorage[0], errMsg)) {
5618 M_FAILURE("First parameter is not a string in %s");
5621 pattern = FindPatternOfWindow(window, patternName);
5622 if (pattern == NULL) {
5623 /* The pattern's name is unknown. */
5624 return True;
5627 return fillPatternResult(result, errMsg, window, patternName,
5628 (argList[0].tag == STRING_TAG), False, pattern->style, -1);
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
5639 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5640 DataValue *result, char **errMsg)
5642 int bufferPos = -1;
5643 textBuffer *buffer = window->buffer;
5644 int patCode = 0;
5646 /* Begin of building the result. */
5647 result->tag = ARRAY_TAG;
5648 result->val.arrayPtr = NULL;
5650 /* Validate number of arguments */
5651 if (nArgs != 1) {
5652 return wrongNArgsErr(errMsg);
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;
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;
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;
5677 return fillPatternResult(result, errMsg, window,
5678 HighlightNameOfCode(window, patCode), False, True,
5679 HighlightStyleOfCode(window, patCode), bufferPos);
5682 static int wrongNArgsErr(char **errMsg)
5684 *errMsg = "Wrong number of arguments to function %s";
5685 return False;
5688 static int tooFewArgsErr(char **errMsg)
5690 *errMsg = "Too few arguments to function %s";
5691 return False;
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.
5699 static int strCaseCmp(char *str1, char *str2)
5701 char *c1, *c2;
5703 for (c1 = str1, c2 = str2;
5704 (*c1 != '\0' && *c2 != '\0')
5705 && toupper((unsigned char)*c1) == toupper((unsigned char)*c2);
5706 ++c1, ++c2)
5710 if (((unsigned char)toupper((unsigned char)*c1))
5711 > ((unsigned char)toupper((unsigned char)*c2)))
5713 return(1);
5714 } else if (((unsigned char)toupper((unsigned char)*c1))
5715 < ((unsigned char)toupper((unsigned char)*c2)))
5717 return(-1);
5718 } else
5720 return(0);
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.
5729 static int readIntArg(DataValue dv, int *result, char **errMsg)
5731 char *c;
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;
5742 sscanf(dv.val.str.rep, "%d", result);
5743 return True;
5746 typeError:
5747 *errMsg = "%s called with non-integer argument";
5748 return False;
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.
5758 static int readStringArg(DataValue dv, char **result, char *stringStorage,
5759 char **errMsg)
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;
5769 *errMsg = "%s called with unknown object";
5770 return False;