Minor fix.
[nedit.git] / source / macro.c
blob11bda07cbdf3aba1e57f8d4b4e2604e7a059bf16
1 static const char CVSID[] = "$Id: macro.c,v 1.98 2005/02/11 02:42:18 ajbj 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 "highlight.h"
57 #include "highlightData.h"
58 #include "rangeset.h"
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <ctype.h>
64 #include <errno.h>
65 #ifdef VMS
66 #include "../util/VMSparam.h"
67 #include <types.h>
68 #include <stat.h>
69 #include <unixio.h>
70 #else
71 #include <sys/types.h>
72 #include <sys/stat.h>
73 #ifndef __MVS__
74 #include <sys/param.h>
75 #endif
76 #include <fcntl.h>
77 #endif /*VMS*/
79 #include <X11/Intrinsic.h>
80 #include <X11/keysym.h>
81 #include <Xm/Xm.h>
82 #include <Xm/CutPaste.h>
83 #include <Xm/Form.h>
84 #include <Xm/RowColumn.h>
85 #include <Xm/LabelG.h>
86 #include <Xm/List.h>
87 #include <Xm/ToggleB.h>
88 #include <Xm/DialogS.h>
89 #include <Xm/MessageB.h>
90 #include <Xm/SelectioB.h>
91 #include <Xm/PushB.h>
92 #include <Xm/Text.h>
93 #include <Xm/Separator.h>
95 #ifdef HAVE_DEBUG_H
96 #include "../debug.h"
97 #endif
99 /* Maximum number of actions in a macro and args in
100 an action (to simplify the reader) */
101 #define MAX_MACRO_ACTIONS 1024
102 #define MAX_ACTION_ARGS 40
104 /* How long to wait (msec) before putting up Macro Command banner */
105 #define BANNER_WAIT_TIME 6000
107 /* The following definitions cause an exit from the macro with a message */
108 /* added if (1) to remove compiler warnings on solaris */
109 #define M_FAILURE(s) do { *errMsg = s; if (1) return False; } while (0)
110 #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)
111 #define M_ARRAY_INSERT_FAILURE() M_FAILURE("array element failed to insert: %s")
113 /* Data attached to window during shell command execution with
114 information for controling and communicating with the process */
115 typedef struct {
116 XtIntervalId bannerTimeoutID;
117 XtWorkProcId continueWorkProcID;
118 char bannerIsUp;
119 char closeOnCompletion;
120 Program *program;
121 RestartData *context;
122 Widget dialog;
123 } macroCmdInfo;
125 /* Widgets and global data for Repeat dialog */
126 typedef struct {
127 WindowInfo *forWindow;
128 char *lastCommand;
129 Widget shell, repeatText, lastCmdToggle;
130 Widget inSelToggle, toEndToggle;
131 } repeatDialog;
133 static void cancelLearn(void);
134 static void runMacro(WindowInfo *window, Program *prog);
135 static void finishMacroCmdExecution(WindowInfo *window);
136 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData);
137 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData);
138 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event);
139 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData);
140 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
141 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
142 XEvent *event, String *params, Cardinal *numParams);
143 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
144 XEvent *event, String *params, Cardinal *numParams);
145 static char *actionToString(Widget w, char *actionName, XEvent *event,
146 String *params, Cardinal numParams);
147 static int isMouseAction(const char *action);
148 static int isRedundantAction(const char *action);
149 static int isIgnoredAction(const char *action);
150 static int readCheckMacroString(Widget dialogParent, char *string,
151 WindowInfo *runWindow, const char *errIn, char **errPos);
152 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id);
153 static Boolean continueWorkProc(XtPointer clientData);
154 static int escapeStringChars(char *fromString, char *toString);
155 static int escapedStringLength(char *string);
156 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
157 DataValue *result, char **errMsg);
158 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
159 DataValue *result, char **errMsg);
160 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
161 DataValue *result, char **errMsg);
162 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
163 DataValue *result, char **errMsg);
164 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
165 DataValue *result, char **errMsg);
166 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
167 DataValue *result, char **errMsg);
168 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
169 DataValue *result, char **errMsg);
170 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
171 DataValue *result, char **errMsg);
172 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
173 DataValue *result, char **errMsg);
174 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
175 DataValue *result, char **errMsg);
176 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
177 DataValue *result, char **errMsg);
178 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
179 DataValue *result, char **errMsg);
180 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
181 DataValue *result, char **errMsg);
182 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
183 DataValue *result, char **errMsg);
184 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
185 DataValue *result, char **errMsg);
186 static int writeOrAppendFile(int append, WindowInfo *window,
187 DataValue *argList, int nArgs, DataValue *result, char **errMsg);
188 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
189 DataValue *result, char **errMsg);
190 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
191 DataValue *result, char **errMsg);
192 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
193 DataValue *result, char **errMsg);
194 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
195 DataValue *result, char **errMsg);
196 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
197 DataValue *result, char **errMsg);
198 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
199 DataValue *result, char **errMsg);
200 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
201 DataValue *result, char **errMsg);
202 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
203 DataValue *result, char **errMsg);
204 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
205 DataValue *result, char **errMsg);
206 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
207 DataValue *result, char **errMsg);
208 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
209 DataValue *result, char **errMsg);
210 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
211 DataValue *result, char **errMsg);
212 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
213 DataValue *result, char **errMsg);
214 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
215 DataValue *result, char **errMsg);
216 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
217 DataValue *result, char **errMsg);
218 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData);
219 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData);
220 #ifdef LESSTIF_VERSION
221 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
222 Boolean *cont);
223 #endif /* LESSTIF_VERSION */
224 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
225 DataValue *result, char **errMsg);
226 static void stringDialogBtnCB(Widget w, XtPointer clientData,
227 XtPointer callData);
228 static void stringDialogCloseCB(Widget w, XtPointer clientData,
229 XtPointer callData);
230 #ifdef LESSTIF_VERSION
231 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
232 Boolean *cont);
233 #endif /* LESSTIF_VERSION */
234 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
235 DataValue *result, char **errMsg);
236 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
237 DataValue *result, char **errMsg);
238 /* T Balinski */
239 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
240 DataValue *result, char **errMsg);
241 static void listDialogBtnCB(Widget w, XtPointer clientData,
242 XtPointer callData);
243 static void listDialogCloseCB(Widget w, XtPointer clientData,
244 XtPointer callData);
245 /* T Balinski End */
246 #ifdef LESSTIF_VERSION
247 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
248 Boolean *cont);
249 #endif /* LESSTIF_VERSION */
250 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
251 DataValue *result, char **errMsg);
252 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
253 DataValue *result, char **errMsg);
254 /* DISASBLED for 5.4
255 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
256 int nArgs, DataValue *result, char **errMsg);
258 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
259 DataValue *result, char **errMsg);
260 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
261 DataValue *result, char **errMsg);
262 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
263 DataValue *result, char **errMsg);
264 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
265 DataValue *result, char **errMsg);
266 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
267 DataValue *result, char **errMsg);
268 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
269 DataValue *result, char **errMsg);
270 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
271 DataValue *result, char **errMsg);
272 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
273 DataValue *result, char **errMsg);
274 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
275 DataValue *result, char **errMsg);
276 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
277 DataValue *result, char **errMsg);
278 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
279 DataValue *result, char **errMsg);
280 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
281 DataValue *result, char **errMsg);
282 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
283 DataValue *result, char **errMsg);
284 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
285 DataValue *result, char **errMsg);
286 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
287 DataValue *result, char **errMsg);
288 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
289 DataValue *result, char **errMsg);
290 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
291 DataValue *result, char **errMsg);
292 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
293 DataValue *result, char **errMsg);
294 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
295 DataValue *result, char **errMsg);
296 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
297 DataValue *result, char **errMsg);
298 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
299 DataValue *result, char **errMsg);
300 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
301 DataValue *result, char **errMsg);
302 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
303 DataValue *result, char **errMsg);
304 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
305 DataValue *result, char **errMsg);
306 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
307 DataValue *result, char **errMsg);
308 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
309 DataValue *result, char **errMsg);
310 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
311 DataValue *result, char **errMsg);
312 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
313 DataValue *result, char **errMsg);
314 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
315 DataValue *result, char **errMsg);
316 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
317 DataValue *result, char **errMsg);
318 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
319 DataValue *result, char **errMsg);
320 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
321 DataValue *result, char **errMsg);
322 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
323 DataValue *result, char **errMsg);
324 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
325 DataValue *result, char **errMsg);
326 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
327 DataValue *result, char **errMsg);
328 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
329 DataValue *result, char **errMsg);
330 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
331 DataValue *result, char **errMsg);
332 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
333 DataValue *result, char **errMsg);
334 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
335 DataValue *result, char **errMsg);
336 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
337 DataValue *result, char **errMsg);
338 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
339 DataValue *result, char **errMsg);
340 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
341 DataValue *result, char **errMsg);
342 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
343 DataValue *result, char **errMsg);
344 static int calltipIDMV(WindowInfo *window, DataValue *argList, int nArgs,
345 DataValue *result, char **errMsg);
346 static int readSearchArgs(DataValue *argList, int nArgs, int*searchDirection,
347 int *searchType, int *wrap, char **errMsg);
348 static int wrongNArgsErr(char **errMsg);
349 static int tooFewArgsErr(char **errMsg);
350 static int strCaseCmp(char *str1, char *str2);
351 static int readIntArg(DataValue dv, int *result, char **errMsg);
352 static int readStringArg(DataValue dv, char **result, char *stringStorage,
353 char **errMsg);
354 /* DISABLED FOR 5.4
355 static int backlightStringMV(WindowInfo *window, DataValue *argList,
356 int nArgs, DataValue *result, char **errMsg);
358 static int rangesetListMV(WindowInfo *window, DataValue *argList,
359 int nArgs, DataValue *result, char **errMsg);
360 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
361 DataValue *result, char **errMsg);
362 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
363 DataValue *result, char **errMsg);
364 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
365 DataValue *result, char **errMsg);
366 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
367 DataValue *result, char **errMsg);
368 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
369 DataValue *result, char **errMsg);
370 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
371 DataValue *result, char **errMsg);
372 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
373 DataValue *result, char **errMsg);
374 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
375 DataValue *result, char **errMsg);
376 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
377 int nArgs, DataValue *result, char **errMsg);
378 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
379 int nArgs, DataValue *result, char **errMsg);
380 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
381 int nArgs, DataValue *result, char **errMsg);
382 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
383 int nArgs, DataValue *result, char **errMsg);
385 static int fillPatternResult(DataValue *result, char **errMsg, WindowInfo *window,
386 char *patternName, Boolean preallocatedPatternName, Boolean includeName,
387 char *styleName, int bufferPos);
388 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
389 DataValue *result, char **errMsg);
390 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
391 DataValue *result, char **errMsg);
393 static int fillStyleResult(DataValue *result, char **errMsg,
394 WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
395 Boolean includeName, int patCode, int bufferPos);
396 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
397 DataValue *result, char **errMsg);
398 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
399 DataValue *result, char **errMsg);
401 /* Built-in subroutines and variables for the macro language */
402 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS,
403 dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS,
404 setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS,
405 searchStringMS, substringMS, replaceSubstringMS, readFileMS,
406 writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS,
407 replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS,
408 shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS,
409 tolowerMS, listDialogMS, getenvMS,
410 stringCompareMS, splitMS, calltipMS, killCalltipMS,
411 /* DISABLED for 5.4 setBacklightStringMS,*/
412 rangesetCreateMS, rangesetDestroyMS,
413 rangesetAddMS, rangesetSubtractMS, rangesetInvertMS,
414 rangesetInfoMS, rangesetRangeMS, rangesetIncludesPosMS,
415 rangesetSetColorMS, rangesetSetNameMS, rangesetSetModeMS,
416 rangesetGetByNameMS,
417 getPatternByNameMS, getPatternAtPosMS,
418 getStyleByNameMS, getStyleAtPosMS
420 #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs)
421 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print",
422 "dialog", "string_dialog", "replace_range", "replace_selection",
423 "set_cursor_pos", "get_character", "min", "max", "search",
424 "search_string", "substring", "replace_substring", "read_file",
425 "write_file", "append_file", "beep", "get_selection", "valid_number",
426 "replace_in_string", "select", "select_rectangle", "focus_window",
427 "shell_command", "string_to_clipboard", "clipboard_to_string",
428 "toupper", "tolower", "list_dialog", "getenv",
429 "string_compare", "split", "calltip", "kill_calltip",
430 /* DISABLED for 5.4 "set_backlight_string", */
431 "rangeset_create", "rangeset_destroy",
432 "rangeset_add", "rangeset_subtract", "rangeset_invert",
433 "rangeset_info", "rangeset_range", "rangeset_includes",
434 "rangeset_set_color", "rangeset_set_name", "rangeset_set_mode",
435 "rangeset_get_by_name",
436 "get_pattern_by_name", "get_pattern_at_pos",
437 "get_style_by_name", "get_style_at_pos"
439 static BuiltInSubr SpecialVars[] = {cursorMV, lineMV, columnMV,
440 fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV,
441 selectionLeftMV, selectionRightMV, wrapMarginMV, tabDistMV,
442 emTabDistMV, useTabsMV, languageModeMV, modifiedMV,
443 statisticsLineMV, incSearchLineMV, showLineNumbersMV,
444 autoIndentMV, wrapTextMV, highlightSyntaxMV,
445 makeBackupCopyMV, incBackupMV, showMatchingMV,
446 overTypeModeMV, readOnlyMV, lockedMV, fileFormatMV,
447 fontNameMV, fontNameItalicMV,
448 fontNameBoldMV, fontNameBoldItalicMV, subscriptSepMV,
449 minFontWidthMV, maxFontWidthMV, topLineMV, numDisplayLinesMV,
450 displayWidthMV, activePaneMV, nPanesMV, emptyArrayMV,
451 serverNameMV, calltipIDMV,
452 /* DISABLED for 5.4 backlightStringMV, */
453 rangesetListMV
455 #define N_SPECIAL_VARS (sizeof SpecialVars/sizeof *SpecialVars)
456 static const char *SpecialVarNames[N_SPECIAL_VARS] = {"$cursor", "$line", "$column",
457 "$file_name", "$file_path", "$text_length", "$selection_start",
458 "$selection_end", "$selection_left", "$selection_right",
459 "$wrap_margin", "$tab_dist", "$em_tab_dist", "$use_tabs",
460 "$language_mode", "$modified",
461 "$statistics_line", "$incremental_search_line", "$show_line_numbers",
462 "$auto_indent", "$wrap_text", "$highlight_syntax",
463 "$make_backup_copy", "$incremental_backup", "$show_matching",
464 "$overtype_mode", "$read_only", "$locked", "$file_format",
465 "$font_name", "$font_name_italic",
466 "$font_name_bold", "$font_name_bold_italic", "$sub_sep",
467 "$min_font_width", "$max_font_width", "$top_line", "$n_display_lines",
468 "$display_width", "$active_pane", "$n_panes", "$empty_array",
469 "$server_name", "$calltip_ID",
470 /* DISABLED for 5.4 "$backlight_string", */
471 "$rangeset_list"
474 /* Global symbols for returning values from built-in functions */
475 #define N_RETURN_GLOBALS 5
476 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS,
477 SHELL_CMD_STATUS, LIST_DIALOG_BUTTON};
478 static const char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button",
479 "$search_end", "$read_status", "$shell_cmd_status",
480 "$list_dialog_button"};
481 static Symbol *ReturnGlobals[N_RETURN_GLOBALS];
483 /* List of actions not useful when learning a macro sequence (also see below) */
484 static char* IgnoredActions[] = {"focusIn", "focusOut"};
486 /* List of actions intended to be attached to mouse buttons, which the user
487 must be warned can't be recorded in a learn/replay sequence */
488 static const char* MouseActions[] = {"grab_focus", "extend_adjust", "extend_start",
489 "extend_end", "secondary_or_drag_adjust", "secondary_adjust",
490 "secondary_or_drag_start", "secondary_start", "move_destination",
491 "move_to", "move_to_or_end_drag", "copy_to", "copy_to_or_end_drag",
492 "exchange", "process_bdrag", "mouse_pan"};
494 /* List of actions to not record because they
495 generate further actions, more suitable for recording */
496 static const char* RedundantActions[] = {"open_dialog", "save_as_dialog",
497 "revert_to_saved_dialog", "include_file_dialog", "load_macro_file_dialog",
498 "load_tags_file_dialog", "find_dialog", "replace_dialog",
499 "goto_line_number_dialog", "mark_dialog", "goto_mark_dialog",
500 "control_code_dialog", "filter_selection_dialog", "execute_command_dialog",
501 "repeat_dialog", "start_incremental_find"};
503 /* The last command executed (used by the Repeat command) */
504 static char *LastCommand = NULL;
506 /* The current macro to execute on Replay command */
507 static char *ReplayMacro = NULL;
509 /* Buffer where macro commands are recorded in Learn mode */
510 static textBuffer *MacroRecordBuf = NULL;
512 /* Action Hook id for recording actions for Learn mode */
513 static XtActionHookId MacroRecordActionHook = 0;
515 /* Window where macro recording is taking place */
516 static WindowInfo *MacroRecordWindow = NULL;
518 /* Arrays for translating escape characters in escapeStringChars */
519 static char ReplaceChars[] = "\\\"ntbrfav";
520 static char EscapeChars[] = "\\\"\n\t\b\r\f\a\v";
523 ** Install built-in macro subroutines and special variables for accessing
524 ** editor information
526 void RegisterMacroSubroutines(void)
528 static DataValue subrPtr = {NO_TAG, {0}}, noValue = {NO_TAG, {0}};
529 unsigned i;
531 /* Install symbols for built-in routines and variables, with pointers
532 to the appropriate c routines to do the work */
533 for (i=0; i<N_MACRO_SUBRS; i++) {
534 subrPtr.val.subr = MacroSubrs[i];
535 InstallSymbol(MacroSubrNames[i], C_FUNCTION_SYM, subrPtr);
537 for (i=0; i<N_SPECIAL_VARS; i++) {
538 subrPtr.val.subr = SpecialVars[i];
539 InstallSymbol(SpecialVarNames[i], PROC_VALUE_SYM, subrPtr);
542 /* Define global variables used for return values, remember their
543 locations so they can be set without a LookupSymbol call */
544 for (i=0; i<N_RETURN_GLOBALS; i++)
545 ReturnGlobals[i] = InstallSymbol(ReturnGlobalNames[i], GLOBAL_SYM,
546 noValue);
549 #define MAX_LEARN_MSG_LEN ((2 * MAX_ACCEL_LEN) + 60)
550 void BeginLearn(WindowInfo *window)
552 WindowInfo *win;
553 XmString s;
554 XmString xmFinish;
555 XmString xmCancel;
556 char *cFinish;
557 char *cCancel;
558 char message[MAX_LEARN_MSG_LEN];
560 /* If we're already in learn mode, return */
561 if (MacroRecordActionHook != 0)
562 return;
564 /* dim the inappropriate menus and items, and undim finish and cancel */
565 for (win=WindowList; win!=NULL; win=win->next) {
566 if (!IsTopDocument(win))
567 continue;
568 XtSetSensitive(win->learnItem, False);
570 SetSensitive(window, window->finishLearnItem, True);
571 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
572 s=XmStringCreateSimple("Cancel Learn"), NULL);
573 XmStringFree(s);
574 SetSensitive(window, window->cancelMacroItem, True);
576 /* Mark the window where learn mode is happening */
577 MacroRecordWindow = window;
579 /* Allocate a text buffer for accumulating the macro strings */
580 MacroRecordBuf = BufCreate();
582 /* Add the action hook for recording the actions */
583 MacroRecordActionHook =
584 XtAppAddActionHook(XtWidgetToApplicationContext(window->shell),
585 learnActionHook, window);
587 /* Extract accelerator texts from menu PushButtons */
588 XtVaGetValues(window->finishLearnItem, XmNacceleratorText, &xmFinish, NULL);
589 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
591 /* Translate Motif strings to char* */
592 cFinish = GetXmStringText(xmFinish);
593 cCancel = GetXmStringText(xmCancel);
595 /* Free Motif Strings */
596 XmStringFree(xmFinish);
597 XmStringFree(xmCancel);
599 /* Create message */
600 if (cFinish[0] == '\0') {
601 if (cCancel[0] == '\0') {
602 strncpy(message, "Learn Mode -- Use menu to finish or cancel",
603 MAX_LEARN_MSG_LEN);
604 message[MAX_LEARN_MSG_LEN - 1] = '\0';
606 else {
607 sprintf(message,
608 "Learn Mode -- Use menu to finish, press %s to cancel",
609 cCancel);
612 else {
613 if (cCancel[0] == '\0') {
614 sprintf(message,
615 "Learn Mode -- Press %s to finish, use menu to cancel",
616 cFinish);
619 else {
620 sprintf(message,
621 "Learn Mode -- Press %s to finish, %s to cancel",
622 cFinish,
623 cCancel);
627 /* Free C-strings */
628 XtFree(cFinish);
629 XtFree(cCancel);
631 /* Put up the learn-mode banner */
632 SetModeMessage(window, message);
635 void AddLastCommandActionHook(XtAppContext context)
637 XtAppAddActionHook(context, lastActionHook, NULL);
640 void FinishLearn(void)
642 WindowInfo *win;
644 /* If we're not in learn mode, return */
645 if (MacroRecordActionHook == 0)
646 return;
648 /* Remove the action hook */
649 XtRemoveActionHook(MacroRecordActionHook);
650 MacroRecordActionHook = 0;
652 /* Free the old learn/replay sequence */
653 if (ReplayMacro != NULL)
654 XtFree(ReplayMacro);
656 /* Store the finished action for the replay menu item */
657 ReplayMacro = BufGetAll(MacroRecordBuf);
659 /* Free the buffer used to accumulate the macro sequence */
660 BufFree(MacroRecordBuf);
662 /* Undim the menu items dimmed during learn */
663 for (win=WindowList; win!=NULL; win=win->next) {
664 if (!IsTopDocument(win))
665 continue;
666 XtSetSensitive(win->learnItem, True);
668 if (IsTopDocument(MacroRecordWindow)) {
669 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
670 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
673 /* Undim the replay and paste-macro buttons */
674 for (win=WindowList; win!=NULL; win=win->next) {
675 if (!IsTopDocument(win))
676 continue;
677 XtSetSensitive(win->replayItem, True);
679 DimPasteReplayBtns(True);
681 /* Clear learn-mode banner */
682 ClearModeMessage(MacroRecordWindow);
686 ** Cancel Learn mode, or macro execution (they're bound to the same menu item)
688 void CancelMacroOrLearn(WindowInfo *window)
690 if (MacroRecordActionHook != 0)
691 cancelLearn();
692 else if (window->macroCmdData != NULL)
693 AbortMacroCommand(window);
696 static void cancelLearn(void)
698 WindowInfo *win;
700 /* If we're not in learn mode, return */
701 if (MacroRecordActionHook == 0)
702 return;
704 /* Remove the action hook */
705 XtRemoveActionHook(MacroRecordActionHook);
706 MacroRecordActionHook = 0;
708 /* Free the macro under construction */
709 BufFree(MacroRecordBuf);
711 /* Undim the menu items dimmed during learn */
712 for (win=WindowList; win!=NULL; win=win->next) {
713 if (!IsTopDocument(win))
714 continue;
715 XtSetSensitive(win->learnItem, True);
717 if (IsTopDocument(MacroRecordWindow)) {
718 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
719 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
722 /* Clear learn-mode banner */
723 ClearModeMessage(MacroRecordWindow);
727 ** Execute the learn/replay sequence stored in "window"
729 void Replay(WindowInfo *window)
731 Program *prog;
732 char *errMsg, *stoppedAt;
734 /* Verify that a replay macro exists and it's not empty and that */
735 /* we're not already running a macro */
736 if (ReplayMacro != NULL &&
737 ReplayMacro[0] != 0 &&
738 window->macroCmdData == NULL) {
739 /* Parse the replay macro (it's stored in text form) and compile it into
740 an executable program "prog" */
741 prog = ParseMacro(ReplayMacro, &errMsg, &stoppedAt);
742 if (prog == NULL) {
743 fprintf(stderr,
744 "NEdit internal error, learn/replay macro syntax error: %s\n",
745 errMsg);
746 return;
749 /* run the executable program */
750 runMacro(window, prog);
755 ** Read the initial NEdit macro file if one exists.
757 void ReadMacroInitFile(WindowInfo *window)
759 const char* autoloadName = GetRCFileName(AUTOLOAD_NM);
760 static int initFileLoaded = False;
762 /* GetRCFileName() might return NULL if an error occurs during
763 creation of the preference file directory. */
764 if (autoloadName != NULL && !initFileLoaded)
766 ReadMacroFile(window, autoloadName, False);
767 initFileLoaded = True;
772 ** Read an NEdit macro file. Extends the syntax of the macro parser with
773 ** define keyword, and allows intermixing of defines with immediate actions.
775 int ReadMacroFile(WindowInfo *window, const char *fileName, int warnNotExist)
777 int result;
778 char *fileString;
780 fileString = ReadAnyTextFile(fileName);
781 if (fileString == NULL){
782 if (errno != ENOENT || warnNotExist)
784 DialogF(DF_ERR, window->shell, 1, "Read Macro",
785 "Error reading macro file %s: %s", "OK", fileName,
786 #ifdef VMS
787 strerror(errno, vaxc$errno));
788 #else
789 strerror(errno));
790 #endif
792 return False;
795 /* Parse fileString */
796 result = readCheckMacroString(window->shell, fileString, window, fileName,
797 NULL);
798 XtFree(fileString);
799 return result;
803 ** Parse and execute a macro string including macro definitions. Report
804 ** parsing errors in a dialog posted over window->shell.
806 int ReadMacroString(WindowInfo *window, char *string, const char *errIn)
808 return readCheckMacroString(window->shell, string, window, errIn, NULL);
812 ** Check a macro string containing definitions for errors. Returns True
813 ** if macro compiled successfully. Returns False and puts up
814 ** a dialog explaining if macro did not compile successfully.
816 int CheckMacroString(Widget dialogParent, char *string, const char *errIn,
817 char **errPos)
819 return readCheckMacroString(dialogParent, string, NULL, errIn, errPos);
823 ** Parse and optionally execute a macro string including macro definitions.
824 ** Report parsing errors in a dialog posted over dialogParent, using the
825 ** string errIn to identify the entity being parsed (filename, macro string,
826 ** etc.). If runWindow is specified, runs the macro against the window. If
827 ** runWindow is passed as NULL, does parse only. If errPos is non-null,
828 ** returns a pointer to the error location in the string.
830 static int readCheckMacroString(Widget dialogParent, char *string,
831 WindowInfo *runWindow, const char *errIn, char **errPos)
833 char *stoppedAt, *inPtr, *namePtr, *errMsg;
834 char subrName[MAX_SYM_LEN];
835 Program *prog;
836 Symbol *sym;
837 DataValue subrPtr;
839 inPtr = string;
840 while (*inPtr != '\0') {
842 /* skip over white space and comments */
843 while (*inPtr==' ' || *inPtr=='\t' || *inPtr=='\n'|| *inPtr=='#') {
844 if (*inPtr == '#')
845 while (*inPtr != '\n' && *inPtr != '\0') inPtr++;
846 else
847 inPtr++;
849 if (*inPtr == '\0')
850 break;
852 /* look for define keyword, and compile and store defined routines */
853 if (!strncmp(inPtr, "define", 6) && (inPtr[6]==' ' || inPtr[6]=='\t')) {
854 inPtr += 6;
855 inPtr += strspn(inPtr, " \t\n");
856 namePtr = subrName;
857 while (isalnum((unsigned char)*inPtr) || *inPtr == '_')
858 *namePtr++ = *inPtr++;
859 *namePtr = '\0';
860 inPtr += strspn(inPtr, " \t\n");
861 if (*inPtr != '{') {
862 if (errPos != NULL) *errPos = stoppedAt;
863 return ParseError(dialogParent, string, inPtr,
864 errIn, "expected '{'");
866 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
867 if (prog == NULL) {
868 if (errPos != NULL) *errPos = stoppedAt;
869 return ParseError(dialogParent, string, stoppedAt,
870 errIn, errMsg);
872 if (runWindow != NULL) {
873 sym = LookupSymbol(subrName);
874 if (sym == NULL) {
875 subrPtr.val.prog = prog;
876 subrPtr.tag = NO_TAG;
877 sym = InstallSymbol(subrName, MACRO_FUNCTION_SYM, subrPtr);
878 } else {
879 if (sym->type == MACRO_FUNCTION_SYM)
880 FreeProgram(sym->value.val.prog);
881 else
882 sym->type = MACRO_FUNCTION_SYM;
883 sym->value.val.prog = prog;
886 inPtr = stoppedAt;
888 /* Parse and execute immediate (outside of any define) macro commands
889 and WAIT for them to finish executing before proceeding. Note that
890 the code below is not perfect. If you interleave code blocks with
891 definitions in a file which is loaded from another macro file, it
892 will probably run the code blocks in reverse order! */
893 } else {
894 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
895 if (prog == NULL) {
896 if (errPos != NULL) *errPos = stoppedAt;
897 return ParseError(dialogParent, string, stoppedAt,
898 errIn, errMsg);
900 if (runWindow != NULL) {
901 XEvent nextEvent;
902 if (runWindow->macroCmdData == NULL) {
903 runMacro(runWindow, prog);
904 while (runWindow->macroCmdData != NULL) {
905 XtAppNextEvent(XtWidgetToApplicationContext(
906 runWindow->shell), &nextEvent);
907 ServerDispatchEvent(&nextEvent);
909 } else
910 RunMacroAsSubrCall(prog);
912 inPtr = stoppedAt;
915 return True;
919 ** Run a pre-compiled macro, changing the interface state to reflect that
920 ** a macro is running, and handling preemption, resumption, and cancellation.
921 ** frees prog when macro execution is complete;
923 static void runMacro(WindowInfo *window, Program *prog)
925 DataValue result;
926 char *errMsg;
927 int stat;
928 macroCmdInfo *cmdData;
929 XmString s;
931 /* If a macro is already running, just call the program as a subroutine,
932 instead of starting a new one, so we don't have to keep a separate
933 context, and the macros will serialize themselves automatically */
934 if (window->macroCmdData != NULL) {
935 RunMacroAsSubrCall(prog);
936 return;
939 /* put up a watch cursor over the waiting window */
940 BeginWait(window->shell);
942 /* enable the cancel menu item */
943 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
944 s=XmStringCreateSimple("Cancel Macro"), NULL);
945 XmStringFree(s);
946 SetSensitive(window, window->cancelMacroItem, True);
948 /* Create a data structure for passing macro execution information around
949 amongst the callback routines which will process i/o and completion */
950 cmdData = (macroCmdInfo *)XtMalloc(sizeof(macroCmdInfo));
951 window->macroCmdData = cmdData;
952 cmdData->bannerIsUp = False;
953 cmdData->closeOnCompletion = False;
954 cmdData->program = prog;
955 cmdData->context = NULL;
956 cmdData->continueWorkProcID = 0;
957 cmdData->dialog = NULL;
959 /* Set up timer proc for putting up banner when macro takes too long */
960 cmdData->bannerTimeoutID = XtAppAddTimeOut(
961 XtWidgetToApplicationContext(window->shell), BANNER_WAIT_TIME,
962 bannerTimeoutProc, window);
964 /* Begin macro execution */
965 stat = ExecuteMacro(window, prog, 0, NULL, &result, &cmdData->context,
966 &errMsg);
968 if (stat == MACRO_ERROR)
970 finishMacroCmdExecution(window);
971 DialogF(DF_ERR, window->shell, 1, "Macro Error",
972 "Error executing macro: %s", "OK", errMsg);
973 return;
976 if (stat == MACRO_DONE) {
977 finishMacroCmdExecution(window);
978 return;
980 if (stat == MACRO_TIME_LIMIT) {
981 ResumeMacroExecution(window);
982 return;
984 /* (stat == MACRO_PREEMPT) Macro was preempted */
988 ** Continue with macro execution after preemption. Called by the routines
989 ** whose actions cause preemption when they have completed their lengthy tasks.
990 ** Re-establishes macro execution work proc. Window must be the window in
991 ** which the macro is executing (the window to which macroCmdData is attached),
992 ** and not the window to which operations are focused.
994 void ResumeMacroExecution(WindowInfo *window)
996 macroCmdInfo *cmdData = (macroCmdInfo *)window->macroCmdData;
998 if (cmdData != NULL)
999 cmdData->continueWorkProcID = XtAppAddWorkProc(
1000 XtWidgetToApplicationContext(window->shell),
1001 continueWorkProc, window);
1005 ** Cancel the macro command in progress (user cancellation via GUI)
1007 void AbortMacroCommand(WindowInfo *window)
1009 if (window->macroCmdData == NULL)
1010 return;
1012 /* If there's both a macro and a shell command executing, the shell command
1013 must have been called from the macro. When called from a macro, shell
1014 commands don't put up cancellation controls of their own, but rely
1015 instead on the macro cancellation mechanism (here) */
1016 #ifndef VMS
1017 if (window->shellCmdData != NULL)
1018 AbortShellCommand(window);
1019 #endif
1021 /* Free the continuation */
1022 FreeRestartData(((macroCmdInfo *)window->macroCmdData)->context);
1024 /* Kill the macro command */
1025 finishMacroCmdExecution(window);
1029 ** Call this before closing a window, to clean up macro references to the
1030 ** window, stop any macro which might be running from it, free associated
1031 ** memory, and check that a macro is not attempting to close the window from
1032 ** which it is run. If this is being called from a macro, and the window
1033 ** this routine is examining is the window from which the macro was run, this
1034 ** routine will return False, and the caller must NOT CLOSE THE WINDOW.
1035 ** Instead, empty it and make it Untitled, and let the macro completion
1036 ** process close the window when the macro is finished executing.
1038 int MacroWindowCloseActions(WindowInfo *window)
1040 macroCmdInfo *mcd, *cmdData = window->macroCmdData;
1041 WindowInfo *w;
1043 if (MacroRecordActionHook != 0 && MacroRecordWindow == window) {
1044 FinishLearn();
1047 /* If no macro is executing in the window, allow the close, but check
1048 if macros executing in other windows have it as focus. If so, set
1049 their focus back to the window from which they were originally run */
1050 if (cmdData == NULL) {
1051 for (w=WindowList; w!=NULL; w=w->next) {
1052 mcd = (macroCmdInfo *)w->macroCmdData;
1053 if (w == MacroRunWindow() && MacroFocusWindow() == window)
1054 SetMacroFocusWindow(MacroRunWindow());
1055 else if (mcd != NULL && mcd->context->focusWindow == window)
1056 mcd->context->focusWindow = mcd->context->runWindow;
1058 return True;
1061 /* If the macro currently running (and therefore calling us, because
1062 execution must otherwise return to the main loop to execute any
1063 commands), is running in this window, tell the caller not to close,
1064 and schedule window close on completion of macro */
1065 if (window == MacroRunWindow()) {
1066 cmdData->closeOnCompletion = True;
1067 return False;
1070 /* Free the continuation */
1071 FreeRestartData(cmdData->context);
1073 /* Kill the macro command */
1074 finishMacroCmdExecution(window);
1075 return True;
1079 ** Clean up after the execution of a macro command: free memory, and restore
1080 ** the user interface state.
1082 static void finishMacroCmdExecution(WindowInfo *window)
1084 macroCmdInfo *cmdData = window->macroCmdData;
1085 int closeOnCompletion = cmdData->closeOnCompletion;
1086 XmString s;
1087 XClientMessageEvent event;
1089 /* Cancel pending timeout and work proc */
1090 if (cmdData->bannerTimeoutID != 0)
1091 XtRemoveTimeOut(cmdData->bannerTimeoutID);
1092 if (cmdData->continueWorkProcID != 0)
1093 XtRemoveWorkProc(cmdData->continueWorkProcID);
1095 /* Clean up waiting-for-macro-command-to-complete mode */
1096 EndWait(window->shell);
1097 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
1098 s=XmStringCreateSimple("Cancel Learn"), NULL);
1099 XmStringFree(s);
1100 SetSensitive(window, window->cancelMacroItem, False);
1101 if (cmdData->bannerIsUp)
1102 ClearModeMessage(window);
1104 /* If a dialog was up, get rid of it */
1105 if (cmdData->dialog != NULL)
1106 XtDestroyWidget(XtParent(cmdData->dialog));
1108 /* Free execution information */
1109 FreeProgram(cmdData->program);
1110 XtFree((char *)cmdData);
1111 window->macroCmdData = NULL;
1113 /* If macro closed its own window, window was made empty and untitled,
1114 but close was deferred until completion. This is completion, so if
1115 the window is still empty, do the close */
1116 if (closeOnCompletion && !window->filenameSet && !window->fileChanged) {
1117 CloseWindow(window);
1118 window = NULL;
1121 /* If no other macros are executing, do garbage collection */
1122 SafeGC();
1124 /* In processing the .neditmacro file (and possibly elsewhere), there
1125 is an event loop which waits for macro completion. Send an event
1126 to wake up that loop, otherwise execution will stall until the user
1127 does something to the window. */
1128 if (!closeOnCompletion) {
1129 event.format = 8;
1130 event.type = ClientMessage;
1131 XSendEvent(XtDisplay(window->shell), XtWindow(window->shell), False,
1132 NoEventMask, (XEvent *)&event);
1137 ** Do garbage collection of strings if there are no macros currently
1138 ** executing. NEdit's macro language GC strategy is to call this routine
1139 ** whenever a macro completes. If other macros are still running (preempted
1140 ** or waiting for a shell command or dialog), this does nothing and therefore
1141 ** defers GC to the completion of the last macro out.
1143 void SafeGC(void)
1145 WindowInfo *win;
1147 for (win=WindowList; win!=NULL; win=win->next)
1148 if (win->macroCmdData != NULL || InSmartIndentMacros(win))
1149 return;
1150 GarbageCollectStrings();
1154 ** Executes macro string "macro" using the lastFocus pane in "window".
1155 ** Reports errors via a dialog posted over "window", integrating the name
1156 ** "errInName" into the message to help identify the source of the error.
1158 void DoMacro(WindowInfo *window, const char *macro, const char *errInName)
1160 Program *prog;
1161 char *errMsg, *stoppedAt, *tMacro;
1162 int macroLen;
1164 /* Add a terminating newline (which command line users are likely to omit
1165 since they are typically invoking a single routine) */
1166 macroLen = strlen(macro);
1167 tMacro = XtMalloc(strlen(macro)+2);
1168 strncpy(tMacro, macro, macroLen);
1169 tMacro[macroLen] = '\n';
1170 tMacro[macroLen+1] = '\0';
1172 /* Parse the macro and report errors if it fails */
1173 prog = ParseMacro(tMacro, &errMsg, &stoppedAt);
1174 if (prog == NULL) {
1175 ParseError(window->shell, tMacro, stoppedAt, errInName, errMsg);
1176 XtFree(tMacro);
1177 return;
1179 XtFree(tMacro);
1181 /* run the executable program (prog is freed upon completion) */
1182 runMacro(window, prog);
1186 ** Get the current Learn/Replay macro in text form. Returned string is a
1187 ** pointer to the stored macro and should not be freed by the caller (and
1188 ** will cease to exist when the next replay macro is installed)
1190 char *GetReplayMacro(void)
1192 return ReplayMacro;
1196 ** Present the user a dialog for "Repeat" command
1198 void RepeatDialog(WindowInfo *window)
1200 Widget form, selBox, radioBox, timesForm;
1201 repeatDialog *rd;
1202 Arg selBoxArgs[1];
1203 char *lastCmdLabel, *parenChar;
1204 XmString s1;
1205 int cmdNameLen;
1207 if (LastCommand == NULL)
1209 DialogF(DF_WARN, window->shell, 1, "Repeat Macro",
1210 "No previous commands or learn/\nreplay sequences to repeat",
1211 "OK");
1212 return;
1215 /* Remeber the last command, since the user is allowed to work in the
1216 window while the dialog is up */
1217 rd = (repeatDialog *)XtMalloc(sizeof(repeatDialog));
1218 rd->lastCommand = XtNewString(LastCommand);
1220 /* make a label for the Last command item of the dialog, which includes
1221 the last executed action name */
1222 parenChar = strchr(LastCommand, '(');
1223 if (parenChar == NULL)
1224 return;
1225 cmdNameLen = parenChar-LastCommand;
1226 lastCmdLabel = XtMalloc(16 + cmdNameLen);
1227 strcpy(lastCmdLabel, "Last Command (");
1228 strncpy(&lastCmdLabel[14], LastCommand, cmdNameLen);
1229 strcpy(&lastCmdLabel[14 + cmdNameLen], ")");
1231 XtSetArg(selBoxArgs[0], XmNautoUnmanage, False);
1232 selBox = CreatePromptDialog(window->shell, "repeat", selBoxArgs, 1);
1233 rd->shell = XtParent(selBox);
1234 XtAddCallback(rd->shell, XmNdestroyCallback, repeatDestroyCB, rd);
1235 XtAddCallback(selBox, XmNokCallback, repeatOKCB, rd);
1236 XtAddCallback(selBox, XmNapplyCallback, repeatApplyCB, rd);
1237 XtAddCallback(selBox, XmNcancelCallback, repeatCancelCB, rd);
1238 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_TEXT));
1239 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_SELECTION_LABEL));
1240 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_HELP_BUTTON));
1241 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_APPLY_BUTTON));
1242 XtVaSetValues(XtParent(selBox), XmNtitle, "Repeat Macro", NULL);
1243 AddMotifCloseCallback(XtParent(selBox), repeatCancelCB, rd);
1245 form = XtVaCreateManagedWidget("form", xmFormWidgetClass, selBox, NULL);
1247 radioBox = XtVaCreateManagedWidget("cmdSrc", xmRowColumnWidgetClass, form,
1248 XmNradioBehavior, True,
1249 XmNorientation, XmHORIZONTAL,
1250 XmNpacking, XmPACK_TIGHT,
1251 XmNtopAttachment, XmATTACH_FORM,
1252 XmNleftAttachment, XmATTACH_FORM, NULL);
1253 rd->lastCmdToggle = XtVaCreateManagedWidget("lastCmdToggle",
1254 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1255 XmNlabelString, s1=XmStringCreateSimple(lastCmdLabel),
1256 XmNmnemonic, 'C', NULL);
1257 XmStringFree(s1);
1258 XtFree(lastCmdLabel);
1259 XtVaCreateManagedWidget("learnReplayToggle",
1260 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1261 XmNlabelString,
1262 s1=XmStringCreateSimple("Learn/Replay"),
1263 XmNmnemonic, 'L',
1264 XmNsensitive, ReplayMacro != NULL, NULL);
1265 XmStringFree(s1);
1267 timesForm = XtVaCreateManagedWidget("form", xmFormWidgetClass, form,
1268 XmNtopAttachment, XmATTACH_WIDGET,
1269 XmNtopWidget, radioBox,
1270 XmNtopOffset, 10,
1271 XmNleftAttachment, XmATTACH_FORM, NULL);
1272 radioBox = XtVaCreateManagedWidget("method", xmRowColumnWidgetClass,
1273 timesForm,
1274 XmNradioBehavior, True,
1275 XmNorientation, XmHORIZONTAL,
1276 XmNpacking, XmPACK_TIGHT,
1277 XmNtopAttachment, XmATTACH_FORM,
1278 XmNbottomAttachment, XmATTACH_FORM,
1279 XmNleftAttachment, XmATTACH_FORM, NULL);
1280 rd->inSelToggle = XtVaCreateManagedWidget("inSelToggle",
1281 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1282 XmNlabelString, s1=XmStringCreateSimple("In Selection"),
1283 XmNmnemonic, 'I', NULL);
1284 XmStringFree(s1);
1285 rd->toEndToggle = XtVaCreateManagedWidget("toEndToggle",
1286 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1287 XmNlabelString, s1=XmStringCreateSimple("To End"),
1288 XmNmnemonic, 'T', NULL);
1289 XmStringFree(s1);
1290 XtVaCreateManagedWidget("nTimesToggle",
1291 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1292 XmNlabelString, s1=XmStringCreateSimple("N Times"),
1293 XmNmnemonic, 'N',
1294 XmNset, True, NULL);
1295 XmStringFree(s1);
1296 rd->repeatText = XtVaCreateManagedWidget("repeatText", xmTextWidgetClass,
1297 timesForm,
1298 XmNcolumns, 5,
1299 XmNtopAttachment, XmATTACH_FORM,
1300 XmNbottomAttachment, XmATTACH_FORM,
1301 XmNleftAttachment, XmATTACH_WIDGET,
1302 XmNleftWidget, radioBox, NULL);
1303 RemapDeleteKey(rd->repeatText);
1305 /* Handle mnemonic selection of buttons and focus to dialog */
1306 AddDialogMnemonicHandler(form, FALSE);
1308 /* Set initial focus */
1309 #if XmVersion >= 1002
1310 XtVaSetValues(form, XmNinitialFocus, timesForm, NULL);
1311 XtVaSetValues(timesForm, XmNinitialFocus, rd->repeatText, NULL);
1312 #endif
1314 /* put up dialog */
1315 rd->forWindow = window;
1316 ManageDialogCenteredOnPointer(selBox);
1319 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData)
1321 repeatDialog *rd = (repeatDialog *)clientData;
1323 if (doRepeatDialogAction(rd, ((XmAnyCallbackStruct *)callData)->event))
1324 XtDestroyWidget(rd->shell);
1327 /* Note that the apply button is not managed in the repeat dialog. The dialog
1328 itself is capable of non-modal operation, but to be complete, it needs
1329 to dynamically update last command, dimming of learn/replay, possibly a
1330 stop button for the macro, and possibly in-selection with selection */
1331 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData)
1333 doRepeatDialogAction((repeatDialog *)clientData,
1334 ((XmAnyCallbackStruct *)callData)->event);
1337 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event)
1339 int nTimes;
1340 char nTimesStr[TYPE_INT_STR_SIZE(int)];
1341 char *params[2];
1343 /* Find out from the dialog how to repeat the command */
1344 if (XmToggleButtonGetState(rd->inSelToggle))
1346 if (!rd->forWindow->buffer->primary.selected)
1348 DialogF(DF_WARN, rd->shell, 1, "Repeat Macro",
1349 "No selection in window to repeat within", "OK");
1350 XmProcessTraversal(rd->inSelToggle, XmTRAVERSE_CURRENT);
1351 return False;
1353 params[0] = "in_selection";
1354 } else if (XmToggleButtonGetState(rd->toEndToggle))
1356 params[0] = "to_end";
1357 } else
1359 if (GetIntTextWarn(rd->repeatText, &nTimes, "number of times", True)
1360 != TEXT_READ_OK)
1362 XmProcessTraversal(rd->repeatText, XmTRAVERSE_CURRENT);
1363 return False;
1365 sprintf(nTimesStr, "%d", nTimes);
1366 params[0] = nTimesStr;
1369 /* Figure out which command user wants to repeat */
1370 if (XmToggleButtonGetState(rd->lastCmdToggle))
1371 params[1] = XtNewString(rd->lastCommand);
1372 else {
1373 if (ReplayMacro == NULL)
1374 return False;
1375 params[1] = XtNewString(ReplayMacro);
1378 /* call the action routine repeat_macro to do the work */
1379 XtCallActionProc(rd->forWindow->lastFocus, "repeat_macro", event, params,2);
1380 XtFree(params[1]);
1381 return True;
1384 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData)
1386 repeatDialog *rd = (repeatDialog *)clientData;
1388 XtDestroyWidget(rd->shell);
1391 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1393 repeatDialog *rd = (repeatDialog *)clientData;
1395 XtFree(rd->lastCommand);
1396 XtFree((char *)rd);
1400 ** Dispatches a macro to which repeats macro command in "command", either
1401 ** an integer number of times ("how" == positive integer), or within a
1402 ** selected range ("how" == REPEAT_IN_SEL), or to the end of the window
1403 ** ("how == REPEAT_TO_END).
1405 ** Note that as with most macro routines, this returns BEFORE the macro is
1406 ** finished executing
1408 void RepeatMacro(WindowInfo *window, const char *command, int how)
1410 Program *prog;
1411 char *errMsg, *stoppedAt, *loopMacro, *loopedCmd;
1413 if (command == NULL)
1414 return;
1416 /* Wrap a for loop and counter/tests around the command */
1417 if (how == REPEAT_TO_END)
1418 loopMacro = "lastCursor=-1\nstartPos=$cursor\n\
1419 while($cursor>=startPos&&$cursor!=lastCursor){\nlastCursor=$cursor\n%s\n}\n";
1420 else if (how == REPEAT_IN_SEL)
1421 loopMacro = "selStart = $selection_start\nif (selStart == -1)\nreturn\n\
1422 selEnd = $selection_end\nset_cursor_pos(selStart)\nselect(0,0)\n\
1423 boundText = get_range(selEnd, selEnd+10)\n\
1424 while($cursor >= selStart && $cursor < selEnd && \\\n\
1425 get_range(selEnd, selEnd+10) == boundText) {\n\
1426 startLength = $text_length\n%s\n\
1427 selEnd += $text_length - startLength\n}\n";
1428 else
1429 loopMacro = "for(i=0;i<%d;i++){\n%s\n}\n";
1430 loopedCmd = XtMalloc(strlen(command) + strlen(loopMacro) + 25);
1431 if (how == REPEAT_TO_END || how == REPEAT_IN_SEL)
1432 sprintf(loopedCmd, loopMacro, command);
1433 else
1434 sprintf(loopedCmd, loopMacro, how, command);
1436 /* Parse the resulting macro into an executable program "prog" */
1437 prog = ParseMacro(loopedCmd, &errMsg, &stoppedAt);
1438 if (prog == NULL) {
1439 fprintf(stderr, "NEdit internal error, repeat macro syntax wrong: %s\n",
1440 errMsg);
1441 return;
1443 XtFree(loopedCmd);
1445 /* run the executable program */
1446 runMacro(window, prog);
1450 ** Macro recording action hook for Learn/Replay, added temporarily during
1451 ** learn.
1453 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
1454 XEvent *event, String *params, Cardinal *numParams)
1456 WindowInfo *window;
1457 int i;
1458 char *actionString;
1460 /* Select only actions in text panes in the window for which this
1461 action hook is recording macros (from clientData). */
1462 for (window=WindowList; window!=NULL; window=window->next) {
1463 if (window->textArea == w)
1464 break;
1465 for (i=0; i<window->nPanes; i++) {
1466 if (window->textPanes[i] == w)
1467 break;
1469 if (i < window->nPanes)
1470 break;
1472 if (window == NULL || window != (WindowInfo *)clientData)
1473 return;
1475 /* beep on un-recordable operations which require a mouse position, to
1476 remind the user that the action was not recorded */
1477 if (isMouseAction(actionName)) {
1478 XBell(XtDisplay(w), 0);
1479 return;
1482 /* Record the action and its parameters */
1483 actionString = actionToString(w, actionName, event, params, *numParams);
1484 if (actionString != NULL) {
1485 BufInsert(MacroRecordBuf, MacroRecordBuf->length, actionString);
1486 XtFree(actionString);
1491 ** Permanent action hook for remembering last action for possible replay
1493 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
1494 XEvent *event, String *params, Cardinal *numParams)
1496 WindowInfo *window;
1497 int i;
1498 char *actionString;
1500 /* Find the window to which this action belongs */
1501 for (window=WindowList; window!=NULL; window=window->next) {
1502 if (window->textArea == w)
1503 break;
1504 for (i=0; i<window->nPanes; i++) {
1505 if (window->textPanes[i] == w)
1506 break;
1508 if (i < window->nPanes)
1509 break;
1511 if (window == NULL)
1512 return;
1514 /* The last action is recorded for the benefit of repeating the last
1515 action. Don't record repeat_macro and wipe out the real action */
1516 if (!strcmp(actionName, "repeat_macro"))
1517 return;
1519 /* Record the action and its parameters */
1520 actionString = actionToString(w, actionName, event, params, *numParams);
1521 if (actionString != NULL) {
1522 if (LastCommand != NULL)
1523 XtFree(LastCommand);
1524 LastCommand = actionString;
1529 ** Create a macro string to represent an invocation of an action routine.
1530 ** Returns NULL for non-operational or un-recordable actions.
1532 static char *actionToString(Widget w, char *actionName, XEvent *event,
1533 String *params, Cardinal numParams)
1535 char chars[20], *charList[1], *outStr, *outPtr;
1536 KeySym keysym;
1537 int i, nChars, nParams, length, nameLength;
1538 #ifndef NO_XMIM
1539 int status;
1540 #endif
1542 if (isIgnoredAction(actionName) || isRedundantAction(actionName) ||
1543 isMouseAction(actionName))
1544 return NULL;
1546 /* Convert self_insert actions, to insert_string */
1547 if (!strcmp(actionName, "self_insert") ||
1548 !strcmp(actionName, "self-insert")) {
1549 actionName = "insert_string";
1550 #ifdef NO_XMIM
1551 nChars = XLookupString((XKeyEvent *)event, chars, 19, &keysym, NULL);
1552 if (nChars == 0)
1553 return NULL;
1554 #else
1556 nChars = XmImMbLookupString(w, (XKeyEvent *)event,
1557 chars, 19, &keysym, &status);
1558 if (nChars == 0 || status == XLookupNone ||
1559 status == XLookupKeySym || status == XBufferOverflow)
1560 return NULL;
1561 #endif
1562 chars[nChars] = '\0';
1563 charList[0] = chars;
1564 params = charList;
1565 nParams = 1;
1566 } else
1567 nParams = numParams;
1569 /* Figure out the length of string required */
1570 nameLength = strlen(actionName);
1571 length = nameLength + 3;
1572 for (i=0; i<nParams; i++)
1573 length += escapedStringLength(params[i]) + 4;
1575 /* Allocate the string and copy the information to it */
1576 outPtr = outStr = XtMalloc(length + 1);
1577 strcpy(outPtr, actionName);
1578 outPtr += nameLength;
1579 *outPtr++ = '(';
1580 for (i=0; i<nParams; i++) {
1581 *outPtr++ = '\"';
1582 outPtr += escapeStringChars(params[i], outPtr);
1583 *outPtr++ = '\"'; *outPtr++ = ','; *outPtr++ = ' ';
1585 if (nParams != 0)
1586 outPtr -= 2;
1587 *outPtr++ = ')'; *outPtr++ = '\n'; *outPtr++ = '\0';
1588 return outStr;
1591 static int isMouseAction(const char *action)
1593 int i;
1595 for (i=0; i<(int)XtNumber(MouseActions); i++)
1596 if (!strcmp(action, MouseActions[i]))
1597 return True;
1598 return False;
1601 static int isRedundantAction(const char *action)
1603 int i;
1605 for (i=0; i<(int)XtNumber(RedundantActions); i++)
1606 if (!strcmp(action, RedundantActions[i]))
1607 return True;
1608 return False;
1611 static int isIgnoredAction(const char *action)
1613 int i;
1615 for (i=0; i<(int)XtNumber(IgnoredActions); i++)
1616 if (!strcmp(action, IgnoredActions[i]))
1617 return True;
1618 return False;
1622 ** Timer proc for putting up the "Macro Command in Progress" banner if
1623 ** the process is taking too long.
1625 #define MAX_TIMEOUT_MSG_LEN (MAX_ACCEL_LEN + 60)
1626 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id)
1628 WindowInfo *window = (WindowInfo *)clientData;
1629 macroCmdInfo *cmdData = window->macroCmdData;
1630 XmString xmCancel;
1631 char *cCancel;
1632 char message[MAX_TIMEOUT_MSG_LEN];
1634 cmdData->bannerIsUp = True;
1636 /* Extract accelerator text from menu PushButtons */
1637 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
1639 /* Translate Motif string to char* */
1640 cCancel = GetXmStringText(xmCancel);
1642 /* Free Motif String */
1643 XmStringFree(xmCancel);
1645 /* Create message */
1646 if (cCancel[0] == '\0') {
1647 strncpy(message, "Macro Command in Progress", MAX_TIMEOUT_MSG_LEN);
1648 message[MAX_TIMEOUT_MSG_LEN - 1] = '\0';
1650 else {
1651 sprintf(message,
1652 "Macro Command in Progress -- Press %s to Cancel",
1653 cCancel);
1656 /* Free C-string */
1657 XtFree(cCancel);
1659 SetModeMessage(window, message);
1660 cmdData->bannerTimeoutID = 0;
1664 ** Work proc for continuing execution of a preempted macro.
1666 ** Xt WorkProcs are designed to run first-in first-out, which makes them
1667 ** very bad at sharing time between competing tasks. For this reason, it's
1668 ** usually bad to use work procs anywhere where their execution is likely to
1669 ** overlap. Using a work proc instead of a timer proc (which I usually
1670 ** prefer) here means macros will probably share time badly, but we're more
1671 ** interested in making the macros cancelable, and in continuing other work
1672 ** than having users run a bunch of them at once together.
1674 static Boolean continueWorkProc(XtPointer clientData)
1676 WindowInfo *window = (WindowInfo *)clientData;
1677 macroCmdInfo *cmdData = window->macroCmdData;
1678 char *errMsg;
1679 int stat;
1680 DataValue result;
1682 stat = ContinueMacro(cmdData->context, &result, &errMsg);
1683 if (stat == MACRO_ERROR)
1685 finishMacroCmdExecution(window);
1686 DialogF(DF_ERR, window->shell, 1, "Macro Error",
1687 "Error executing macro: %s", "OK", errMsg);
1688 return True;
1689 } else if (stat == MACRO_DONE)
1691 finishMacroCmdExecution(window);
1692 return True;
1693 } else if (stat == MACRO_PREEMPT)
1695 cmdData->continueWorkProcID = 0;
1696 return True;
1699 /* Macro exceeded time slice, re-schedule it */
1700 if (stat != MACRO_TIME_LIMIT)
1701 return True; /* shouldn't happen */
1702 return False;
1706 ** Copy fromString to toString replacing special characters in strings, such
1707 ** that they can be read back by the macro parser's string reader. i.e. double
1708 ** quotes are replaced by \", backslashes are replaced with \\, C-std control
1709 ** characters like \n are replaced with their backslash counterparts. This
1710 ** routine should be kept reasonably in sync with yylex in parse.y. Companion
1711 ** routine escapedStringLength predicts the length needed to write the string
1712 ** when it is expanded with the additional characters. Returns the number
1713 ** of characters to which the string expanded.
1715 static int escapeStringChars(char *fromString, char *toString)
1717 char *e, *c, *outPtr = toString;
1719 /* substitute escape sequences */
1720 for (c=fromString; *c!='\0'; c++) {
1721 for (e=EscapeChars; *e!='\0'; e++) {
1722 if (*c == *e) {
1723 *outPtr++ = '\\';
1724 *outPtr++ = ReplaceChars[e-EscapeChars];
1725 break;
1728 if (*e == '\0')
1729 *outPtr++ = *c;
1731 *outPtr = '\0';
1732 return outPtr - toString;
1736 ** Predict the length of a string needed to hold a copy of "string" with
1737 ** special characters replaced with escape sequences by escapeStringChars.
1739 static int escapedStringLength(char *string)
1741 char *c, *e;
1742 int length = 0;
1744 /* calculate length and allocate returned string */
1745 for (c=string; *c!='\0'; c++) {
1746 for (e=EscapeChars; *e!='\0'; e++) {
1747 if (*c == *e) {
1748 length++;
1749 break;
1752 length++;
1754 return length;
1758 ** Built-in macro subroutine for getting the length of a string
1760 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
1761 DataValue *result, char **errMsg)
1763 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
1765 if (nArgs != 1)
1766 return wrongNArgsErr(errMsg);
1767 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1768 return False;
1769 result->tag = INT_TAG;
1770 result->val.n = strlen(string);
1771 return True;
1775 ** Built-in macro subroutines for min and max
1777 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
1778 DataValue *result, char **errMsg)
1780 int minVal, value, i;
1782 if (nArgs == 1)
1783 return tooFewArgsErr(errMsg);
1784 if (!readIntArg(argList[0], &minVal, errMsg))
1785 return False;
1786 for (i=0; i<nArgs; i++) {
1787 if (!readIntArg(argList[i], &value, errMsg))
1788 return False;
1789 minVal = value < minVal ? value : minVal;
1791 result->tag = INT_TAG;
1792 result->val.n = minVal;
1793 return True;
1795 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
1796 DataValue *result, char **errMsg)
1798 int maxVal, value, i;
1800 if (nArgs == 1)
1801 return tooFewArgsErr(errMsg);
1802 if (!readIntArg(argList[0], &maxVal, errMsg))
1803 return False;
1804 for (i=0; i<nArgs; i++) {
1805 if (!readIntArg(argList[i], &value, errMsg))
1806 return False;
1807 maxVal = value > maxVal ? value : maxVal;
1809 result->tag = INT_TAG;
1810 result->val.n = maxVal;
1811 return True;
1814 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
1815 DataValue *result, char **errMsg)
1817 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1818 WindowInfo *w;
1819 char fullname[MAXPATHLEN];
1820 char normalizedString[MAXPATHLEN];
1822 /* Read the argument representing the window to focus to, and translate
1823 it into a pointer to a real WindowInfo */
1824 if (nArgs != 1)
1825 return wrongNArgsErr(errMsg);
1827 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
1828 return False;
1829 } else if (!strcmp(string, "last")) {
1830 w = WindowList;
1831 } else if (!strcmp(string, "next")) {
1832 w = window->next;
1833 } else if (strlen(string) >= MAXPATHLEN) {
1834 *errMsg = "Pathname too long in focus_window()";
1835 return False;
1836 } else {
1837 /* just use the plain name as supplied */
1838 for (w=WindowList; w != NULL; w = w->next) {
1839 sprintf(fullname, "%s%s", w->path, w->filename);
1840 if (!strcmp(string, fullname)) {
1841 break;
1844 /* didn't work? try normalizing the string passed in */
1845 if (w == NULL) {
1846 strncpy(normalizedString, string, MAXPATHLEN);
1847 normalizedString[MAXPATHLEN-1] = '\0';
1848 if (1 == NormalizePathname(normalizedString)) {
1849 /* Something is broken with the input pathname. */
1850 *errMsg = "Pathname too long in focus_window()";
1851 return False;
1853 for (w=WindowList; w != NULL; w = w->next) {
1854 sprintf(fullname, "%s%s", w->path, w->filename);
1855 if (!strcmp(normalizedString, fullname))
1856 break;
1861 /* If no matching window was found, return empty string and do nothing */
1862 if (w == NULL) {
1863 result->tag = STRING_TAG;
1864 result->val.str.rep = PERM_ALLOC_STR("");
1865 result->val.str.len = 0;
1866 return True;
1869 /* Change the focused window to the requested one */
1870 SetMacroFocusWindow(w);
1872 /* turn on syntax highlight that might have been deferred */
1873 if (w->highlightSyntax && w->highlightData==NULL)
1874 StartHighlighting(w, False);
1876 /* Return the name of the window */
1877 result->tag = STRING_TAG;
1878 AllocNString(&result->val.str, strlen(w->path)+strlen(w->filename)+1);
1879 sprintf(result->val.str.rep, "%s%s", w->path, w->filename);
1880 return True;
1884 ** Built-in macro subroutine for getting text from the current window's text
1885 ** buffer
1887 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1888 DataValue *result, char **errMsg)
1890 int from, to;
1891 textBuffer *buf = window->buffer;
1892 char *rangeText;
1894 /* Validate arguments and convert to int */
1895 if (nArgs != 2)
1896 return wrongNArgsErr(errMsg);
1897 if (!readIntArg(argList[0], &from, errMsg))
1898 return False;
1899 if (!readIntArg(argList[1], &to, errMsg))
1900 return False;
1901 if (from < 0) from = 0;
1902 if (from > buf->length) from = buf->length;
1903 if (to < 0) to = 0;
1904 if (to > buf->length) to = buf->length;
1905 if (from > to) {int temp = from; from = to; to = temp;}
1907 /* Copy text from buffer (this extra copy could be avoided if textBuf.c
1908 provided a routine for writing into a pre-allocated string) */
1909 result->tag = STRING_TAG;
1910 AllocNString(&result->val.str, to - from + 1);
1911 rangeText = BufGetRange(buf, from, to);
1912 BufUnsubstituteNullChars(rangeText, buf);
1913 strcpy(result->val.str.rep, rangeText);
1914 /* Note: after the un-substitution, it is possible that strlen() != len,
1915 but that's because strlen() can't deal with 0-characters. */
1916 XtFree(rangeText);
1917 return True;
1921 ** Built-in macro subroutine for getting a single character at the position
1922 ** given, from the current window
1924 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
1925 DataValue *result, char **errMsg)
1927 int pos;
1928 textBuffer *buf = window->buffer;
1930 /* Validate argument and convert it to int */
1931 if (nArgs != 1)
1932 return wrongNArgsErr(errMsg);
1933 if (!readIntArg(argList[0], &pos, errMsg))
1934 return False;
1935 if (pos < 0) pos = 0;
1936 if (pos > buf->length) pos = buf->length;
1938 /* Return the character in a pre-allocated string) */
1939 result->tag = STRING_TAG;
1940 AllocNString(&result->val.str, 2);
1941 result->val.str.rep[0] = BufGetCharacter(buf, pos);
1942 BufUnsubstituteNullChars(result->val.str.rep, buf);
1943 /* Note: after the un-substitution, it is possible that strlen() != len,
1944 but that's because strlen() can't deal with 0-characters. */
1945 return True;
1949 ** Built-in macro subroutine for replacing text in the current window's text
1950 ** buffer
1952 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1953 DataValue *result, char **errMsg)
1955 int from, to;
1956 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1957 textBuffer *buf = window->buffer;
1959 /* Validate arguments and convert to int */
1960 if (nArgs != 3)
1961 return wrongNArgsErr(errMsg);
1962 if (!readIntArg(argList[0], &from, errMsg))
1963 return False;
1964 if (!readIntArg(argList[1], &to, errMsg))
1965 return False;
1966 if (!readStringArg(argList[2], &string, stringStorage, errMsg))
1967 return False;
1968 if (from < 0) from = 0;
1969 if (from > buf->length) from = buf->length;
1970 if (to < 0) to = 0;
1971 if (to > buf->length) to = buf->length;
1972 if (from > to) {int temp = from; from = to; to = temp;}
1974 /* Don't allow modifications if the window is read-only */
1975 if (IS_ANY_LOCKED(window->lockReasons)) {
1976 XBell(XtDisplay(window->shell), 0);
1977 result->tag = NO_TAG;
1978 return True;
1981 /* There are no null characters in the string (because macro strings
1982 still have null termination), but if the string contains the
1983 character used by the buffer for null substitution, it could
1984 theoretically become a null. In the highly unlikely event that
1985 all of the possible substitution characters in the buffer are used
1986 up, stop the macro and tell the user of the failure */
1987 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
1988 *errMsg = "Too much binary data in file";
1989 return False;
1992 /* Do the replace */
1993 BufReplace(buf, from, to, string);
1994 result->tag = NO_TAG;
1995 return True;
1999 ** Built-in macro subroutine for replacing the primary-selection selected
2000 ** text in the current window's text buffer
2002 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2003 DataValue *result, char **errMsg)
2005 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2007 /* Validate argument and convert to string */
2008 if (nArgs != 1)
2009 return wrongNArgsErr(errMsg);
2010 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2011 return False;
2013 /* Don't allow modifications if the window is read-only */
2014 if (IS_ANY_LOCKED(window->lockReasons)) {
2015 XBell(XtDisplay(window->shell), 0);
2016 result->tag = NO_TAG;
2017 return True;
2020 /* There are no null characters in the string (because macro strings
2021 still have null termination), but if the string contains the
2022 character used by the buffer for null substitution, it could
2023 theoretically become a null. In the highly unlikely event that
2024 all of the possible substitution characters in the buffer are used
2025 up, stop the macro and tell the user of the failure */
2026 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
2027 *errMsg = "Too much binary data in file";
2028 return False;
2031 /* Do the replace */
2032 BufReplaceSelected(window->buffer, string);
2033 result->tag = NO_TAG;
2034 return True;
2038 ** Built-in macro subroutine for getting the text currently selected by
2039 ** the primary selection in the current window's text buffer, or in any
2040 ** part of screen if "any" argument is given
2042 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2043 DataValue *result, char **errMsg)
2045 char *selText;
2047 /* Read argument list to check for "any" keyword, and get the appropriate
2048 selection */
2049 if (nArgs != 0 && nArgs != 1)
2050 return wrongNArgsErr(errMsg);
2051 if (nArgs == 1) {
2052 if (argList[0].tag != STRING_TAG || strcmp(argList[0].val.str.rep, "any")) {
2053 *errMsg = "Unrecognized argument to %s";
2054 return False;
2056 selText = GetAnySelection(window);
2057 if (selText == NULL)
2058 selText = XtNewString("");
2059 } else {
2060 selText = BufGetSelectionText(window->buffer);
2061 BufUnsubstituteNullChars(selText, window->buffer);
2064 /* Return the text as an allocated string */
2065 result->tag = STRING_TAG;
2066 AllocNStringCpy(&result->val.str, selText);
2067 XtFree(selText);
2068 return True;
2072 ** Built-in macro subroutine for determining if implicit conversion of
2073 ** a string to number will succeed or fail
2075 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
2076 DataValue *result, char **errMsg)
2078 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
2080 if (nArgs != 1) {
2081 return wrongNArgsErr(errMsg);
2083 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
2084 return False;
2087 result->tag = INT_TAG;
2088 result->val.n = StringToNum(string, NULL);
2090 return True;
2094 ** Built-in macro subroutine for replacing a substring within another string
2096 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
2097 DataValue *result, char **errMsg)
2099 int from, to, length, replaceLen, outLen;
2100 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *replStr;
2102 /* Validate arguments and convert to int */
2103 if (nArgs != 4)
2104 return wrongNArgsErr(errMsg);
2105 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2106 return False;
2107 if (!readIntArg(argList[1], &from, errMsg))
2108 return False;
2109 if (!readIntArg(argList[2], &to, errMsg))
2110 return False;
2111 if (!readStringArg(argList[3], &replStr, stringStorage[1], errMsg))
2112 return False;
2113 length = strlen(string);
2114 if (from < 0) from = 0;
2115 if (from > length) from = length;
2116 if (to < 0) to = 0;
2117 if (to > length) to = length;
2118 if (from > to) {int temp = from; from = to; to = temp;}
2120 /* Allocate a new string and do the replacement */
2121 replaceLen = strlen(replStr);
2122 outLen = length - (to - from) + replaceLen;
2123 result->tag = STRING_TAG;
2124 AllocNString(&result->val.str, outLen+1);
2125 strncpy(result->val.str.rep, string, from);
2126 strncpy(&result->val.str.rep[from], replStr, replaceLen);
2127 strncpy(&result->val.str.rep[from + replaceLen], &string[to], length - to);
2128 return True;
2132 ** Built-in macro subroutine for getting a substring of a string.
2133 ** Called as substring(string, from [, to])
2135 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
2136 DataValue *result, char **errMsg)
2138 int from, to, length;
2139 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2141 /* Validate arguments and convert to int */
2142 if (nArgs != 2 && nArgs != 3)
2143 return wrongNArgsErr(errMsg);
2144 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2145 return False;
2146 if (!readIntArg(argList[1], &from, errMsg))
2147 return False;
2148 length = to = strlen(string);
2149 if (nArgs == 3)
2150 if (!readIntArg(argList[2], &to, errMsg))
2151 return False;
2152 if (from < 0) from += length;
2153 if (from < 0) from = 0;
2154 if (from > length) from = length;
2155 if (to < 0) to += length;
2156 if (to < 0) to = 0;
2157 if (to > length) to = length;
2158 if (from > to) to = from;
2160 /* Allocate a new string and copy the sub-string into it */
2161 result->tag = STRING_TAG;
2162 AllocNStringNCpy(&result->val.str, &string[from], to - from);
2163 return True;
2166 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
2167 DataValue *result, char **errMsg)
2169 int i, length;
2170 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2172 /* Validate arguments and convert to int */
2173 if (nArgs != 1)
2174 return wrongNArgsErr(errMsg);
2175 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2176 return False;
2177 length = strlen(string);
2179 /* Allocate a new string and copy an uppercased version of the string it */
2180 result->tag = STRING_TAG;
2181 AllocNString(&result->val.str, length + 1);
2182 for (i=0; i<length; i++)
2183 result->val.str.rep[i] = toupper((unsigned char)string[i]);
2184 return True;
2187 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
2188 DataValue *result, char **errMsg)
2190 int i, length;
2191 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2193 /* Validate arguments and convert to int */
2194 if (nArgs != 1)
2195 return wrongNArgsErr(errMsg);
2196 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2197 return False;
2198 length = strlen(string);
2200 /* Allocate a new string and copy an lowercased version of the string it */
2201 result->tag = STRING_TAG;
2202 AllocNString(&result->val.str, length + 1);
2203 for (i=0; i<length; i++)
2204 result->val.str.rep[i] = tolower((unsigned char)string[i]);
2205 return True;
2208 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
2209 DataValue *result, char **errMsg)
2211 long itemID = 0;
2212 XmString s;
2213 int stat;
2214 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2216 /* Get the string argument */
2217 if (nArgs != 1)
2218 return wrongNArgsErr(errMsg);
2219 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2220 return False;
2222 /* Use the XmClipboard routines to copy the text to the clipboard.
2223 If errors occur, just give up. */
2224 result->tag = NO_TAG;
2225 stat = XmClipboardStartCopy(TheDisplay, XtWindow(window->textArea),
2226 s=XmStringCreateSimple("NEdit"), XtLastTimestampProcessed(TheDisplay),
2227 window->textArea, NULL, &itemID);
2228 XmStringFree(s);
2229 if (stat != ClipboardSuccess)
2230 return True;
2231 if (XmClipboardCopy(TheDisplay, XtWindow(window->textArea), itemID, "STRING",
2232 string, strlen(string), 0, NULL) != ClipboardSuccess)
2233 return True;
2234 XmClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2235 return True;
2238 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2239 DataValue *result, char **errMsg)
2241 unsigned long length, retLength;
2242 long id = 0;
2244 /* Should have no arguments */
2245 if (nArgs != 0)
2246 return wrongNArgsErr(errMsg);
2248 /* Ask if there's a string in the clipboard, and get its length */
2249 if (XmClipboardInquireLength(TheDisplay, XtWindow(window->shell), "STRING",
2250 &length) != ClipboardSuccess) {
2251 result->tag = STRING_TAG;
2252 result->val.str.rep = PERM_ALLOC_STR("");
2253 result->val.str.len = 0;
2254 return True;
2257 /* Allocate a new string to hold the data */
2258 result->tag = STRING_TAG;
2259 AllocNString(&result->val.str, (int)length + 1);
2261 /* Copy the clipboard contents to the string */
2262 if (XmClipboardRetrieve(TheDisplay, XtWindow(window->shell), "STRING",
2263 result->val.str.rep, length, &retLength, &id) != ClipboardSuccess)
2264 retLength = 0;
2265 result->val.str.rep[retLength] = '\0';
2266 result->val.str.len = retLength;
2268 return True;
2273 ** Built-in macro subroutine for reading the contents of a text file into
2274 ** a string. On success, returns 1 in $readStatus, and the contents of the
2275 ** file as a string in the subroutine return value. On failure, returns
2276 ** the empty string "" and an 0 $readStatus.
2278 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2279 DataValue *result, char **errMsg)
2281 char stringStorage[TYPE_INT_STR_SIZE(int)], *name;
2282 struct stat statbuf;
2283 FILE *fp;
2284 int readLen;
2286 /* Validate arguments and convert to int */
2287 if (nArgs != 1)
2288 return wrongNArgsErr(errMsg);
2289 if (!readStringArg(argList[0], &name, stringStorage, errMsg))
2290 return False;
2292 /* Read the whole file into an allocated string */
2293 if ((fp = fopen(name, "r")) == NULL)
2294 goto errorNoClose;
2295 if (fstat(fileno(fp), &statbuf) != 0)
2296 goto error;
2297 result->tag = STRING_TAG;
2298 AllocNString(&result->val.str, statbuf.st_size+1);
2299 readLen = fread(result->val.str.rep, sizeof(char), statbuf.st_size+1, fp);
2300 if (ferror(fp))
2301 goto error;
2302 if(!feof(fp)){
2303 /* Couldn't trust file size. Use slower but more general method */
2304 int chunkSize = 1024;
2305 char *buffer;
2307 buffer = XtMalloc(readLen * sizeof(char));
2308 memcpy(buffer, result->val.str.rep, readLen * sizeof(char));
2309 while (!feof(fp)){
2310 buffer = XtRealloc(buffer, (readLen+chunkSize)*sizeof(char));
2311 readLen += fread(&buffer[readLen], sizeof(char), chunkSize, fp);
2312 if (ferror(fp)){
2313 XtFree(buffer);
2314 goto error;
2317 AllocNString(&result->val.str, readLen + 1);
2318 memcpy(result->val.str.rep, buffer, readLen * sizeof(char));
2319 XtFree(buffer);
2321 fclose(fp);
2323 /* Return the results */
2324 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2325 ReturnGlobals[READ_STATUS]->value.val.n = True;
2326 return True;
2328 error:
2329 fclose(fp);
2331 errorNoClose:
2332 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2333 ReturnGlobals[READ_STATUS]->value.val.n = False;
2334 result->tag = STRING_TAG;
2335 result->val.str.rep = PERM_ALLOC_STR("");
2336 result->val.str.len = 0;
2337 return True;
2341 ** Built-in macro subroutines for writing or appending a string (parameter $1)
2342 ** to a file named in parameter $2. Returns 1 on successful write, or 0 if
2343 ** unsuccessful.
2345 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2346 DataValue *result, char **errMsg)
2348 return writeOrAppendFile(False, window, argList, nArgs, result, errMsg);
2351 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2352 DataValue *result, char **errMsg)
2354 return writeOrAppendFile(True, window, argList, nArgs, result, errMsg);
2357 static int writeOrAppendFile(int append, WindowInfo *window,
2358 DataValue *argList, int nArgs, DataValue *result, char **errMsg)
2360 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *name, *string;
2361 FILE *fp;
2363 /* Validate argument */
2364 if (nArgs != 2)
2365 return wrongNArgsErr(errMsg);
2366 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2367 return False;
2368 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg))
2369 return False;
2371 /* open the file */
2372 if ((fp = fopen(name, append ? "a" : "w")) == NULL) {
2373 result->tag = INT_TAG;
2374 result->val.n = False;
2375 return True;
2378 /* write the string to the file */
2379 fwrite(string, sizeof(char), strlen(string), fp);
2380 if (ferror(fp)) {
2381 fclose(fp);
2382 result->tag = INT_TAG;
2383 result->val.n = False;
2384 return True;
2386 fclose(fp);
2388 /* return the status */
2389 result->tag = INT_TAG;
2390 result->val.n = True;
2391 return True;
2395 ** Built-in macro subroutine for searching silently in a window without
2396 ** dialogs, beeps, or changes to the selection. Arguments are: $1: string to
2397 ** search for, $2: starting position. Optional arguments may include the
2398 ** strings: "wrap" to make the search wrap around the beginning or end of the
2399 ** string, "backward" or "forward" to change the search direction ("forward" is
2400 ** the default), "literal", "case" or "regex" to change the search type
2401 ** (default is "literal").
2403 ** Returns the starting position of the match, or -1 if nothing matched.
2404 ** also returns the ending position of the match in $searchEndPos
2406 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
2407 DataValue *result, char **errMsg)
2409 DataValue newArgList[9];
2410 int retVal;
2412 /* Use the search string routine, by adding the buffer contents as
2413 the string argument */
2414 if (nArgs > 8)
2415 return wrongNArgsErr(errMsg);
2416 newArgList[0].tag = STRING_TAG;
2417 newArgList[0].val.str.rep = BufGetAll(window->buffer);
2418 newArgList[0].val.str.len = window->buffer->length;
2419 memcpy(&newArgList[1], argList, nArgs * sizeof(DataValue));
2420 retVal = searchStringMS(window, newArgList, nArgs+1, result, errMsg);
2421 XtFree(newArgList[0].val.str.rep);
2422 return retVal;
2426 ** Built-in macro subroutine for searching a string. Arguments are $1:
2427 ** string to search in, $2: string to search for, $3: starting position.
2428 ** Optional arguments may include the strings: "wrap" to make the search
2429 ** wrap around the beginning or end of the string, "backward" or "forward"
2430 ** to change the search direction ("forward" is the default), "literal",
2431 ** "case" or "regex" to change the search type (default is "literal").
2433 ** Returns the starting position of the match, or -1 if nothing matched.
2434 ** also returns the ending position of the match in $searchEndPos
2436 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2437 DataValue *result, char **errMsg)
2439 int beginPos, wrap, direction, found = False, foundStart, foundEnd, type;
2440 int skipSearch = False, len;
2441 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *searchStr;
2443 /* Validate arguments and convert to proper types */
2444 if (nArgs < 3)
2445 return tooFewArgsErr(errMsg);
2446 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2447 return False;
2448 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2449 return False;
2450 if (!readIntArg(argList[2], &beginPos, errMsg))
2451 return False;
2452 if (!readSearchArgs(&argList[3], nArgs-3, &direction, &type, &wrap, errMsg))
2453 return False;
2455 len = argList[0].val.str.len;
2456 if (beginPos > len) {
2457 if (direction == SEARCH_FORWARD) {
2458 if (wrap) {
2459 beginPos = 0; /* Wrap immediately */
2460 } else {
2461 found = False;
2462 skipSearch = True;
2464 } else {
2465 beginPos = len;
2467 } else if (beginPos < 0) {
2468 if (direction == SEARCH_BACKWARD) {
2469 if (wrap) {
2470 beginPos = len; /* Wrap immediately */
2471 } else {
2472 found = False;
2473 skipSearch = True;
2475 } else {
2476 beginPos = 0;
2480 if (!skipSearch)
2481 found = SearchString(string, searchStr, direction, type, wrap, beginPos,
2482 &foundStart, &foundEnd, NULL, NULL, GetWindowDelimiters(window));
2484 /* Return the results */
2485 ReturnGlobals[SEARCH_END]->value.tag = INT_TAG;
2486 ReturnGlobals[SEARCH_END]->value.val.n = found ? foundEnd : 0;
2487 result->tag = INT_TAG;
2488 result->val.n = found ? foundStart : -1;
2489 return True;
2493 ** Built-in macro subroutine for replacing all occurences of a search string in
2494 ** a string with a replacement string. Arguments are $1: string to search in,
2495 ** $2: string to search for, $3: replacement string. Also takes an optional
2496 ** search type: one of "literal", "case" or "regex" (default is "literal"), and
2497 ** an optional "copy" argument.
2499 ** Returns a new string with all of the replacements done. If no replacements
2500 ** were performed and "copy" was specified, returns a copy of the original
2501 ** string. Otherwise returns an empty string ("").
2503 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2504 DataValue *result, char **errMsg)
2506 char stringStorage[3][TYPE_INT_STR_SIZE(int)], *string, *searchStr, *replaceStr;
2507 char *argStr, *replacedStr;
2508 int searchType = SEARCH_LITERAL, copyStart, copyEnd;
2509 int replacedLen, replaceEnd, force=False, i;
2511 /* Validate arguments and convert to proper types */
2512 if (nArgs < 3 || nArgs > 5)
2513 return wrongNArgsErr(errMsg);
2514 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2515 return False;
2516 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2517 return False;
2518 if (!readStringArg(argList[2], &replaceStr, stringStorage[2], errMsg))
2519 return False;
2520 for (i = 3; i < nArgs; i++) {
2521 /* Read the optional search type and force arguments */
2522 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
2523 return False;
2524 if (!StringToSearchType(argStr, &searchType)) {
2525 /* It's not a search type. is it "copy"? */
2526 if (!strcmp(argStr, "copy")) {
2527 force = True;
2528 } else {
2529 *errMsg = "unrecognized argument to %s";
2530 return False;
2535 /* Do the replace */
2536 replacedStr = ReplaceAllInString(string, searchStr, replaceStr, searchType,
2537 &copyStart, &copyEnd, &replacedLen, GetWindowDelimiters(window));
2539 /* Return the results */
2540 result->tag = STRING_TAG;
2541 if (replacedStr == NULL) {
2542 if (force) {
2543 /* Just copy the original DataValue */
2544 if (argList[0].tag == STRING_TAG) {
2545 result->val.str.rep = argList[0].val.str.rep;
2546 result->val.str.len = argList[0].val.str.len;
2548 else {
2549 AllocNStringCpy(&result->val.str, string);
2552 else {
2553 result->val.str.rep = PERM_ALLOC_STR("");
2554 result->val.str.len = 0;
2557 else {
2558 size_t remainder = strlen(&string[copyEnd]);
2559 replaceEnd = copyStart + replacedLen;
2560 AllocNString(&result->val.str, replaceEnd + remainder + 1);
2561 strncpy(result->val.str.rep, string, copyStart);
2562 strcpy(&result->val.str.rep[copyStart], replacedStr);
2563 strcpy(&result->val.str.rep[replaceEnd], &string[copyEnd]);
2564 XtFree(replacedStr);
2566 return True;
2569 static int readSearchArgs(DataValue *argList, int nArgs, int *searchDirection,
2570 int *searchType, int *wrap, char **errMsg)
2572 int i;
2573 char *argStr, stringStorage[TYPE_INT_STR_SIZE(int)];
2575 *wrap = False;
2576 *searchDirection = SEARCH_FORWARD;
2577 *searchType = SEARCH_LITERAL;
2578 for (i=0; i<nArgs; i++) {
2579 if (!readStringArg(argList[i], &argStr, stringStorage, errMsg))
2580 return False;
2581 else if (!strcmp(argStr, "wrap"))
2582 *wrap = True;
2583 else if (!strcmp(argStr, "nowrap"))
2584 *wrap = False;
2585 else if (!strcmp(argStr, "backward"))
2586 *searchDirection = SEARCH_BACKWARD;
2587 else if (!strcmp(argStr, "forward"))
2588 *searchDirection = SEARCH_FORWARD;
2589 else if (!StringToSearchType(argStr, searchType)) {
2590 *errMsg = "Unrecognized argument to %s";
2591 return False;
2594 return True;
2597 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
2598 DataValue *result, char **errMsg)
2600 int pos;
2602 /* Get argument and convert to int */
2603 if (nArgs != 1)
2604 return wrongNArgsErr(errMsg);
2605 if (!readIntArg(argList[0], &pos, errMsg))
2606 return False;
2608 /* Set the position */
2609 TextSetCursorPos(window->lastFocus, pos);
2610 result->tag = NO_TAG;
2611 return True;
2614 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
2615 DataValue *result, char **errMsg)
2617 int start, end, startTmp;
2619 /* Get arguments and convert to int */
2620 if (nArgs != 2)
2621 return wrongNArgsErr(errMsg);
2622 if (!readIntArg(argList[0], &start, errMsg))
2623 return False;
2624 if (!readIntArg(argList[1], &end, errMsg))
2625 return False;
2627 /* Verify integrity of arguments */
2628 if (start > end) {
2629 startTmp = start;
2630 start = end;
2631 end = startTmp;
2633 if (start < 0) start = 0;
2634 if (start > window->buffer->length) start = window->buffer->length;
2635 if (end < 0) end = 0;
2636 if (end > window->buffer->length) end = window->buffer->length;
2638 /* Make the selection */
2639 BufSelect(window->buffer, start, end);
2640 result->tag = NO_TAG;
2641 return True;
2644 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
2645 DataValue *result, char **errMsg)
2647 int start, end, left, right;
2649 /* Get arguments and convert to int */
2650 if (nArgs != 4)
2651 return wrongNArgsErr(errMsg);
2652 if (!readIntArg(argList[0], &start, errMsg))
2653 return False;
2654 if (!readIntArg(argList[1], &end, errMsg))
2655 return False;
2656 if (!readIntArg(argList[2], &left, errMsg))
2657 return False;
2658 if (!readIntArg(argList[3], &right, errMsg))
2659 return False;
2661 /* Make the selection */
2662 BufRectSelect(window->buffer, start, end, left, right);
2663 result->tag = NO_TAG;
2664 return True;
2668 ** Macro subroutine to ring the bell
2670 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
2671 DataValue *result, char **errMsg)
2673 if (nArgs != 0)
2674 return wrongNArgsErr(errMsg);
2675 XBell(XtDisplay(window->shell), 0);
2676 result->tag = NO_TAG;
2677 return True;
2680 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
2681 DataValue *result, char **errMsg)
2683 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2684 int i;
2686 if (nArgs == 0)
2687 return tooFewArgsErr(errMsg);
2688 for (i=0; i<nArgs; i++) {
2689 if (!readStringArg(argList[i], &string, stringStorage, errMsg))
2690 return False;
2691 printf("%s%s", string, i==nArgs-1 ? "" : " ");
2693 fflush( stdout );
2694 result->tag = NO_TAG;
2695 return True;
2699 ** Built-in macro subroutine for getting the value of an environment variable
2701 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
2702 DataValue *result, char **errMsg)
2704 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
2705 char *name;
2706 char *value;
2708 /* Get name of variable to get */
2709 if (nArgs != 1)
2710 return wrongNArgsErr(errMsg);
2711 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
2712 *errMsg = "argument to %s must be a string";
2713 return False;
2715 value = getenv(name);
2716 if (value == NULL)
2717 value = "";
2719 /* Return the text as an allocated string */
2720 result->tag = STRING_TAG;
2721 AllocNStringCpy(&result->val.str, value);
2722 return True;
2725 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
2726 DataValue *result, char **errMsg)
2728 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *cmdString, *inputString;
2730 if (nArgs != 2)
2731 return wrongNArgsErr(errMsg);
2732 if (!readStringArg(argList[0], &cmdString, stringStorage[0], errMsg))
2733 return False;
2734 if (!readStringArg(argList[1], &inputString, stringStorage[1], errMsg))
2735 return False;
2737 /* Shell command execution requires that the macro be suspended, so
2738 this subroutine can't be run if macro execution can't be interrupted */
2739 if (MacroRunWindow()->macroCmdData == NULL) {
2740 *errMsg = "%s can't be called from non-suspendable context";
2741 return False;
2744 #ifdef VMS
2745 *errMsg = "Shell commands not supported under VMS";
2746 return False;
2747 #else
2748 ShellCmdToMacroString(window, cmdString, inputString);
2749 result->tag = INT_TAG;
2750 result->val.n = 0;
2751 return True;
2752 #endif /*VMS*/
2756 ** Method used by ShellCmdToMacroString (called by shellCmdMS), for returning
2757 ** macro string and exit status after the execution of a shell command is
2758 ** complete. (Sorry about the poor modularity here, it's just not worth
2759 ** teaching other modules about macro return globals, since other than this,
2760 ** they're not used outside of macro.c)
2762 void ReturnShellCommandOutput(WindowInfo *window, const char *outText, int status)
2764 DataValue retVal;
2765 macroCmdInfo *cmdData = window->macroCmdData;
2767 if (cmdData == NULL)
2768 return;
2769 retVal.tag = STRING_TAG;
2770 AllocNStringCpy(&retVal.val.str, outText);
2771 ModifyReturnedValue(cmdData->context, retVal);
2772 ReturnGlobals[SHELL_CMD_STATUS]->value.tag = INT_TAG;
2773 ReturnGlobals[SHELL_CMD_STATUS]->value.val.n = status;
2776 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2777 DataValue *result, char **errMsg)
2779 macroCmdInfo *cmdData;
2780 char stringStorage[TYPE_INT_STR_SIZE(int)];
2781 char btnStorage[TYPE_INT_STR_SIZE(int)];
2782 char *btnLabel;
2783 char *message;
2784 Arg al[20];
2785 int ac;
2786 Widget dialog, btn;
2787 int i, nBtns;
2788 XmString s1, s2;
2790 /* Ignore the focused window passed as the function argument and put
2791 the dialog up over the window which is executing the macro */
2792 window = MacroRunWindow();
2793 cmdData = window->macroCmdData;
2795 /* Dialogs require macro to be suspended and interleaved with other macros.
2796 This subroutine can't be run if macro execution can't be interrupted */
2797 if (!cmdData) {
2798 *errMsg = "%s can't be called from non-suspendable context";
2799 return False;
2802 /* Read and check the arguments. The first being the dialog message,
2803 and the rest being the button labels */
2804 if (nArgs == 0) {
2805 *errMsg = "%s subroutine called with no arguments";
2806 return False;
2808 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
2809 return False;
2812 /* check that all button labels can be read */
2813 for (i=1; i<nArgs; i++) {
2814 if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg)) {
2815 return False;
2819 /* pick up the first button */
2820 if (nArgs == 1) {
2821 btnLabel = "OK";
2822 nBtns = 1;
2824 else {
2825 nBtns = nArgs - 1;
2826 argList++;
2827 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
2830 /* Create the message box dialog widget and its dialog shell parent */
2831 ac = 0;
2832 XtSetArg(al[ac], XmNtitle, " "); ac++;
2833 XtSetArg(al[ac], XmNmessageString, s1=MKSTRING(message)); ac++;
2834 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
2835 dialog = CreateMessageDialog(window->shell, "macroDialog", al, ac);
2836 if (1 == nArgs)
2838 /* Only set margin width for the default OK button */
2839 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2840 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
2841 NULL);
2844 XmStringFree(s1);
2845 XmStringFree(s2);
2846 AddMotifCloseCallback(XtParent(dialog), dialogCloseCB, window);
2847 XtAddCallback(dialog, XmNokCallback, dialogBtnCB, window);
2848 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2849 XmNuserData, (XtPointer)1, NULL);
2850 cmdData->dialog = dialog;
2852 /* Unmanage default buttons, except for "OK" */
2853 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2854 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2856 /* Make callback for the unmanaged cancel button (which can
2857 still get executed via the esc key) activate close box action */
2858 XtAddCallback(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2859 XmNactivateCallback, dialogCloseCB, window);
2861 /* Add user specified buttons (1st is already done) */
2862 for (i=1; i<nBtns; i++) {
2863 readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
2864 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2865 XmNlabelString, s1=XmStringCreateSimple(btnLabel),
2866 XmNuserData, (XtPointer)(i+1), NULL);
2867 XtAddCallback(btn, XmNactivateCallback, dialogBtnCB, window);
2868 XmStringFree(s1);
2871 #ifdef LESSTIF_VERSION
2872 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2873 the escape key for closing the dialog (probably because the
2874 cancel button is not managed). */
2875 XtAddEventHandler(dialog, KeyPressMask, False, dialogEscCB,
2876 (XtPointer)window);
2877 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
2878 True, GrabModeAsync, GrabModeAsync);
2879 #endif /* LESSTIF_VERSION */
2881 /* Put up the dialog */
2882 ManageDialogCenteredOnPointer(dialog);
2884 /* Stop macro execution until the dialog is complete */
2885 PreemptMacro();
2887 /* Return placeholder result. Value will be changed by button callback */
2888 result->tag = INT_TAG;
2889 result->val.n = 0;
2890 return True;
2893 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData)
2895 WindowInfo *window = (WindowInfo *)clientData;
2896 macroCmdInfo *cmdData = window->macroCmdData;
2897 XtPointer userData;
2898 DataValue retVal;
2900 /* Return the index of the button which was pressed (stored in the userData
2901 field of the button widget). The 1st button, being a gadget, is not
2902 returned in w. */
2903 if (cmdData == NULL)
2904 return; /* shouldn't happen */
2905 if (XtClass(w) == xmPushButtonWidgetClass) {
2906 XtVaGetValues(w, XmNuserData, &userData, NULL);
2907 retVal.val.n = (int)userData;
2908 } else
2909 retVal.val.n = 1;
2910 retVal.tag = INT_TAG;
2911 ModifyReturnedValue(cmdData->context, retVal);
2913 /* Pop down the dialog */
2914 XtDestroyWidget(XtParent(cmdData->dialog));
2915 cmdData->dialog = NULL;
2917 /* Continue preempted macro execution */
2918 ResumeMacroExecution(window);
2921 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData)
2923 WindowInfo *window = (WindowInfo *)clientData;
2924 macroCmdInfo *cmdData = window->macroCmdData;
2925 DataValue retVal;
2927 /* Return 0 to show that the dialog was closed via the window close box */
2928 retVal.val.n = 0;
2929 retVal.tag = INT_TAG;
2930 ModifyReturnedValue(cmdData->context, retVal);
2932 /* Pop down the dialog */
2933 XtDestroyWidget(XtParent(cmdData->dialog));
2934 cmdData->dialog = NULL;
2936 /* Continue preempted macro execution */
2937 ResumeMacroExecution(window);
2940 #ifdef LESSTIF_VERSION
2941 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
2942 Boolean *cont)
2944 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
2945 return;
2946 if (clientData != NULL) {
2947 dialogCloseCB(w, (WindowInfo *)clientData, NULL);
2949 *cont = False;
2951 #endif /* LESSTIF_VERSION */
2953 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2954 DataValue *result, char **errMsg)
2956 macroCmdInfo *cmdData;
2957 char stringStorage[TYPE_INT_STR_SIZE(int)];
2958 char btnStorage[TYPE_INT_STR_SIZE(int)];
2959 char *btnLabel;
2960 char *message;
2961 Widget dialog, btn;
2962 int i, nBtns;
2963 XmString s1, s2;
2964 Arg al[20];
2965 int ac;
2967 /* Ignore the focused window passed as the function argument and put
2968 the dialog up over the window which is executing the macro */
2969 window = MacroRunWindow();
2970 cmdData = window->macroCmdData;
2972 /* Dialogs require macro to be suspended and interleaved with other macros.
2973 This subroutine can't be run if macro execution can't be interrupted */
2974 if (!cmdData) {
2975 *errMsg = "%s can't be called from non-suspendable context";
2976 return False;
2979 /* Read and check the arguments. The first being the dialog message,
2980 and the rest being the button labels */
2981 if (nArgs == 0) {
2982 *errMsg = "%s subroutine called with no arguments";
2983 return False;
2985 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) {
2986 return False;
2988 /* check that all button labels can be read */
2989 for (i=1; i<nArgs; i++) {
2990 if (!readStringArg(argList[i], &btnLabel, stringStorage, errMsg)) {
2991 return False;
2994 if (nArgs == 1) {
2995 btnLabel = "OK";
2996 nBtns = 1;
2998 else {
2999 nBtns = nArgs - 1;
3000 argList++;
3001 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
3004 /* Create the selection box dialog widget and its dialog shell parent */
3005 ac = 0;
3006 XtSetArg(al[ac], XmNtitle, " "); ac++;
3007 XtSetArg(al[ac], XmNselectionLabelString, s1=MKSTRING(message)); ac++;
3008 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
3009 dialog = CreatePromptDialog(window->shell, "macroStringDialog", al, ac);
3010 if (1 == nArgs)
3012 /* Only set margin width for the default OK button */
3013 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3014 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
3015 NULL);
3018 XmStringFree(s1);
3019 XmStringFree(s2);
3020 AddMotifCloseCallback(XtParent(dialog), stringDialogCloseCB, window);
3021 XtAddCallback(dialog, XmNokCallback, stringDialogBtnCB, window);
3022 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3023 XmNuserData, (XtPointer)1, NULL);
3024 cmdData->dialog = dialog;
3026 /* Unmanage unneded widgets */
3027 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3028 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3030 /* Make callback for the unmanaged cancel button (which can
3031 still get executed via the esc key) activate close box action */
3032 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3033 XmNactivateCallback, stringDialogCloseCB, window);
3035 /* Add user specified buttons (1st is already done). Selection box
3036 requires a place-holder widget to be added before buttons can be
3037 added, that's what the separator below is for */
3038 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3039 for (i=1; i<nBtns; i++) {
3040 readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
3041 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3042 XmNlabelString, s1=XmStringCreateSimple(btnLabel),
3043 XmNuserData, (XtPointer)(i+1), NULL);
3044 XtAddCallback(btn, XmNactivateCallback, stringDialogBtnCB, window);
3045 XmStringFree(s1);
3048 #ifdef LESSTIF_VERSION
3049 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3050 the escape key for closing the dialog (probably because the
3051 cancel button is not managed). */
3052 XtAddEventHandler(dialog, KeyPressMask, False, stringDialogEscCB,
3053 (XtPointer)window);
3054 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3055 True, GrabModeAsync, GrabModeAsync);
3056 #endif /* LESSTIF_VERSION */
3058 /* Put up the dialog */
3059 ManageDialogCenteredOnPointer(dialog);
3061 /* Stop macro execution until the dialog is complete */
3062 PreemptMacro();
3064 /* Return placeholder result. Value will be changed by button callback */
3065 result->tag = INT_TAG;
3066 result->val.n = 0;
3067 return True;
3070 static void stringDialogBtnCB(Widget w, XtPointer clientData,
3071 XtPointer callData)
3073 WindowInfo *window = (WindowInfo *)clientData;
3074 macroCmdInfo *cmdData = window->macroCmdData;
3075 XtPointer userData;
3076 DataValue retVal;
3077 char *text;
3078 int btnNum;
3080 /* shouldn't happen, but would crash if it did */
3081 if (cmdData == NULL)
3082 return;
3084 /* Return the string entered in the selection text area */
3085 text = XmTextGetString(XmSelectionBoxGetChild(cmdData->dialog,
3086 XmDIALOG_TEXT));
3087 retVal.tag = STRING_TAG;
3088 AllocNStringCpy(&retVal.val.str, text);
3089 XtFree(text);
3090 ModifyReturnedValue(cmdData->context, retVal);
3092 /* Find the index of the button which was pressed (stored in the userData
3093 field of the button widget). The 1st button, being a gadget, is not
3094 returned in w. */
3095 if (XtClass(w) == xmPushButtonWidgetClass) {
3096 XtVaGetValues(w, XmNuserData, &userData, NULL);
3097 btnNum = (int)userData;
3098 } else
3099 btnNum = 1;
3101 /* Return the button number in the global variable $string_dialog_button */
3102 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3103 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = btnNum;
3105 /* Pop down the dialog */
3106 XtDestroyWidget(XtParent(cmdData->dialog));
3107 cmdData->dialog = NULL;
3109 /* Continue preempted macro execution */
3110 ResumeMacroExecution(window);
3113 static void stringDialogCloseCB(Widget w, XtPointer clientData,
3114 XtPointer callData)
3116 WindowInfo *window = (WindowInfo *)clientData;
3117 macroCmdInfo *cmdData = window->macroCmdData;
3118 DataValue retVal;
3120 /* shouldn't happen, but would crash if it did */
3121 if (cmdData == NULL)
3122 return;
3124 /* Return an empty string */
3125 retVal.tag = STRING_TAG;
3126 retVal.val.str.rep = PERM_ALLOC_STR("");
3127 retVal.val.str.len = 0;
3128 ModifyReturnedValue(cmdData->context, retVal);
3130 /* Return button number 0 in the global variable $string_dialog_button */
3131 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3132 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = 0;
3134 /* Pop down the dialog */
3135 XtDestroyWidget(XtParent(cmdData->dialog));
3136 cmdData->dialog = NULL;
3138 /* Continue preempted macro execution */
3139 ResumeMacroExecution(window);
3142 #ifdef LESSTIF_VERSION
3143 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3144 Boolean *cont)
3146 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3147 return;
3148 if (clientData != NULL) {
3149 stringDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3151 *cont = False;
3153 #endif /* LESSTIF_VERSION */
3156 ** A subroutine to put up a calltip
3157 ** First arg is either text to be displayed or a key for tip/tag lookup.
3158 ** Optional second arg is the buffer position beneath which to display the
3159 ** upper-left corner of the tip. Default (or -1) puts it under the cursor.
3160 ** Additional optional arguments:
3161 ** "tipText": (default) Indicates first arg is text to be displayed in tip.
3162 ** "tipKey": Indicates first arg is key in calltips database. If key
3163 ** is not found in tip database then the tags database is also
3164 ** searched.
3165 ** "tagKey": Indicates first arg is key in tags database. (Skips
3166 ** search in calltips database.)
3167 ** "center": Horizontally center the calltip at the position
3168 ** "right": Put the right edge of the calltip at the position
3169 ** "center" and "right" cannot both be specified.
3170 ** "above": Place the calltip above the position
3171 ** "strict": Don't move the calltip to keep it on-screen and away
3172 ** from the cursor's line.
3174 ** Returns the new calltip's ID on success, 0 on failure.
3176 ** Does this need to go on IgnoredActions? I don't think so, since
3177 ** showing a calltip may be part of the action you want to learn.
3179 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3180 DataValue *result, char **errMsg)
3182 char stringStorage[TYPE_INT_STR_SIZE(int)], *tipText, *txtArg;
3183 Boolean anchored = False, lookup = True;
3184 int mode = -1, i;
3185 int anchorPos, hAlign = TIP_LEFT, vAlign = TIP_BELOW,
3186 alignMode = TIP_SLOPPY;
3188 /* Read and check the string */
3189 if (nArgs < 1) {
3190 *errMsg = "%s subroutine called with too few arguments";
3191 return False;
3193 if (nArgs > 6) {
3194 *errMsg = "%s subroutine called with too many arguments";
3195 return False;
3198 /* Read the tip text or key */
3199 if (!readStringArg(argList[0], &tipText, stringStorage, errMsg))
3200 return False;
3202 /* Read the anchor position (-1 for unanchored) */
3203 if (nArgs > 1) {
3204 if (!readIntArg(argList[1], &anchorPos, errMsg))
3205 return False;
3206 } else {
3207 anchorPos = -1;
3209 if (anchorPos >= 0) anchored = True;
3211 /* Any further args are directives for relative positioning */
3212 for (i = 2; i < nArgs; ++i) {
3213 if (!readStringArg(argList[i], &txtArg, stringStorage, errMsg)){
3214 return False;
3216 switch( txtArg[0] ) {
3217 case 'c':
3218 if (strcmp(txtArg, "center"))
3219 goto bad_arg;
3220 hAlign = TIP_CENTER;
3221 break;
3222 case 'r':
3223 if (strcmp(txtArg, "right"))
3224 goto bad_arg;
3225 hAlign = TIP_RIGHT;
3226 break;
3227 case 'a':
3228 if (strcmp(txtArg, "above"))
3229 goto bad_arg;
3230 vAlign = TIP_ABOVE;
3231 break;
3232 case 's':
3233 if (strcmp(txtArg, "strict"))
3234 goto bad_arg;
3235 alignMode = TIP_STRICT;
3236 break;
3237 case 't':
3238 if (!strcmp(txtArg, "tipText"))
3239 mode = -1;
3240 else if (!strcmp(txtArg, "tipKey"))
3241 mode = TIP;
3242 else if (!strcmp(txtArg, "tagKey"))
3243 mode = TIP_FROM_TAG;
3244 else
3245 goto bad_arg;
3246 break;
3247 default:
3248 goto bad_arg;
3252 result->tag = INT_TAG;
3253 if (mode < 0) lookup = False;
3254 /* Look up (maybe) a calltip and display it */
3255 result->val.n = ShowTipString( window, tipText, anchored, anchorPos, lookup,
3256 mode, hAlign, vAlign, alignMode );
3258 return True;
3260 bad_arg:
3261 /* This is how the (more informative) global var. version would work,
3262 assuming there was a global buffer called msg. */
3263 /* sprintf(msg, "unrecognized argument to %%s: \"%s\"", txtArg);
3264 *errMsg = msg; */
3265 *errMsg = "unrecognized argument to %s";
3266 return False;
3270 ** A subroutine to kill the current calltip
3272 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3273 DataValue *result, char **errMsg)
3275 int calltipID = 0;
3277 if (nArgs > 1) {
3278 *errMsg = "%s subroutine called with too many arguments";
3279 return False;
3281 if (nArgs > 0) {
3282 if (!readIntArg(argList[0], &calltipID, errMsg))
3283 return False;
3286 KillCalltip( window, calltipID );
3288 result->tag = NO_TAG;
3289 return True;
3293 * A subroutine to get the ID of the current calltip, or 0 if there is none.
3295 static int calltipIDMV(WindowInfo *window, DataValue *argList,
3296 int nArgs, DataValue *result, char **errMsg)
3298 result->tag = INT_TAG;
3299 result->val.n = GetCalltipID(window, 0);
3300 return True;
3303 /* T Balinski */
3304 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3305 DataValue *result, char **errMsg)
3307 macroCmdInfo *cmdData;
3308 char stringStorage[TYPE_INT_STR_SIZE(int)];
3309 char textStorage[TYPE_INT_STR_SIZE(int)];
3310 char btnStorage[TYPE_INT_STR_SIZE(int)];
3311 char *btnLabel;
3312 char *message, *text;
3313 Widget dialog, btn;
3314 int i, nBtns;
3315 XmString s1, s2;
3316 long nlines = 0;
3317 char *p, *old_p, **text_lines, *tmp;
3318 int tmp_len;
3319 int n, is_last;
3320 XmString *test_strings;
3321 int tabDist;
3322 Arg al[20];
3323 int ac;
3326 /* Ignore the focused window passed as the function argument and put
3327 the dialog up over the window which is executing the macro */
3328 window = MacroRunWindow();
3329 cmdData = window->macroCmdData;
3331 /* Dialogs require macro to be suspended and interleaved with other macros.
3332 This subroutine can't be run if macro execution can't be interrupted */
3333 if (!cmdData) {
3334 *errMsg = "%s can't be called from non-suspendable context";
3335 return False;
3338 /* Read and check the arguments. The first being the dialog message,
3339 and the rest being the button labels */
3340 if (nArgs < 2) {
3341 *errMsg = "%s subroutine called with no message, string or arguments";
3342 return False;
3345 if (!readStringArg(argList[0], &message, stringStorage, errMsg))
3346 return False;
3348 if (!readStringArg(argList[1], &text, textStorage, errMsg))
3349 return False;
3351 if (!text || text[0] == '\0') {
3352 *errMsg = "%s subroutine called with empty list data";
3353 return False;
3356 /* check that all button labels can be read */
3357 for (i=2; i<nArgs; i++)
3358 if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg))
3359 return False;
3361 /* pick up the first button */
3362 if (nArgs == 2) {
3363 btnLabel = "OK";
3364 nBtns = 1;
3366 else {
3367 nBtns = nArgs - 2;
3368 argList += 2;
3369 readStringArg(argList[0], &btnLabel, btnStorage, errMsg);
3372 /* count the lines in the text - add one for unterminated last line */
3373 nlines = 1;
3374 for (p = text; *p; p++)
3375 if (*p == '\n')
3376 nlines++;
3378 /* now set up arrays of pointers to lines */
3379 /* test_strings to hold the display strings (tab expanded) */
3380 /* text_lines to hold the original text lines (without the '\n's) */
3381 test_strings = (XmString *) XtMalloc(sizeof(XmString) * nlines);
3382 text_lines = (char **)XtMalloc(sizeof(char *) * (nlines + 1));
3383 for (n = 0; n < nlines; n++) {
3384 test_strings[n] = (XmString)0;
3385 text_lines[n] = (char *)0;
3387 text_lines[n] = (char *)0; /* make sure this is a null-terminated table */
3389 /* pick up the tabDist value */
3390 tabDist = window->buffer->tabDist;
3392 /* load the table */
3393 n = 0;
3394 is_last = 0;
3395 p = old_p = text;
3396 tmp_len = 0; /* current allocated size of temporary buffer tmp */
3397 tmp = malloc(1); /* temporary buffer into which to expand tabs */
3398 do {
3399 is_last = (*p == '\0');
3400 if (*p == '\n' || is_last) {
3401 *p = '\0';
3402 if (strlen(old_p) > 0) { /* only include non-empty lines */
3403 char *s, *t;
3404 int l;
3406 /* save the actual text line in text_lines[n] */
3407 text_lines[n] = (char *)XtMalloc(strlen(old_p) + 1);
3408 strcpy(text_lines[n], old_p);
3410 /* work out the tabs expanded length */
3411 for (s = old_p, l = 0; *s; s++)
3412 l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
3414 /* verify tmp is big enough then tab-expand old_p into tmp */
3415 if (l > tmp_len)
3416 tmp = realloc(tmp, (tmp_len = l) + 1);
3417 for (s = old_p, t = tmp, l = 0; *s; s++) {
3418 if (*s == '\t') {
3419 for (i = tabDist - (l % tabDist); i--; l++)
3420 *t++ = ' ';
3422 else {
3423 *t++ = *s;
3424 l++;
3427 *t = '\0';
3428 /* that's it: tmp is the tab-expanded version of old_p */
3429 test_strings[n] = MKSTRING(tmp);
3430 n++;
3432 old_p = p + 1;
3433 if (!is_last)
3434 *p = '\n'; /* put back our newline */
3436 p++;
3437 } while (!is_last);
3439 free(tmp); /* don't need this anymore */
3440 nlines = n;
3441 if (nlines == 0) {
3442 test_strings[0] = MKSTRING("");
3443 nlines = 1;
3446 /* Create the selection box dialog widget and its dialog shell parent */
3447 ac = 0;
3448 XtSetArg(al[ac], XmNtitle, " "); ac++;
3449 XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++;
3450 XtSetArg(al[ac], XmNlistItems, test_strings); ac++;
3451 XtSetArg(al[ac], XmNlistItemCount, nlines); ac++;
3452 XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++;
3453 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++;
3454 dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac);
3455 if (2 == nArgs)
3457 /* Only set margin width for the default OK button */
3458 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3459 XmNmarginWidth, BUTTON_WIDTH_MARGIN,
3460 NULL);
3463 AddMotifCloseCallback(XtParent(dialog), listDialogCloseCB, window);
3464 XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window);
3465 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3466 XmNuserData, (XtPointer)1, NULL);
3467 XmStringFree(s1);
3468 XmStringFree(s2);
3469 cmdData->dialog = dialog;
3471 /* forget lines stored in list */
3472 while (n--)
3473 XmStringFree(test_strings[n]);
3474 XtFree((char *)test_strings);
3476 /* modify the list */
3477 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST),
3478 XmNselectionPolicy, XmSINGLE_SELECT,
3479 XmNuserData, (XtPointer)text_lines, NULL);
3481 /* Unmanage unneeded widgets */
3482 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
3483 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3484 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3485 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
3486 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));
3488 /* Make callback for the unmanaged cancel button (which can
3489 still get executed via the esc key) activate close box action */
3490 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3491 XmNactivateCallback, listDialogCloseCB, window);
3493 /* Add user specified buttons (1st is already done). Selection box
3494 requires a place-holder widget to be added before buttons can be
3495 added, that's what the separator below is for */
3496 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3497 for (i=1; i<nBtns; i++) {
3498 readStringArg(argList[i], &btnLabel, btnStorage, errMsg);
3499 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3500 XmNlabelString, s1=XmStringCreateSimple(btnLabel),
3501 XmNuserData, (XtPointer)(i+1), NULL);
3502 XtAddCallback(btn, XmNactivateCallback, listDialogBtnCB, window);
3503 XmStringFree(s1);
3506 #ifdef LESSTIF_VERSION
3507 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3508 the escape key for closing the dialog. */
3509 XtAddEventHandler(dialog, KeyPressMask, False, listDialogEscCB,
3510 (XtPointer)window);
3511 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3512 True, GrabModeAsync, GrabModeAsync);
3513 #endif /* LESSTIF_VERSION */
3515 /* Put up the dialog */
3516 ManageDialogCenteredOnPointer(dialog);
3518 /* Stop macro execution until the dialog is complete */
3519 PreemptMacro();
3521 /* Return placeholder result. Value will be changed by button callback */
3522 result->tag = INT_TAG;
3523 result->val.n = 0;
3524 return True;
3527 static void listDialogBtnCB(Widget w, XtPointer clientData,
3528 XtPointer callData)
3530 WindowInfo *window = (WindowInfo *)clientData;
3531 macroCmdInfo *cmdData = window->macroCmdData;
3532 XtPointer userData;
3533 DataValue retVal;
3534 char *text;
3535 char **text_lines;
3536 int btnNum;
3537 int n_sel, *seltable, sel_index = 0;
3538 Widget theList;
3539 size_t length;
3541 /* shouldn't happen, but would crash if it did */
3542 if (cmdData == NULL)
3543 return;
3545 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3546 /* Return the string selected in the selection list area */
3547 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3548 if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) {
3549 n_sel = 0;
3551 else {
3552 sel_index = seltable[0] - 1;
3553 XtFree((XtPointer)seltable);
3556 if (!n_sel) {
3557 text = PERM_ALLOC_STR("");
3558 length = 0;
3560 else {
3561 length = strlen((char *)text_lines[sel_index]);
3562 text = AllocString(length + 1);
3563 strcpy(text, text_lines[sel_index]);
3566 /* don't need text_lines anymore: free it */
3567 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3568 XtFree((XtPointer)text_lines[sel_index]);
3569 XtFree((XtPointer)text_lines);
3571 retVal.tag = STRING_TAG;
3572 retVal.val.str.rep = text;
3573 retVal.val.str.len = length;
3574 ModifyReturnedValue(cmdData->context, retVal);
3576 /* Find the index of the button which was pressed (stored in the userData
3577 field of the button widget). The 1st button, being a gadget, is not
3578 returned in w. */
3579 if (XtClass(w) == xmPushButtonWidgetClass) {
3580 XtVaGetValues(w, XmNuserData, &userData, NULL);
3581 btnNum = (int)userData;
3582 } else
3583 btnNum = 1;
3585 /* Return the button number in the global variable $list_dialog_button */
3586 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3587 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = btnNum;
3589 /* Pop down the dialog */
3590 XtDestroyWidget(XtParent(cmdData->dialog));
3591 cmdData->dialog = NULL;
3593 /* Continue preempted macro execution */
3594 ResumeMacroExecution(window);
3597 static void listDialogCloseCB(Widget w, XtPointer clientData,
3598 XtPointer callData)
3600 WindowInfo *window = (WindowInfo *)clientData;
3601 macroCmdInfo *cmdData = window->macroCmdData;
3602 DataValue retVal;
3603 char **text_lines;
3604 int sel_index;
3605 Widget theList;
3607 /* shouldn't happen, but would crash if it did */
3608 if (cmdData == NULL)
3609 return;
3611 /* don't need text_lines anymore: retrieve it then free it */
3612 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3613 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3614 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3615 XtFree((XtPointer)text_lines[sel_index]);
3616 XtFree((XtPointer)text_lines);
3618 /* Return an empty string */
3619 retVal.tag = STRING_TAG;
3620 retVal.val.str.rep = PERM_ALLOC_STR("");
3621 retVal.val.str.len = 0;
3622 ModifyReturnedValue(cmdData->context, retVal);
3624 /* Return button number 0 in the global variable $list_dialog_button */
3625 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3626 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = 0;
3628 /* Pop down the dialog */
3629 XtDestroyWidget(XtParent(cmdData->dialog));
3630 cmdData->dialog = NULL;
3632 /* Continue preempted macro execution */
3633 ResumeMacroExecution(window);
3635 /* T Balinski End */
3637 #ifdef LESSTIF_VERSION
3638 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3639 Boolean *cont)
3641 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3642 return;
3643 if (clientData != NULL) {
3644 listDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3646 *cont = False;
3648 #endif /* LESSTIF_VERSION */
3651 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
3652 DataValue *result, char **errMsg)
3654 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3655 char *leftStr, *rightStr, *argStr;
3656 int considerCase = True;
3657 int i;
3658 int compareResult;
3660 if (nArgs < 2) {
3661 return(wrongNArgsErr(errMsg));
3663 if (!readStringArg(argList[0], &leftStr, stringStorage[0], errMsg))
3664 return False;
3665 if (!readStringArg(argList[1], &rightStr, stringStorage[1], errMsg))
3666 return False;
3667 for (i = 2; i < nArgs; ++i) {
3668 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
3669 return False;
3670 else if (!strcmp(argStr, "case"))
3671 considerCase = True;
3672 else if (!strcmp(argStr, "nocase"))
3673 considerCase = False;
3674 else {
3675 *errMsg = "Unrecognized argument to %s";
3676 return False;
3679 if (considerCase) {
3680 compareResult = strcmp(leftStr, rightStr);
3681 compareResult = (compareResult > 0) ? 1 : ((compareResult < 0) ? -1 : 0);
3683 else {
3684 compareResult = strCaseCmp(leftStr, rightStr);
3686 result->tag = INT_TAG;
3687 result->val.n = compareResult;
3688 return True;
3692 ** This function is intended to split strings into an array of substrings
3693 ** Importatnt note: It should always return at least one entry with key 0
3694 ** split("", ",") result[0] = ""
3695 ** split("1,2", ",") result[0] = "1" result[1] = "2"
3696 ** split("1,2,", ",") result[0] = "1" result[1] = "2" result[2] = ""
3698 ** This behavior is specifically important when used to break up
3699 ** array sub-scripts
3702 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
3703 DataValue *result, char **errMsg)
3705 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3706 char *sourceStr, *splitStr, *typeSplitStr;
3707 int searchType, beginPos, foundStart, foundEnd, strLength, lastEnd;
3708 int found, elementEnd, indexNum;
3709 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
3710 DataValue element;
3711 int elementLen;
3713 if (nArgs < 2) {
3714 return(wrongNArgsErr(errMsg));
3716 if (!readStringArg(argList[0], &sourceStr, stringStorage[0], errMsg)) {
3717 *errMsg = "first argument must be a string: %s";
3718 return(False);
3720 if (!readStringArg(argList[1], &splitStr, stringStorage[1], errMsg)) {
3721 splitStr = NULL;
3723 else {
3724 if (splitStr[0] == 0) {
3725 splitStr = NULL;
3728 if (splitStr == NULL) {
3729 *errMsg = "second argument must be a non-empty string: %s";
3730 return(False);
3732 if (nArgs > 2 && readStringArg(argList[2], &typeSplitStr, stringStorage[2], errMsg)) {
3733 if (!StringToSearchType(typeSplitStr, &searchType)) {
3734 *errMsg = "unrecognized argument to %s";
3735 return(False);
3738 else {
3739 searchType = SEARCH_LITERAL;
3742 result->tag = ARRAY_TAG;
3743 result->val.arrayPtr = ArrayNew();
3745 beginPos = 0;
3746 lastEnd = 0;
3747 indexNum = 0;
3748 strLength = strlen(sourceStr);
3749 found = 1;
3750 while (found && beginPos < strLength) {
3751 sprintf(indexStr, "%d", indexNum);
3752 allocIndexStr = AllocString(strlen(indexStr) + 1);
3753 if (!allocIndexStr) {
3754 *errMsg = "array element failed to allocate key: %s";
3755 return(False);
3757 strcpy(allocIndexStr, indexStr);
3758 found = SearchString(sourceStr, splitStr, SEARCH_FORWARD, searchType,
3759 False, beginPos, &foundStart, &foundEnd,
3760 NULL, NULL, GetWindowDelimiters(window));
3761 elementEnd = found ? foundStart : strLength;
3762 elementLen = elementEnd - lastEnd;
3763 element.tag = STRING_TAG;
3764 if (!AllocNStringNCpy(&element.val.str, &sourceStr[lastEnd], elementLen)) {
3765 *errMsg = "failed to allocate element value: %s";
3766 return(False);
3769 if (!ArrayInsert(result, allocIndexStr, &element)) {
3770 M_ARRAY_INSERT_FAILURE();
3773 if (found) {
3774 if (foundStart == foundEnd) {
3775 beginPos = foundEnd + 1; /* Avoid endless loop for 0-width match */
3776 } else {
3777 beginPos = foundEnd;
3779 } else {
3780 beginPos = strLength; /* Break the loop */
3782 lastEnd = foundEnd;
3783 ++indexNum;
3785 if (found) {
3786 sprintf(indexStr, "%d", indexNum);
3787 allocIndexStr = AllocString(strlen(indexStr) + 1);
3788 if (!allocIndexStr) {
3789 *errMsg = "array element failed to allocate key: %s";
3790 return(False);
3792 strcpy(allocIndexStr, indexStr);
3793 element.tag = STRING_TAG;
3794 if (lastEnd == strLength) {
3795 /* The pattern mathed the end of the string. Add an empty chunk. */
3796 element.val.str.rep = PERM_ALLOC_STR("");
3797 element.val.str.len = 0;
3799 if (!ArrayInsert(result, allocIndexStr, &element)) {
3800 M_ARRAY_INSERT_FAILURE();
3802 } else {
3803 /* We skipped the last character to prevent an endless loop.
3804 Add it to the list. */
3805 elementLen = strLength - lastEnd;
3806 if (!AllocNStringNCpy(&element.val.str, &sourceStr[lastEnd], elementLen)) {
3807 *errMsg = "failed to allocate element value: %s";
3808 return(False);
3811 if (!ArrayInsert(result, allocIndexStr, &element)) {
3812 M_ARRAY_INSERT_FAILURE();
3815 /* If the pattern can match zero-length strings, we may have to
3816 add a final empty chunk.
3817 For instance: split("abc\n", "$", "regex")
3818 -> matches before \n and at end of string
3819 -> expected output: "abc", "\n", ""
3820 The '\n' gets added in the lines above, but we still have to
3821 verify whether the pattern also matches the end of the string,
3822 and add an empty chunk in case it does. */
3823 found = SearchString(sourceStr, splitStr, SEARCH_FORWARD,
3824 searchType, False, strLength, &foundStart, &foundEnd,
3825 NULL, NULL, GetWindowDelimiters(window));
3826 if (found) {
3827 ++indexNum;
3828 sprintf(indexStr, "%d", indexNum);
3829 allocIndexStr = AllocString(strlen(indexStr) + 1);
3830 if (!allocIndexStr) {
3831 *errMsg = "array element failed to allocate key: %s";
3832 return(False);
3834 strcpy(allocIndexStr, indexStr);
3835 element.tag = STRING_TAG;
3836 element.val.str.rep = PERM_ALLOC_STR("");
3837 element.val.str.len = 0;
3839 if (!ArrayInsert(result, allocIndexStr, &element)) {
3840 M_ARRAY_INSERT_FAILURE();
3845 return(True);
3849 ** Set the backlighting string resource for the current window. If no parameter
3850 ** is passed or the value "default" is passed, it attempts to set the preference
3851 ** value of the resource. If the empty string is passed, the backlighting string
3852 ** will be cleared, turning off backlighting.
3854 /* DISABLED for 5.4
3855 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
3856 int nArgs, DataValue *result, char **errMsg)
3858 char *backlightString;
3860 if (nArgs == 0) {
3861 backlightString = GetPrefBacklightCharTypes();
3863 else if (nArgs == 1) {
3864 if (argList[0].tag != STRING_TAG) {
3865 *errMsg = "%s not called with a string parameter";
3866 return False;
3868 backlightString = argList[0].val.str.rep;
3870 else
3871 return wrongNArgsErr(errMsg);
3873 if (strcmp(backlightString, "default") == 0)
3874 backlightString = GetPrefBacklightCharTypes();
3875 if (backlightString && *backlightString == '\0') / * empty string param * /
3876 backlightString = NULL; / * turns of backlighting * /
3878 SetBacklightChars(window, backlightString);
3879 return True;
3880 } */
3882 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
3883 DataValue *result, char **errMsg)
3885 result->tag = INT_TAG;
3886 result->val.n = TextGetCursorPos(window->lastFocus);
3887 return True;
3890 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
3891 DataValue *result, char **errMsg)
3893 int line, cursorPos, colNum;
3895 result->tag = INT_TAG;
3896 cursorPos = TextGetCursorPos(window->lastFocus);
3897 if (!TextPosToLineAndCol(window->lastFocus, cursorPos, &line, &colNum))
3898 line = BufCountLines(window->buffer, 0, cursorPos) + 1;
3899 result->val.n = line;
3900 return True;
3903 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
3904 DataValue *result, char **errMsg)
3906 textBuffer *buf = window->buffer;
3907 int cursorPos;
3909 result->tag = INT_TAG;
3910 cursorPos = TextGetCursorPos(window->lastFocus);
3911 result->val.n = BufCountDispChars(buf, BufStartOfLine(buf, cursorPos),
3912 cursorPos);
3913 return True;
3916 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
3917 DataValue *result, char **errMsg)
3919 result->tag = STRING_TAG;
3920 AllocNStringCpy(&result->val.str, window->filename);
3921 return True;
3924 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
3925 DataValue *result, char **errMsg)
3927 result->tag = STRING_TAG;
3928 AllocNStringCpy(&result->val.str, window->path);
3929 return True;
3932 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
3933 DataValue *result, char **errMsg)
3935 result->tag = INT_TAG;
3936 result->val.n = window->buffer->length;
3937 return True;
3940 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
3941 DataValue *result, char **errMsg)
3943 result->tag = INT_TAG;
3944 result->val.n = window->buffer->primary.selected ?
3945 window->buffer->primary.start : -1;
3946 return True;
3949 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
3950 DataValue *result, char **errMsg)
3952 result->tag = INT_TAG;
3953 result->val.n = window->buffer->primary.selected ?
3954 window->buffer->primary.end : -1;
3955 return True;
3958 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
3959 DataValue *result, char **errMsg)
3961 selection *sel = &window->buffer->primary;
3963 result->tag = INT_TAG;
3964 result->val.n = sel->selected && sel->rectangular ? sel->rectStart : -1;
3965 return True;
3968 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
3969 DataValue *result, char **errMsg)
3971 selection *sel = &window->buffer->primary;
3973 result->tag = INT_TAG;
3974 result->val.n = sel->selected && sel->rectangular ? sel->rectEnd : -1;
3975 return True;
3978 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
3979 DataValue *result, char **errMsg)
3981 int margin, nCols;
3983 XtVaGetValues(window->textArea, textNcolumns, &nCols,
3984 textNwrapMargin, &margin, NULL);
3985 result->tag = INT_TAG;
3986 result->val.n = margin == 0 ? nCols : margin;
3987 return True;
3990 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
3991 DataValue *result, char **errMsg)
3993 result->tag = INT_TAG;
3994 result->val.n = window->showStats ? 1 : 0;
3995 return True;
3998 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
3999 DataValue *result, char **errMsg)
4001 result->tag = INT_TAG;
4002 result->val.n = window->showISearchLine ? 1 : 0;
4003 return True;
4006 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
4007 DataValue *result, char **errMsg)
4009 result->tag = INT_TAG;
4010 result->val.n = window->showLineNumbers ? 1 : 0;
4011 return True;
4014 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
4015 DataValue *result, char **errMsg)
4017 char *res = NULL;
4019 switch (window->indentStyle) {
4020 case NO_AUTO_INDENT:
4021 res = PERM_ALLOC_STR("off");
4022 break;
4023 case AUTO_INDENT:
4024 res = PERM_ALLOC_STR("on");
4025 break;
4026 case SMART_INDENT:
4027 res = PERM_ALLOC_STR("smart");
4028 break;
4029 default:
4030 *errMsg = "Invalid indent style value encountered in %s";
4031 return False;
4032 break;
4034 result->tag = STRING_TAG;
4035 result->val.str.rep = res;
4036 result->val.str.len = strlen(res);
4037 return True;
4040 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
4041 DataValue *result, char **errMsg)
4043 char *res = NULL;
4045 switch (window->wrapMode) {
4046 case NO_WRAP:
4047 res = PERM_ALLOC_STR("none");
4048 break;
4049 case NEWLINE_WRAP:
4050 res = PERM_ALLOC_STR("auto");
4051 break;
4052 case CONTINUOUS_WRAP:
4053 res = PERM_ALLOC_STR("continuous");
4054 break;
4055 default:
4056 *errMsg = "Invalid wrap style value encountered in %s";
4057 return False;
4058 break;
4060 result->tag = STRING_TAG;
4061 result->val.str.rep = res;
4062 result->val.str.len = strlen(res);
4063 return True;
4066 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
4067 DataValue *result, char **errMsg)
4069 result->tag = INT_TAG;
4070 result->val.n = window->highlightSyntax ? 1 : 0;
4071 return True;
4074 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
4075 DataValue *result, char **errMsg)
4077 result->tag = INT_TAG;
4078 result->val.n = window->saveOldVersion ? 1 : 0;
4079 return True;
4082 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
4083 DataValue *result, char **errMsg)
4085 result->tag = INT_TAG;
4086 result->val.n = window->autoSave ? 1 : 0;
4087 return True;
4090 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
4091 DataValue *result, char **errMsg)
4093 char *res = NULL;
4095 switch (window->showMatchingStyle) {
4096 case NO_FLASH:
4097 res = PERM_ALLOC_STR(NO_FLASH_STRING);
4098 break;
4099 case FLASH_DELIMIT:
4100 res = PERM_ALLOC_STR(FLASH_DELIMIT_STRING);
4101 break;
4102 case FLASH_RANGE:
4103 res = PERM_ALLOC_STR(FLASH_RANGE_STRING);
4104 break;
4105 default:
4106 *errMsg = "Invalid match flashing style value encountered in %s";
4107 return False;
4108 break;
4110 result->tag = STRING_TAG;
4111 result->val.str.rep = res;
4112 result->val.str.len = strlen(res);
4113 return True;
4116 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4117 DataValue *result, char **errMsg)
4119 result->tag = INT_TAG;
4120 result->val.n = window->overstrike ? 1 : 0;
4121 return True;
4124 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
4125 DataValue *result, char **errMsg)
4127 result->tag = INT_TAG;
4128 result->val.n = (IS_ANY_LOCKED(window->lockReasons)) ? 1 : 0;
4129 return True;
4132 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
4133 DataValue *result, char **errMsg)
4135 result->tag = INT_TAG;
4136 result->val.n = (IS_USER_LOCKED(window->lockReasons)) ? 1 : 0;
4137 return True;
4140 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
4141 DataValue *result, char **errMsg)
4143 char *res = NULL;
4145 switch (window->fileFormat) {
4146 case UNIX_FILE_FORMAT:
4147 res = PERM_ALLOC_STR("unix");
4148 break;
4149 case DOS_FILE_FORMAT:
4150 res = PERM_ALLOC_STR("dos");
4151 break;
4152 case MAC_FILE_FORMAT:
4153 res = PERM_ALLOC_STR("macintosh");
4154 break;
4155 default:
4156 *errMsg = "Invalid linefeed style value encountered in %s";
4157 return False;
4159 result->tag = STRING_TAG;
4160 result->val.str.rep = res;
4161 result->val.str.len = strlen(res);
4162 return True;
4165 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4166 DataValue *result, char **errMsg)
4168 result->tag = STRING_TAG;
4169 AllocNStringCpy(&result->val.str, window->fontName);
4170 return True;
4173 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4174 DataValue *result, char **errMsg)
4176 result->tag = STRING_TAG;
4177 AllocNStringCpy(&result->val.str, window->italicFontName);
4178 return True;
4181 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
4182 DataValue *result, char **errMsg)
4184 result->tag = STRING_TAG;
4185 AllocNStringCpy(&result->val.str, window->boldFontName);
4186 return True;
4189 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4190 DataValue *result, char **errMsg)
4192 result->tag = STRING_TAG;
4193 AllocNStringCpy(&result->val.str, window->boldItalicFontName);
4194 return True;
4197 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
4198 DataValue *result, char **errMsg)
4200 result->tag = STRING_TAG;
4201 result->val.str.rep = PERM_ALLOC_STR(ARRAY_DIM_SEP);
4202 result->val.str.len = strlen(result->val.str.rep);
4203 return True;
4206 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4207 DataValue *result, char **errMsg)
4209 result->tag = INT_TAG;
4210 result->val.n = TextGetMinFontWidth(window->textArea, window->highlightSyntax);
4211 return True;
4214 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4215 DataValue *result, char **errMsg)
4217 result->tag = INT_TAG;
4218 result->val.n = TextGetMaxFontWidth(window->textArea, window->highlightSyntax);
4219 return True;
4222 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4223 DataValue *result, char **errMsg)
4225 result->tag = INT_TAG;
4226 result->val.n = TextFirstVisibleLine(window->lastFocus);
4227 return True;
4230 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
4231 DataValue *result, char **errMsg)
4233 result->tag = INT_TAG;
4234 result->val.n = TextNumVisibleLines(window->lastFocus);
4235 return True;
4238 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4239 DataValue *result, char **errMsg)
4241 result->tag = INT_TAG;
4242 result->val.n = TextVisibleWidth(window->lastFocus);
4243 return True;
4246 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
4247 DataValue *result, char **errMsg)
4249 result->tag = INT_TAG;
4250 result->val.n = WidgetToPaneIndex(window, window->lastFocus) + 1;
4251 return True;
4254 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
4255 DataValue *result, char **errMsg)
4257 result->tag = INT_TAG;
4258 result->val.n = window->nPanes + 1;
4259 return True;
4262 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
4263 DataValue *result, char **errMsg)
4265 result->tag = ARRAY_TAG;
4266 result->val.arrayPtr = NULL;
4267 return True;
4270 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4271 DataValue *result, char **errMsg)
4273 result->tag = STRING_TAG;
4274 AllocNStringCpy(&result->val.str, GetPrefServerName());
4275 return True;
4278 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4279 DataValue *result, char **errMsg)
4281 result->tag = INT_TAG;
4282 result->val.n = window->buffer->tabDist;
4283 return True;
4286 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4287 DataValue *result, char **errMsg)
4289 int dist;
4291 XtVaGetValues(window->textArea, textNemulateTabs, &dist, NULL);
4292 result->tag = INT_TAG;
4293 result->val.n = dist == 0 ? -1 : dist;
4294 return True;
4297 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
4298 DataValue *result, char **errMsg)
4300 result->tag = INT_TAG;
4301 result->val.n = window->buffer->useTabs;
4302 return True;
4305 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
4306 DataValue *result, char **errMsg)
4308 result->tag = INT_TAG;
4309 result->val.n = window->fileChanged;
4310 return True;
4313 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4314 DataValue *result, char **errMsg)
4316 char *lmName = LanguageModeName(window->languageMode);
4318 if (lmName == NULL)
4319 lmName = "Plain";
4320 result->tag = STRING_TAG;
4321 AllocNStringCpy(&result->val.str, lmName);
4322 return True;
4325 /* DISABLED for 5.4
4326 static int backlightStringMV(WindowInfo *window, DataValue *argList,
4327 int nArgs, DataValue *result, char **errMsg)
4329 char *backlightString = window->backlightCharTypes;
4331 result->tag = STRING_TAG;
4332 if (!backlightString || !window->backlightChars)
4333 backlightString = "";
4334 AllocNStringCpy(&result->val.str, backlightString);
4335 return True;
4336 } */
4338 /* -------------------------------------------------------------------------- */
4341 ** Range set macro variables and functions
4344 static int rangesetListMV(WindowInfo *window, DataValue *argList, int nArgs,
4345 DataValue *result, char **errMsg)
4347 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4348 unsigned char *rangesetList;
4349 char *allocIndexStr;
4350 char indexStr[TYPE_INT_STR_SIZE(int)] ;
4351 int nRangesets, i;
4352 DataValue element;
4354 result->tag = ARRAY_TAG;
4355 result->val.arrayPtr = ArrayNew();
4357 if (rangesetTable == NULL) {
4358 return True;
4361 rangesetList = RangesetGetList(rangesetTable);
4362 nRangesets = strlen((char*)rangesetList);
4363 for(i = 0; i < nRangesets; i++) {
4364 element.tag = INT_TAG;
4365 element.val.n = rangesetList[i];
4367 sprintf(indexStr, "%d", nRangesets - i - 1);
4368 allocIndexStr = AllocString(strlen(indexStr) + 1);
4369 if (allocIndexStr == NULL)
4370 M_FAILURE("Failed to allocate array key in %s");
4371 strcpy(allocIndexStr, indexStr);
4373 if (!ArrayInsert(result, allocIndexStr, &element))
4374 M_FAILURE("Failed to insert array element in %s");
4377 return True;
4382 ** Built-in macro subroutine to create a new rangeset or rangesets.
4383 ** If called with one argument: $1 is the number of rangesets required and
4384 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4385 ** (or an empty array if the requested number of rangesets are not available).
4386 ** If called with no arguments, returns a single rangeset label (not an array),
4387 ** or an empty string if there are no rangesets available.
4389 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
4390 DataValue *result, char **errMsg)
4392 int label;
4393 int i, nRangesetsRequired;
4394 DataValue element;
4395 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
4397 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4399 if (nArgs > 1)
4400 return wrongNArgsErr(errMsg);
4402 if (rangesetTable == NULL) {
4403 window->buffer->rangesetTable = rangesetTable =
4404 RangesetTableAlloc(window->buffer);
4407 if (nArgs == 0) {
4408 label = RangesetCreate(rangesetTable);
4410 result->tag = INT_TAG;
4411 result->val.n = label;
4412 return True;
4414 else {
4415 if (!readIntArg(argList[0], &nRangesetsRequired, errMsg))
4416 return False;
4418 result->tag = ARRAY_TAG;
4419 result->val.arrayPtr = ArrayNew();
4421 if (nRangesetsRequired > nRangesetsAvailable(rangesetTable))
4422 return True;
4424 for (i = 0; i < nRangesetsRequired; i++) {
4425 element.tag = INT_TAG;
4426 element.val.n = RangesetCreate(rangesetTable);
4428 sprintf(indexStr, "%d", i);
4429 allocIndexStr = AllocString(strlen(indexStr) + 1);
4430 if (!allocIndexStr) {
4431 *errMsg = "Array element failed to allocate key: %s";
4432 return(False);
4434 strcpy(allocIndexStr, indexStr);
4435 ArrayInsert(result, allocIndexStr, &element);
4438 return True;
4444 ** Built-in macro subroutine for forgetting a range set.
4447 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
4448 DataValue *result, char **errMsg)
4450 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4451 DataValue *array;
4452 DataValue element;
4453 char keyString[TYPE_INT_STR_SIZE(int)];
4454 int deleteLabels[N_RANGESETS];
4455 int i, arraySize;
4456 int label = 0;
4458 if (nArgs != 1) {
4459 return wrongNArgsErr(errMsg);
4462 if (argList[0].tag == ARRAY_TAG) {
4463 array = &argList[0];
4464 arraySize = ArraySize(array);
4466 if (arraySize > N_RANGESETS) {
4467 M_FAILURE("Too many elements in array in %s");
4470 for (i = 0; i < arraySize; i++) {
4471 sprintf(keyString, "%d", i);
4473 if (!ArrayGet(array, keyString, &element)) {
4474 M_FAILURE("Invalid key in array in %s");
4477 if (!readIntArg(element, &label, errMsg)
4478 || !RangesetLabelOK(label)) {
4479 M_FAILURE("Invalid rangeset label in array in %s");
4482 deleteLabels[i] = label;
4485 for (i = 0; i < arraySize; i++) {
4486 RangesetForget(rangesetTable, deleteLabels[i]);
4490 else {
4491 if (!readIntArg(argList[0], &label, errMsg)
4492 || !RangesetLabelOK(label)) {
4493 M_FAILURE("Invalid rangeset label in %s");
4496 if(rangesetTable != NULL) {
4497 RangesetForget(rangesetTable, label);
4501 /* set up result */
4502 result->tag = NO_TAG;
4503 return True;
4508 ** Built-in macro subroutine for getting all range sets with a specfic name.
4509 ** Arguments are $1: range set name.
4510 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4513 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
4514 DataValue *result, char **errMsg)
4516 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4517 Rangeset *rangeset;
4518 int label;
4519 char *name, *rangeset_name;
4520 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4521 unsigned char *rangesetList;
4522 char *allocIndexStr;
4523 char indexStr[TYPE_INT_STR_SIZE(int)] ;
4524 int nRangesets, i, insertIndex = 0;
4525 DataValue element;
4527 if (nArgs != 1) {
4528 return wrongNArgsErr(errMsg);
4531 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
4532 M_FAILURE("First parameter is not a name string in %s");
4535 result->tag = ARRAY_TAG;
4536 result->val.arrayPtr = ArrayNew();
4538 if (rangesetTable == NULL) {
4539 return True;
4542 rangesetList = RangesetGetList(rangesetTable);
4543 nRangesets = strlen((char *)rangesetList);
4544 for (i = 0; i < nRangesets; ++i) {
4545 label = rangesetList[i];
4546 rangeset = RangesetFetch(rangesetTable, label);
4547 if (rangeset) {
4548 rangeset_name = RangesetGetName(rangeset);
4549 if (strcmp(name, rangeset_name ? rangeset_name : "") == 0) {
4550 element.tag = INT_TAG;
4551 element.val.n = label;
4553 sprintf(indexStr, "%d", insertIndex);
4554 allocIndexStr = AllocString(strlen(indexStr) + 1);
4555 if (allocIndexStr == NULL)
4556 M_FAILURE("Failed to allocate array key in %s");
4558 strcpy(allocIndexStr, indexStr);
4560 if (!ArrayInsert(result, allocIndexStr, &element))
4561 M_FAILURE("Failed to insert array element in %s");
4563 ++insertIndex;
4568 return True;
4573 ** Built-in macro subroutine for adding to a range set. Arguments are $1: range
4574 ** set label (one integer), then either (a) $2: source range set label,
4575 ** (b) $2: int start-range, $3: int end-range, (c) nothing (use selection
4576 ** if any to specify range to add - must not be rectangular). Returns the
4577 ** index of the newly added range (cases b and c), or 0 (case a).
4580 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
4581 DataValue *result, char **errMsg)
4583 textBuffer *buffer = window->buffer;
4584 RangesetTable *rangesetTable = buffer->rangesetTable;
4585 Rangeset *targetRangeset, *sourceRangeset;
4586 int start, end, isRect, rectStart, rectEnd, maxpos, index;
4587 int label = 0;
4589 if (nArgs < 1 || nArgs > 3)
4590 return wrongNArgsErr(errMsg);
4592 if (!readIntArg(argList[0], &label, errMsg)
4593 || !RangesetLabelOK(label)) {
4594 M_FAILURE("First parameter is an invalid rangeset label in %s");
4597 if (rangesetTable == NULL) {
4598 M_FAILURE("Rangeset does not exist in %s");
4601 targetRangeset = RangesetFetch(rangesetTable, label);
4603 if (targetRangeset == NULL) {
4604 M_FAILURE("Rangeset does not exist in %s");
4607 start = end = -1;
4609 if (nArgs == 1) {
4610 /* pick up current selection in this window */
4611 if (!BufGetSelectionPos(buffer, &start, &end,
4612 &isRect, &rectStart, &rectEnd) || isRect) {
4613 M_FAILURE("Selection missing or rectangular in call to %s");
4615 if (!RangesetAddBetween(targetRangeset, start, end)) {
4616 M_FAILURE("Failure to add selection in %s");
4620 if (nArgs == 2) {
4621 /* add ranges taken from a second set */
4622 if (!readIntArg(argList[1], &label, errMsg)
4623 || !RangesetLabelOK(label)) {
4624 M_FAILURE("Second parameter is an invalid rangeset label in %s");
4627 sourceRangeset = RangesetFetch(rangesetTable, label);
4628 if (sourceRangeset == NULL) {
4629 M_FAILURE("Second rangeset does not exist in %s");
4632 RangesetAdd(targetRangeset, sourceRangeset);
4635 if (nArgs == 3) {
4636 /* add a range bounded by the start and end positions in $2, $3 */
4637 if (!readIntArg(argList[1], &start, errMsg)) {
4638 return False;
4640 if (!readIntArg(argList[2], &end, errMsg)) {
4641 return False;
4644 /* make sure range is in order and fits buffer size */
4645 maxpos = buffer->length;
4646 if (start < 0) start = 0;
4647 if (start > maxpos) start = maxpos;
4648 if (end < 0) end = 0;
4649 if (end > maxpos) end = maxpos;
4650 if (start > end) {int temp = start; start = end; end = temp;}
4652 if ((start != end) && !RangesetAddBetween(targetRangeset, start, end)) {
4653 M_FAILURE("Failed to add range in %s");
4657 /* (to) which range did we just add? */
4658 if (nArgs != 2 && start >= 0) {
4659 start = (start + end) / 2; /* "middle" of added range */
4660 index = 1 + RangesetFindRangeOfPos(targetRangeset, start, False);
4662 else {
4663 index = 0;
4666 /* set up result */
4667 result->tag = INT_TAG;
4668 result->val.n = index;
4669 return True;
4674 ** Built-in macro subroutine for removing from a range set. Almost identical to
4675 ** rangesetAddMS() - only changes are from RangesetAdd()/RangesetAddBetween()
4676 ** to RangesetSubtract()/RangesetSubtractBetween(), the handling of an
4677 ** undefined destination range, and that it returns no value.
4680 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
4681 DataValue *result, char **errMsg)
4683 textBuffer *buffer = window->buffer;
4684 RangesetTable *rangesetTable = buffer->rangesetTable;
4685 Rangeset *targetRangeset, *sourceRangeset;
4686 int start, end, isRect, rectStart, rectEnd, maxpos;
4687 int label = 0;
4689 if (nArgs < 1 || nArgs > 3) {
4690 return wrongNArgsErr(errMsg);
4693 if (!readIntArg(argList[0], &label, errMsg)
4694 || !RangesetLabelOK(label)) {
4695 M_FAILURE("First parameter is an invalid rangeset label in %s");
4698 if (rangesetTable == NULL) {
4699 M_FAILURE("Rangeset does not exist in %s");
4702 targetRangeset = RangesetFetch(rangesetTable, label);
4703 if (targetRangeset == NULL) {
4704 M_FAILURE("Rangeset does not exist in %s");
4707 if (nArgs == 1) {
4708 /* remove current selection in this window */
4709 if (!BufGetSelectionPos(buffer, &start, &end, &isRect, &rectStart, &rectEnd)
4710 || isRect) {
4711 M_FAILURE("Selection missing or rectangular in call to %s");
4713 RangesetRemoveBetween(targetRangeset, start, end);
4716 if (nArgs == 2) {
4717 /* remove ranges taken from a second set */
4718 if (!readIntArg(argList[1], &label, errMsg)
4719 || !RangesetLabelOK(label)) {
4720 M_FAILURE("Second parameter is an invalid rangeset label in %s");
4723 sourceRangeset = RangesetFetch(rangesetTable, label);
4724 if (sourceRangeset == NULL) {
4725 M_FAILURE("Second rangeset does not exist in %s");
4727 RangesetRemove(targetRangeset, sourceRangeset);
4730 if (nArgs == 3) {
4731 /* remove a range bounded by the start and end positions in $2, $3 */
4732 if (!readIntArg(argList[1], &start, errMsg))
4733 return False;
4734 if (!readIntArg(argList[2], &end, errMsg))
4735 return False;
4737 /* make sure range is in order and fits buffer size */
4738 maxpos = buffer->length;
4739 if (start < 0) start = 0;
4740 if (start > maxpos) start = maxpos;
4741 if (end < 0) end = 0;
4742 if (end > maxpos) end = maxpos;
4743 if (start > end) {int temp = start; start = end; end = temp;}
4745 RangesetRemoveBetween(targetRangeset, start, end);
4748 /* set up result */
4749 result->tag = NO_TAG;
4750 return True;
4755 ** Built-in macro subroutine to invert a range set. Argument is $1: range set
4756 ** label (one alphabetic character). Returns nothing. Fails if range set
4757 ** undefined.
4760 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
4761 DataValue *result, char **errMsg)
4764 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4765 Rangeset *rangeset;
4766 int label = 0;
4768 if (nArgs != 1)
4769 return wrongNArgsErr(errMsg);
4771 if (!readIntArg(argList[0], &label, errMsg)
4772 || !RangesetLabelOK(label)) {
4773 M_FAILURE("First parameter is an invalid rangeset label in %s");
4776 if (rangesetTable == NULL) {
4777 M_FAILURE("Rangeset does not exist in %s");
4780 rangeset = RangesetFetch(rangesetTable, label);
4781 if (rangeset == NULL) {
4782 M_FAILURE("Rangeset does not exist in %s");
4785 if (RangesetInverse(rangeset) < 0) {
4786 M_FAILURE("Problem inverting rangeset in %s");
4789 /* set up result */
4790 result->tag = NO_TAG;
4791 return True;
4796 ** Built-in macro subroutine for finding out info about a rangeset. Takes one
4797 ** argument of a rangeset label. Returns an array with the following keys:
4798 ** defined, count, color, mode.
4801 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
4802 DataValue *result, char **errMsg)
4804 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4805 Rangeset *rangeset = NULL;
4806 int count, defined;
4807 char *color, *name, *mode;
4808 DataValue element;
4809 int label = 0;
4811 if (nArgs != 1)
4812 return wrongNArgsErr(errMsg);
4814 if (!readIntArg(argList[0], &label, errMsg)
4815 || !RangesetLabelOK(label)) {
4816 M_FAILURE("First parameter is an invalid rangeset label in %s");
4819 if (rangesetTable != NULL) {
4820 rangeset = RangesetFetch(rangesetTable, label);
4823 RangesetGetInfo(rangeset, &defined, &label, &count, &color, &name, &mode);
4825 /* set up result */
4826 result->tag = ARRAY_TAG;
4827 result->val.arrayPtr = ArrayNew();
4829 element.tag = INT_TAG;
4830 element.val.n = defined;
4831 if (!ArrayInsert(result, PERM_ALLOC_STR("defined"), &element))
4832 M_FAILURE("Failed to insert array element \"defined\" in %s");
4834 element.tag = INT_TAG;
4835 element.val.n = count;
4836 if (!ArrayInsert(result, PERM_ALLOC_STR("count"), &element))
4837 M_FAILURE("Failed to insert array element \"count\" in %s");
4839 element.tag = STRING_TAG;
4840 if (!AllocNStringCpy(&element.val.str, color))
4841 M_FAILURE("Failed to allocate array value \"color\" in %s");
4842 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &element))
4843 M_FAILURE("Failed to insert array element \"color\" in %s");
4845 element.tag = STRING_TAG;
4846 if (!AllocNStringCpy(&element.val.str, name))
4847 M_FAILURE("Failed to allocate array value \"name\" in %s");
4848 if (!ArrayInsert(result, PERM_ALLOC_STR("name"), &element)) {
4849 M_FAILURE("Failed to insert array element \"name\" in %s");
4852 element.tag = STRING_TAG;
4853 if (!AllocNStringCpy(&element.val.str, mode))
4854 M_FAILURE("Failed to allocate array value \"mode\" in %s");
4855 if (!ArrayInsert(result, PERM_ALLOC_STR("mode"), &element))
4856 M_FAILURE("Failed to insert array element \"mode\" in %s");
4858 return True;
4862 ** Built-in macro subroutine for finding the extent of a range in a set.
4863 ** If only one parameter is supplied, use the spanning range of all
4864 ** ranges, otherwise select the individual range specified. Returns
4865 ** an array with the keys "start" and "end" and values
4868 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
4869 DataValue *result, char **errMsg)
4871 textBuffer *buffer = window->buffer;
4872 RangesetTable *rangesetTable = buffer->rangesetTable;
4873 Rangeset *rangeset;
4874 int start, end, dummy, rangeIndex, ok;
4875 DataValue element;
4876 int label = 0;
4878 if (nArgs < 1 || nArgs > 2) {
4879 return wrongNArgsErr(errMsg);
4882 if (!readIntArg(argList[0], &label, errMsg)
4883 || !RangesetLabelOK(label)) {
4884 M_FAILURE("First parameter is an invalid rangeset label in %s");
4887 if (rangesetTable == NULL) {
4888 M_FAILURE("Rangeset does not exist in %s");
4891 ok = False;
4892 rangeset = RangesetFetch(rangesetTable, label);
4893 if (rangeset != NULL) {
4894 if (nArgs == 1) {
4895 rangeIndex = RangesetGetNRanges(rangeset) - 1;
4896 ok = RangesetFindRangeNo(rangeset, 0, &start, &dummy);
4897 ok &= RangesetFindRangeNo(rangeset, rangeIndex, &dummy, &end);
4898 rangeIndex = -1;
4900 else if (nArgs == 2) {
4901 if (!readIntArg(argList[1], &rangeIndex, errMsg)) {
4902 return False;
4904 ok = RangesetFindRangeNo(rangeset, rangeIndex-1, &start, &end);
4908 /* set up result */
4909 result->tag = ARRAY_TAG;
4910 result->val.arrayPtr = ArrayNew();
4912 if (!ok)
4913 return True;
4915 element.tag = INT_TAG;
4916 element.val.n = start;
4917 if (!ArrayInsert(result, PERM_ALLOC_STR("start"), &element))
4918 M_FAILURE("Failed to insert array element \"start\" in %s");
4920 element.tag = INT_TAG;
4921 element.val.n = end;
4922 if (!ArrayInsert(result, PERM_ALLOC_STR("end"), &element))
4923 M_FAILURE("Failed to insert array element \"end\" in %s");
4925 return True;
4929 ** Built-in macro subroutine for checking a position against a range. If only
4930 ** one parameter is supplied, the current cursor position is used. Returns
4931 ** false (zero) if not in a range, range index (1-based) if in a range;
4932 ** fails if parameters were bad.
4935 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
4936 int nArgs, DataValue *result, char **errMsg)
4938 textBuffer *buffer = window->buffer;
4939 RangesetTable *rangesetTable = buffer->rangesetTable;
4940 Rangeset *rangeset;
4941 int pos, rangeIndex, maxpos;
4942 int label = 0;
4944 if (nArgs < 1 || nArgs > 2) {
4945 return wrongNArgsErr(errMsg);
4948 if (!readIntArg(argList[0], &label, errMsg)
4949 || !RangesetLabelOK(label)) {
4950 M_FAILURE("First parameter is an invalid rangeset label in %s");
4953 if (rangesetTable == NULL) {
4954 M_FAILURE("Rangeset does not exist in %s");
4957 rangeset = RangesetFetch(rangesetTable, label);
4958 if (rangeset == NULL) {
4959 M_FAILURE("Rangeset does not exist in %s");
4962 if (nArgs == 1) {
4963 pos = TextGetCursorPos(window->lastFocus);
4965 else if (nArgs == 2) {
4966 if (!readIntArg(argList[1], &pos, errMsg))
4967 return False;
4970 maxpos = buffer->length;
4971 if (pos < 0 || pos > maxpos) {
4972 rangeIndex = 0;
4974 else {
4975 rangeIndex = RangesetFindRangeOfPos(rangeset, pos, False) + 1;
4978 /* set up result */
4979 result->tag = INT_TAG;
4980 result->val.n = rangeIndex;
4981 return True;
4985 ** Set the color of a range set's ranges. it is ignored if the color cannot be
4986 ** found/applied. If no color is applied, any current color is removed. Returns
4987 ** true if the rangeset is valid.
4990 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
4991 int nArgs, DataValue *result, char **errMsg)
4993 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4994 textBuffer *buffer = window->buffer;
4995 RangesetTable *rangesetTable = buffer->rangesetTable;
4996 Rangeset *rangeset;
4997 char *color_name;
4998 int label = 0;
5000 if (nArgs != 2) {
5001 return wrongNArgsErr(errMsg);
5004 if (!readIntArg(argList[0], &label, errMsg)
5005 || !RangesetLabelOK(label)) {
5006 M_FAILURE("First parameter is an invalid rangeset label in %s");
5009 if (rangesetTable == NULL) {
5010 M_FAILURE("Rangeset does not exist in %s");
5013 rangeset = RangesetFetch(rangesetTable, label);
5014 if (rangeset == NULL) {
5015 M_FAILURE("Rangeset does not exist in %s");
5018 color_name = "";
5019 if (rangeset != NULL) {
5020 if (!readStringArg(argList[1], &color_name, stringStorage[0], errMsg)) {
5021 M_FAILURE("Second parameter is not a color name string in %s");
5025 RangesetAssignColorName(rangeset, color_name);
5027 /* set up result */
5028 result->tag = NO_TAG;
5029 return True;
5033 ** Set the name of a range set's ranges. Returns
5034 ** true if the rangeset is valid.
5037 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
5038 int nArgs, DataValue *result, char **errMsg)
5040 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5041 textBuffer *buffer = window->buffer;
5042 RangesetTable *rangesetTable = buffer->rangesetTable;
5043 Rangeset *rangeset;
5044 char *name;
5045 int label = 0;
5047 if (nArgs != 2) {
5048 return wrongNArgsErr(errMsg);
5051 if (!readIntArg(argList[0], &label, errMsg)
5052 || !RangesetLabelOK(label)) {
5053 M_FAILURE("First parameter is an invalid rangeset label in %s");
5056 if (rangesetTable == NULL) {
5057 M_FAILURE("Rangeset does not exist in %s");
5060 rangeset = RangesetFetch(rangesetTable, label);
5061 if (rangeset == NULL) {
5062 M_FAILURE("Rangeset does not exist in %s");
5065 name = "";
5066 if (rangeset != NULL) {
5067 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg)) {
5068 M_FAILURE("Second parameter is not a valid name string in %s");
5072 RangesetAssignName(rangeset, name);
5074 /* set up result */
5075 result->tag = NO_TAG;
5076 return True;
5080 ** Change a range's modification response. Returns true if the rangeset is
5081 ** valid and the response type name is valid.
5084 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
5085 int nArgs, DataValue *result, char **errMsg)
5087 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5088 textBuffer *buffer = window->buffer;
5089 RangesetTable *rangesetTable = buffer->rangesetTable;
5090 Rangeset *rangeset;
5091 char *update_fn_name;
5092 int ok;
5093 int label = 0;
5095 if (nArgs < 1 || nArgs > 2) {
5096 return wrongNArgsErr(errMsg);
5099 if (!readIntArg(argList[0], &label, errMsg)
5100 || !RangesetLabelOK(label)) {
5101 M_FAILURE("First parameter is an invalid rangeset label in %s");
5104 if (rangesetTable == NULL) {
5105 M_FAILURE("Rangeset does not exist in %s");
5108 rangeset = RangesetFetch(rangesetTable, label);
5109 if (rangeset == NULL) {
5110 M_FAILURE("Rangeset does not exist in %s");
5113 update_fn_name = "";
5114 if (rangeset != NULL) {
5115 if (nArgs == 2) {
5116 if (!readStringArg(argList[1], &update_fn_name, stringStorage[0], errMsg)) {
5117 M_FAILURE("Second parameter is not a string in %s");
5122 ok = RangesetChangeModifyResponse(rangeset, update_fn_name);
5124 if (!ok) {
5125 M_FAILURE("Second parameter is not a valid mode in %s");
5128 /* set up result */
5129 result->tag = NO_TAG;
5130 return True;
5133 /* -------------------------------------------------------------------------- */
5137 ** Routines to get details directly from the window.
5141 ** Sets up an array containing information about a style given its name or
5142 ** a buffer position (bufferPos >= 0) and its highlighting pattern code
5143 ** (patCode >= 0).
5144 ** From the name we obtain:
5145 ** ["color"] Foreground color name of style
5146 ** ["background"] Background color name of style if specified
5147 ** ["bold"] '1' if style is bold, '0' otherwise
5148 ** ["italic"] '1' if style is italic, '0' otherwise
5149 ** Given position and pattern code we obtain:
5150 ** ["rgb"] RGB representation of foreground color of style
5151 ** ["back_rgb"] RGB representation of background color of style
5152 ** ["extent"] Forward distance from position over which style applies
5153 ** We only supply the style name if the includeName parameter is set:
5154 ** ["style"] Name of style
5157 static int fillStyleResult(DataValue *result, char **errMsg,
5158 WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
5159 Boolean includeName, int patCode, int bufferPos)
5161 DataValue DV;
5162 char colorValue[20];
5163 int r, g, b;
5165 /* initialize array */
5166 result->tag = ARRAY_TAG;
5167 result->val.arrayPtr = ArrayNew();
5169 /* the following array entries will be strings */
5170 DV.tag = STRING_TAG;
5172 if (includeName) {
5173 /* insert style name */
5174 if (preallocatedStyleName) {
5175 DV.val.str.rep = styleName;
5176 DV.val.str.len = strlen(styleName);
5178 else {
5179 AllocNStringCpy(&DV.val.str, styleName);
5181 M_STR_ALLOC_ASSERT(DV);
5182 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5183 M_ARRAY_INSERT_FAILURE();
5187 /* insert color name */
5188 AllocNStringCpy(&DV.val.str, ColorOfNamedStyle(styleName));
5189 M_STR_ALLOC_ASSERT(DV);
5190 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &DV)) {
5191 M_ARRAY_INSERT_FAILURE();
5194 /* Prepare array element for color value
5195 (only possible if we pass through the dynamic highlight pattern tables
5196 in other words, only if we have a pattern code) */
5197 if (patCode) {
5198 HighlightColorValueOfCode(window, patCode, &r, &g, &b);
5199 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
5200 AllocNStringCpy(&DV.val.str, colorValue);
5201 M_STR_ALLOC_ASSERT(DV);
5202 if (!ArrayInsert(result, PERM_ALLOC_STR("rgb"), &DV)) {
5203 M_ARRAY_INSERT_FAILURE();
5207 /* Prepare array element for background color name */
5208 AllocNStringCpy(&DV.val.str, BgColorOfNamedStyle(styleName));
5209 M_STR_ALLOC_ASSERT(DV);
5210 if (!ArrayInsert(result, PERM_ALLOC_STR("background"), &DV)) {
5211 M_ARRAY_INSERT_FAILURE();
5214 /* Prepare array element for background color value
5215 (only possible if we pass through the dynamic highlight pattern tables
5216 in other words, only if we have a pattern code) */
5217 if (patCode) {
5218 GetHighlightBGColorOfCode(window, patCode, &r, &g, &b);
5219 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
5220 AllocNStringCpy(&DV.val.str, colorValue);
5221 M_STR_ALLOC_ASSERT(DV);
5222 if (!ArrayInsert(result, PERM_ALLOC_STR("back_rgb"), &DV)) {
5223 M_ARRAY_INSERT_FAILURE();
5227 /* the following array entries will be integers */
5228 DV.tag = INT_TAG;
5230 /* Put boldness value in array */
5231 DV.val.n = FontOfNamedStyleIsBold(styleName);
5232 if (!ArrayInsert(result, PERM_ALLOC_STR("bold"), &DV)) {
5233 M_ARRAY_INSERT_FAILURE();
5236 /* Put italicity value in array */
5237 DV.val.n = FontOfNamedStyleIsItalic(styleName);
5238 if (!ArrayInsert(result, PERM_ALLOC_STR("italic"), &DV)) {
5239 M_ARRAY_INSERT_FAILURE();
5242 if (bufferPos >= 0) {
5243 /* insert extent */
5244 const char *styleNameNotUsed = NULL;
5245 DV.val.n = StyleLengthOfCodeFromPos(window, bufferPos, &styleNameNotUsed);
5246 if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) {
5247 M_ARRAY_INSERT_FAILURE();
5250 return True;
5254 ** Returns an array containing information about the style of name $1
5255 ** ["color"] Foreground color name of style
5256 ** ["background"] Background color name of style if specified
5257 ** ["bold"] '1' if style is bold, '0' otherwise
5258 ** ["italic"] '1' if style is italic, '0' otherwise
5261 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5262 DataValue *result, char **errMsg)
5264 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5265 char *styleName;
5267 /* Validate number of arguments */
5268 if (nArgs != 1) {
5269 return wrongNArgsErr(errMsg);
5272 /* Prepare result */
5273 result->tag = ARRAY_TAG;
5274 result->val.arrayPtr = NULL;
5276 if (!readStringArg(argList[0], &styleName, stringStorage[0], errMsg)) {
5277 M_FAILURE("First parameter is not a string in %s");
5280 if (!NamedStyleExists(styleName)) {
5281 /* if the given name is invalid we just return an empty array. */
5282 return True;
5285 return fillStyleResult(result, errMsg, window,
5286 styleName, (argList[0].tag == STRING_TAG), False, 0, -1);
5290 ** Returns an array containing information about the style of position $1
5291 ** ["style"] Name of style
5292 ** ["color"] Foreground color name of style
5293 ** ["background"] Background color name of style if specified
5294 ** ["bold"] '1' if style is bold, '0' otherwise
5295 ** ["italic"] '1' if style is italic, '0' otherwise
5296 ** ["rgb"] RGB representation of foreground color of style
5297 ** ["back_rgb"] RGB representation of background color of style
5298 ** ["extent"] Forward distance from position over which style applies
5301 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5302 DataValue *result, char **errMsg)
5304 int patCode;
5305 int bufferPos;
5306 textBuffer *buf = window->buffer;
5308 /* Validate number of arguments */
5309 if (nArgs != 1) {
5310 return wrongNArgsErr(errMsg);
5313 /* Prepare result */
5314 result->tag = ARRAY_TAG;
5315 result->val.arrayPtr = NULL;
5317 if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5318 return False;
5321 /* Verify sane buffer position */
5322 if ((bufferPos < 0) || (bufferPos >= buf->length)) {
5323 /* If the position is not legal, we cannot guess anything about
5324 the style, so we return an empty array. */
5325 return True;
5328 /* Determine pattern code */
5329 patCode = HighlightCodeOfPos(window, bufferPos);
5330 if (patCode == 0) {
5331 /* if there is no pattern we just return an empty array. */
5332 return True;
5335 return fillStyleResult(result, errMsg, window,
5336 HighlightStyleOfCode(window, patCode), False, True, patCode, bufferPos);
5340 ** Sets up an array containing information about a pattern given its name or
5341 ** a buffer position (bufferPos >= 0).
5342 ** From the name we obtain:
5343 ** ["style"] Name of style
5344 ** ["extent"] Forward distance from position over which style applies
5345 ** We only supply the pattern name if the includeName parameter is set:
5346 ** ["pattern"] Name of pattern
5349 static int fillPatternResult(DataValue *result, char **errMsg,
5350 WindowInfo *window, char *patternName, Boolean preallocatedPatternName,
5351 Boolean includeName, char* styleName, int bufferPos)
5353 DataValue DV;
5355 /* initialize array */
5356 result->tag = ARRAY_TAG;
5357 result->val.arrayPtr = ArrayNew();
5359 /* the following array entries will be strings */
5360 DV.tag = STRING_TAG;
5362 if (includeName) {
5363 /* insert pattern name */
5364 if (preallocatedPatternName) {
5365 DV.val.str.rep = patternName;
5366 DV.val.str.len = strlen(patternName);
5368 else {
5369 AllocNStringCpy(&DV.val.str, patternName);
5371 M_STR_ALLOC_ASSERT(DV);
5372 if (!ArrayInsert(result, PERM_ALLOC_STR("pattern"), &DV)) {
5373 M_ARRAY_INSERT_FAILURE();
5377 /* insert style name */
5378 AllocNStringCpy(&DV.val.str, styleName);
5379 M_STR_ALLOC_ASSERT(DV);
5380 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5381 M_ARRAY_INSERT_FAILURE();
5384 /* the following array entries will be integers */
5385 DV.tag = INT_TAG;
5387 if (bufferPos >= 0) {
5388 /* insert extent */
5389 int checkCode = 0;
5390 DV.val.n = HighlightLengthOfCodeFromPos(window, bufferPos, &checkCode);
5391 if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) {
5392 M_ARRAY_INSERT_FAILURE();
5396 return True;
5400 ** Returns an array containing information about a highlighting pattern. The
5401 ** single parameter contains the pattern name for which this information is
5402 ** requested.
5403 ** The returned array looks like this:
5404 ** ["style"] Name of style
5406 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5407 DataValue *result, char **errMsg)
5409 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5410 char *patternName = NULL;
5411 highlightPattern *pattern;
5413 /* Begin of building the result. */
5414 result->tag = ARRAY_TAG;
5415 result->val.arrayPtr = NULL;
5417 /* Validate number of arguments */
5418 if (nArgs != 1) {
5419 return wrongNArgsErr(errMsg);
5422 if (!readStringArg(argList[0], &patternName, stringStorage[0], errMsg)) {
5423 M_FAILURE("First parameter is not a string in %s");
5426 pattern = FindPatternOfWindow(window, patternName);
5427 if (pattern == NULL) {
5428 /* The pattern's name is unknown. */
5429 return True;
5432 return fillPatternResult(result, errMsg, window, patternName,
5433 (argList[0].tag == STRING_TAG), False, pattern->style, -1);
5437 ** Returns an array containing information about the highlighting pattern
5438 ** applied at a given position, passed as the only parameter.
5439 ** The returned array looks like this:
5440 ** ["pattern"] Name of pattern
5441 ** ["style"] Name of style
5442 ** ["extent"] Distance from position over which this pattern applies
5444 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5445 DataValue *result, char **errMsg)
5447 int bufferPos = -1;
5448 textBuffer *buffer = window->buffer;
5449 int patCode = 0;
5451 /* Begin of building the result. */
5452 result->tag = ARRAY_TAG;
5453 result->val.arrayPtr = NULL;
5455 /* Validate number of arguments */
5456 if (nArgs != 1) {
5457 return wrongNArgsErr(errMsg);
5460 /* The most straightforward case: Get a pattern, style and extent
5461 for a buffer position. */
5462 if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5463 return False;
5466 /* Verify sane buffer position
5467 * You would expect that buffer->length would be among the sane
5468 * positions, but we have n characters and n+1 buffer positions. */
5469 if ((bufferPos < 0) || (bufferPos >= buffer->length)) {
5470 /* If the position is not legal, we cannot guess anything about
5471 the highlighting pattern, so we return an empty array. */
5472 return True;
5475 /* Determine the highlighting pattern used */
5476 patCode = HighlightCodeOfPos(window, bufferPos);
5477 if (patCode == 0) {
5478 /* if there is no highlighting pattern we just return an empty array. */
5479 return True;
5482 return fillPatternResult(result, errMsg, window,
5483 HighlightNameOfCode(window, patCode), False, True,
5484 HighlightStyleOfCode(window, patCode), bufferPos);
5487 static int wrongNArgsErr(char **errMsg)
5489 *errMsg = "Wrong number of arguments to function %s";
5490 return False;
5493 static int tooFewArgsErr(char **errMsg)
5495 *errMsg = "Too few arguments to function %s";
5496 return False;
5500 ** strCaseCmp compares its arguments and returns 0 if the two strings
5501 ** are equal IGNORING case differences. Otherwise returns 1 or -1
5502 ** depending on relative comparison.
5504 static int strCaseCmp(char *str1, char *str2)
5506 char *c1, *c2;
5508 for (c1 = str1, c2 = str2;
5509 (*c1 != '\0' && *c2 != '\0')
5510 && toupper((unsigned char)*c1) == toupper((unsigned char)*c2);
5511 ++c1, ++c2)
5515 if (((unsigned char)toupper((unsigned char)*c1))
5516 > ((unsigned char)toupper((unsigned char)*c2)))
5518 return(1);
5519 } else if (((unsigned char)toupper((unsigned char)*c1))
5520 < ((unsigned char)toupper((unsigned char)*c2)))
5522 return(-1);
5523 } else
5525 return(0);
5530 ** Get an integer value from a tagged DataValue structure. Return True
5531 ** if conversion succeeded, and store result in *result, otherwise
5532 ** return False with an error message in *errMsg.
5534 static int readIntArg(DataValue dv, int *result, char **errMsg)
5536 char *c;
5538 if (dv.tag == INT_TAG) {
5539 *result = dv.val.n;
5540 return True;
5541 } else if (dv.tag == STRING_TAG) {
5542 for (c=dv.val.str.rep; *c != '\0'; c++) {
5543 if (!(isdigit((unsigned char)*c) || *c == ' ' || *c == '\t')) {
5544 goto typeError;
5547 sscanf(dv.val.str.rep, "%d", result);
5548 return True;
5551 typeError:
5552 *errMsg = "%s called with non-integer argument";
5553 return False;
5557 ** Get an string value from a tagged DataValue structure. Return True
5558 ** if conversion succeeded, and store result in *result, otherwise
5559 ** return False with an error message in *errMsg. If an integer value
5560 ** is converted, write the string in the space provided by "stringStorage",
5561 ** which must be large enough to handle ints of the maximum size.
5563 static int readStringArg(DataValue dv, char **result, char *stringStorage,
5564 char **errMsg)
5566 if (dv.tag == STRING_TAG) {
5567 *result = dv.val.str.rep;
5568 return True;
5569 } else if (dv.tag == INT_TAG) {
5570 sprintf(stringStorage, "%d", dv.val.n);
5571 *result = stringStorage;
5572 return True;
5574 *errMsg = "%s called with unknown object";
5575 return False;