Another Lesstif Escape key workaround (list dialog) by Oliver Schmidt.
[nedit.git] / source / macro.c
blobbc8ad6bec644fb8242b8a6af4a78d605ff5b7e6c
1 static const char CVSID[] = "$Id: macro.c,v 1.73 2003/07/27 10:03:22 edg 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. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * April, 1997 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "macro.h"
35 #include "textBuf.h"
36 #include "text.h"
37 #include "nedit.h"
38 #include "window.h"
39 #include "preferences.h"
40 #include "interpret.h"
41 #include "parse.h"
42 #include "search.h"
43 #include "server.h"
44 #include "shell.h"
45 #include "smartIndent.h"
46 #include "userCmds.h"
47 #include "selection.h"
48 #include "rbTree.h"
49 #include "tags.h"
50 #include "calltips.h"
51 #include "../util/DialogF.h"
52 #include "../util/misc.h"
53 #include "../util/fileUtils.h"
54 #include "../util/utils.h"
55 #include "highlight.h"
56 #include "highlightData.h"
57 #include "rangeset.h"
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <ctype.h>
63 #include <errno.h>
64 #ifdef VMS
65 #include "../util/VMSparam.h"
66 #include <types.h>
67 #include <stat.h>
68 #include <unixio.h>
69 #else
70 #include <sys/types.h>
71 #include <sys/stat.h>
72 #ifndef __MVS__
73 #include <sys/param.h>
74 #endif
75 #include <fcntl.h>
76 #endif /*VMS*/
78 #include <X11/Intrinsic.h>
79 #include <X11/keysym.h>
80 #include <Xm/Xm.h>
81 #include <Xm/CutPaste.h>
82 #include <Xm/Form.h>
83 #include <Xm/RowColumn.h>
84 #include <Xm/LabelG.h>
85 #include <Xm/List.h>
86 #include <Xm/ToggleB.h>
87 #include <Xm/DialogS.h>
88 #include <Xm/MessageB.h>
89 #include <Xm/SelectioB.h>
90 #include <Xm/PushB.h>
91 #include <Xm/Text.h>
92 #include <Xm/Separator.h>
94 #ifdef HAVE_DEBUG_H
95 #include "../debug.h"
96 #endif
98 /* Maximum number of actions in a macro and args in
99 an action (to simplify the reader) */
100 #define MAX_MACRO_ACTIONS 1024
101 #define MAX_ACTION_ARGS 40
103 /* How long to wait (msec) before putting up Macro Command banner */
104 #define BANNER_WAIT_TIME 6000
106 /* The following definitions cause an exit from the macro with a message */
107 /* added if (1) to remove compiler warnings on solaris */
108 #define M_FAILURE(s) do { *errMsg = s; if (1) return False; } while (0)
109 #define M_STR_ALLOC_ASSERT(xDV) do { if (xDV.tag == STRING_TAG && !xDV.val.str) { *errMsg = "Failed to allocate value: %s"; return(False); } } while (0)
110 #define M_ARRAY_INSERT_FAILURE() M_FAILURE("array element failed to insert: %s")
112 /* Data attached to window during shell command execution with
113 information for controling and communicating with the process */
114 typedef struct {
115 XtIntervalId bannerTimeoutID;
116 XtWorkProcId continueWorkProcID;
117 char bannerIsUp;
118 char closeOnCompletion;
119 Program *program;
120 RestartData *context;
121 Widget dialog;
122 } macroCmdInfo;
124 /* Widgets and global data for Repeat dialog */
125 typedef struct {
126 WindowInfo *forWindow;
127 char *lastCommand;
128 Widget shell, repeatText, lastCmdToggle;
129 Widget inSelToggle, toEndToggle;
130 } repeatDialog;
132 static void cancelLearn(void);
133 static void runMacro(WindowInfo *window, Program *prog);
134 static void finishMacroCmdExecution(WindowInfo *window);
135 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData);
136 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData);
137 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event);
138 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData);
139 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
140 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
141 XEvent *event, String *params, Cardinal *numParams);
142 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
143 XEvent *event, String *params, Cardinal *numParams);
144 static char *actionToString(Widget w, char *actionName, XEvent *event,
145 String *params, Cardinal numParams);
146 static int isMouseAction(const char *action);
147 static int isRedundantAction(const char *action);
148 static int isIgnoredAction(const char *action);
149 static int readCheckMacroString(Widget dialogParent, char *string,
150 WindowInfo *runWindow, const char *errIn, char **errPos);
151 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id);
152 static Boolean continueWorkProc(XtPointer clientData);
153 static int escapeStringChars(char *fromString, char *toString);
154 static int escapedStringLength(char *string);
155 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
156 DataValue *result, char **errMsg);
157 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
158 DataValue *result, char **errMsg);
159 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
160 DataValue *result, char **errMsg);
161 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
162 DataValue *result, char **errMsg);
163 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
164 DataValue *result, char **errMsg);
165 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
166 DataValue *result, char **errMsg);
167 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
168 DataValue *result, char **errMsg);
169 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
170 DataValue *result, char **errMsg);
171 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
172 DataValue *result, char **errMsg);
173 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
174 DataValue *result, char **errMsg);
175 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
176 DataValue *result, char **errMsg);
177 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
178 DataValue *result, char **errMsg);
179 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
180 DataValue *result, char **errMsg);
181 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
182 DataValue *result, char **errMsg);
183 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
184 DataValue *result, char **errMsg);
185 static int writeOrAppendFile(int append, WindowInfo *window,
186 DataValue *argList, int nArgs, DataValue *result, char **errMsg);
187 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
188 DataValue *result, char **errMsg);
189 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
190 DataValue *result, char **errMsg);
191 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
192 DataValue *result, char **errMsg);
193 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
194 DataValue *result, char **errMsg);
195 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
196 DataValue *result, char **errMsg);
197 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
198 DataValue *result, char **errMsg);
199 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
200 DataValue *result, char **errMsg);
201 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
202 DataValue *result, char **errMsg);
203 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
204 DataValue *result, char **errMsg);
205 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
206 DataValue *result, char **errMsg);
207 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
208 DataValue *result, char **errMsg);
209 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
210 DataValue *result, char **errMsg);
211 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
212 DataValue *result, char **errMsg);
213 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
214 DataValue *result, char **errMsg);
215 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
216 DataValue *result, char **errMsg);
217 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData);
218 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData);
219 #ifdef LESSTIF_VERSION
220 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
221 Boolean *cont);
222 #endif /* LESSTIF_VERSION */
223 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
224 DataValue *result, char **errMsg);
225 static void stringDialogBtnCB(Widget w, XtPointer clientData,
226 XtPointer callData);
227 static void stringDialogCloseCB(Widget w, XtPointer clientData,
228 XtPointer callData);
229 #ifdef LESSTIF_VERSION
230 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
231 Boolean *cont);
232 #endif /* LESSTIF_VERSION */
233 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
234 DataValue *result, char **errMsg);
235 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
236 DataValue *result, char **errMsg);
237 /* T Balinski */
238 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
239 DataValue *result, char **errMsg);
240 static void listDialogBtnCB(Widget w, XtPointer clientData,
241 XtPointer callData);
242 static void listDialogCloseCB(Widget w, XtPointer clientData,
243 XtPointer callData);
244 /* T Balinski End */
245 #ifdef LESSTIF_VERSION
246 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
247 Boolean *cont);
248 #endif /* LESSTIF_VERSION */
249 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
250 DataValue *result, char **errMsg);
251 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
252 DataValue *result, char **errMsg);
253 /* DISASBLED for 5.4
254 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
255 int nArgs, DataValue *result, char **errMsg);
257 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
258 DataValue *result, char **errMsg);
259 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
260 DataValue *result, char **errMsg);
261 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
262 DataValue *result, char **errMsg);
263 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
264 DataValue *result, char **errMsg);
265 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
266 DataValue *result, char **errMsg);
267 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
268 DataValue *result, char **errMsg);
269 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
270 DataValue *result, char **errMsg);
271 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
272 DataValue *result, char **errMsg);
273 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
274 DataValue *result, char **errMsg);
275 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
276 DataValue *result, char **errMsg);
277 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
278 DataValue *result, char **errMsg);
279 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
280 DataValue *result, char **errMsg);
281 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
282 DataValue *result, char **errMsg);
283 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
284 DataValue *result, char **errMsg);
285 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
286 DataValue *result, char **errMsg);
287 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
288 DataValue *result, char **errMsg);
289 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
290 DataValue *result, char **errMsg);
291 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
292 DataValue *result, char **errMsg);
293 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
294 DataValue *result, char **errMsg);
295 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
296 DataValue *result, char **errMsg);
297 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
298 DataValue *result, char **errMsg);
299 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
300 DataValue *result, char **errMsg);
301 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
302 DataValue *result, char **errMsg);
303 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
304 DataValue *result, char **errMsg);
305 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
306 DataValue *result, char **errMsg);
307 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
308 DataValue *result, char **errMsg);
309 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
310 DataValue *result, char **errMsg);
311 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
312 DataValue *result, char **errMsg);
313 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
314 DataValue *result, char **errMsg);
315 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
316 DataValue *result, char **errMsg);
317 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
318 DataValue *result, char **errMsg);
319 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
320 DataValue *result, char **errMsg);
321 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
322 DataValue *result, char **errMsg);
323 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
324 DataValue *result, char **errMsg);
325 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
326 DataValue *result, char **errMsg);
327 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
328 DataValue *result, char **errMsg);
329 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
330 DataValue *result, char **errMsg);
331 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
332 DataValue *result, char **errMsg);
333 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
334 DataValue *result, char **errMsg);
335 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
336 DataValue *result, char **errMsg);
337 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
338 DataValue *result, char **errMsg);
339 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
340 DataValue *result, char **errMsg);
341 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
342 DataValue *result, char **errMsg);
343 static int calltipIDMV(WindowInfo *window, DataValue *argList, int nArgs,
344 DataValue *result, char **errMsg);
345 static int readSearchArgs(DataValue *argList, int nArgs, int*searchDirection,
346 int *searchType, int *wrap, char **errMsg);
347 static int wrongNArgsErr(char **errMsg);
348 static int tooFewArgsErr(char **errMsg);
349 static int strCaseCmp(char *str1, char *str2);
350 static int readIntArg(DataValue dv, int *result, char **errMsg);
351 static int readStringArg(DataValue dv, char **result, char *stringStorage,
352 char **errMsg);
353 /* DISABLED FOR 5.4
354 static int backlightStringMV(WindowInfo *window, DataValue *argList,
355 int nArgs, DataValue *result, char **errMsg);
357 static int rangesetListMV(WindowInfo *window, DataValue *argList,
358 int nArgs, DataValue *result, char **errMsg);
359 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
360 DataValue *result, char **errMsg);
361 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
362 DataValue *result, char **errMsg);
363 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
364 DataValue *result, char **errMsg);
365 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
366 DataValue *result, char **errMsg);
367 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
368 DataValue *result, char **errMsg);
369 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
370 DataValue *result, char **errMsg);
371 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
372 DataValue *result, char **errMsg);
373 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
374 int nArgs, DataValue *result, char **errMsg);
375 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
376 int nArgs, DataValue *result, char **errMsg);
377 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
378 int nArgs, DataValue *result, char **errMsg);
380 static int getPatternMS(WindowInfo *window, DataValue *argList,
381 int nArgs, DataValue *result, char **errMsg);
382 static int getStyleMS(WindowInfo *window, DataValue *argList,
383 int nArgs, DataValue *result, char **errMsg);
385 /* Built-in subroutines and variables for the macro language */
386 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS,
387 dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS,
388 setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS,
389 searchStringMS, substringMS, replaceSubstringMS, readFileMS,
390 writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS,
391 replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS,
392 shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS,
393 tolowerMS, listDialogMS, getenvMS,
394 stringCompareMS, splitMS, calltipMS, killCalltipMS,
395 /* DISABLED for 5.4 setBacklightStringMS,*/
396 rangesetCreateMS, rangesetDestroyMS,
397 rangesetAddMS, rangesetSubtractMS, rangesetInvertMS,
398 rangesetInfoMS, rangesetRangeMS, rangesetIncludesPosMS,
399 rangesetSetColorMS, rangesetSetModeMS,
400 getPatternMS, getStyleMS
402 #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs)
403 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print",
404 "dialog", "string_dialog", "replace_range", "replace_selection",
405 "set_cursor_pos", "get_character", "min", "max", "search",
406 "search_string", "substring", "replace_substring", "read_file",
407 "write_file", "append_file", "beep", "get_selection", "valid_number",
408 "replace_in_string", "select", "select_rectangle", "focus_window",
409 "shell_command", "string_to_clipboard", "clipboard_to_string",
410 "toupper", "tolower", "list_dialog", "getenv",
411 "string_compare", "split", "calltip", "kill_calltip",
412 /* DISABLED for 5.4 "set_backlight_string", */
413 "rangeset_create", "rangeset_destroy",
414 "rangeset_add", "rangeset_subtract", "rangeset_invert",
415 "rangeset_info", "rangeset_range", "rangeset_includes",
416 "rangeset_set_color", "rangeset_set_mode",
417 "get_pattern", "get_style"
419 static BuiltInSubr SpecialVars[] = {cursorMV, lineMV, columnMV,
420 fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV,
421 selectionLeftMV, selectionRightMV, wrapMarginMV, tabDistMV,
422 emTabDistMV, useTabsMV, languageModeMV, modifiedMV,
423 statisticsLineMV, incSearchLineMV, showLineNumbersMV,
424 autoIndentMV, wrapTextMV, highlightSyntaxMV,
425 makeBackupCopyMV, incBackupMV, showMatchingMV,
426 overTypeModeMV, readOnlyMV, lockedMV, fileFormatMV,
427 fontNameMV, fontNameItalicMV,
428 fontNameBoldMV, fontNameBoldItalicMV, subscriptSepMV,
429 minFontWidthMV, maxFontWidthMV, topLineMV, numDisplayLinesMV,
430 displayWidthMV, activePaneMV, nPanesMV, emptyArrayMV,
431 serverNameMV, calltipIDMV,
432 /* DISABLED for 5.4 backlightStringMV, */
433 rangesetListMV
435 #define N_SPECIAL_VARS (sizeof SpecialVars/sizeof *SpecialVars)
436 static const char *SpecialVarNames[N_SPECIAL_VARS] = {"$cursor", "$line", "$column",
437 "$file_name", "$file_path", "$text_length", "$selection_start",
438 "$selection_end", "$selection_left", "$selection_right",
439 "$wrap_margin", "$tab_dist", "$em_tab_dist", "$use_tabs",
440 "$language_mode", "$modified",
441 "$statistics_line", "$incremental_search_line", "$show_line_numbers",
442 "$auto_indent", "$wrap_text", "$highlight_syntax",
443 "$make_backup_copy", "$incremental_backup", "$show_matching",
444 "$overtype_mode", "$read_only", "$locked", "$file_format",
445 "$font_name", "$font_name_italic",
446 "$font_name_bold", "$font_name_bold_italic", "$sub_sep",
447 "$min_font_width", "$max_font_width", "$top_line", "$n_display_lines",
448 "$display_width", "$active_pane", "$n_panes", "$empty_array",
449 "$server_name", "$calltip_ID",
450 /* DISABLED for 5.4 "$backlight_string", */
451 "$rangeset_list"
454 /* Global symbols for returning values from built-in functions */
455 #define N_RETURN_GLOBALS 5
456 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS,
457 SHELL_CMD_STATUS, LIST_DIALOG_BUTTON};
458 static const char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button",
459 "$search_end", "$read_status", "$shell_cmd_status",
460 "$list_dialog_button"};
461 static Symbol *ReturnGlobals[N_RETURN_GLOBALS];
463 /* List of actions not useful when learning a macro sequence (also see below) */
464 static char* IgnoredActions[] = {"focusIn", "focusOut"};
466 /* List of actions intended to be attached to mouse buttons, which the user
467 must be warned can't be recorded in a learn/replay sequence */
468 static const char* MouseActions[] = {"grab_focus", "extend_adjust", "extend_start",
469 "extend_end", "secondary_or_drag_adjust", "secondary_adjust",
470 "secondary_or_drag_start", "secondary_start", "move_destination",
471 "move_to", "move_to_or_end_drag", "copy_to", "copy_to_or_end_drag",
472 "exchange", "process_bdrag", "mouse_pan"};
474 /* List of actions to not record because they
475 generate further actions, more suitable for recording */
476 static const char* RedundantActions[] = {"open_dialog", "save_as_dialog",
477 "revert_to_saved_dialog", "include_file_dialog", "load_macro_file_dialog",
478 "load_tags_file_dialog", "find_dialog", "replace_dialog",
479 "goto_line_number_dialog", "mark_dialog", "goto_mark_dialog",
480 "control_code_dialog", "filter_selection_dialog", "execute_command_dialog",
481 "repeat_dialog", "start_incremental_find"};
483 /* The last command executed (used by the Repeat command) */
484 static char *LastCommand = NULL;
486 /* The current macro to execute on Replay command */
487 static char *ReplayMacro = NULL;
489 /* Buffer where macro commands are recorded in Learn mode */
490 static textBuffer *MacroRecordBuf = NULL;
492 /* Action Hook id for recording actions for Learn mode */
493 static XtActionHookId MacroRecordActionHook = 0;
495 /* Window where macro recording is taking place */
496 static WindowInfo *MacroRecordWindow = NULL;
498 /* Arrays for translating escape characters in escapeStringChars */
499 static char ReplaceChars[] = "\\\"ntbrfav";
500 static char EscapeChars[] = "\\\"\n\t\b\r\f\a\v";
503 ** Install built-in macro subroutines and special variables for accessing
504 ** editor information
506 void RegisterMacroSubroutines(void)
508 static DataValue subrPtr = {NO_TAG, {0}}, noValue = {NO_TAG, {0}};
509 unsigned i;
511 /* Install symbols for built-in routines and variables, with pointers
512 to the appropriate c routines to do the work */
513 for (i=0; i<N_MACRO_SUBRS; i++) {
514 subrPtr.val.subr = MacroSubrs[i];
515 InstallSymbol(MacroSubrNames[i], C_FUNCTION_SYM, subrPtr);
517 for (i=0; i<N_SPECIAL_VARS; i++) {
518 subrPtr.val.subr = SpecialVars[i];
519 InstallSymbol(SpecialVarNames[i], PROC_VALUE_SYM, subrPtr);
522 /* Define global variables used for return values, remember their
523 locations so they can be set without a LookupSymbol call */
524 for (i=0; i<N_RETURN_GLOBALS; i++)
525 ReturnGlobals[i] = InstallSymbol(ReturnGlobalNames[i], GLOBAL_SYM,
526 noValue);
529 #define MAX_LEARN_MSG_LEN ((2 * MAX_ACCEL_LEN) + 60)
530 void BeginLearn(WindowInfo *window)
532 WindowInfo *win;
533 XmString s;
534 XmString xmFinish;
535 XmString xmCancel;
536 char *cFinish;
537 char *cCancel;
538 char message[MAX_LEARN_MSG_LEN];
540 /* If we're already in learn mode, return */
541 if (MacroRecordActionHook != 0)
542 return;
544 /* dim the inappropriate menus and items, and undim finish and cancel */
545 for (win=WindowList; win!=NULL; win=win->next)
546 XtSetSensitive(win->learnItem, False);
547 XtSetSensitive(window->finishLearnItem, True);
548 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
549 s=XmStringCreateSimple("Cancel Learn"), NULL);
550 XmStringFree(s);
551 XtSetSensitive(window->cancelMacroItem, True);
553 /* Mark the window where learn mode is happening */
554 MacroRecordWindow = window;
556 /* Allocate a text buffer for accumulating the macro strings */
557 MacroRecordBuf = BufCreate();
559 /* Add the action hook for recording the actions */
560 MacroRecordActionHook =
561 XtAppAddActionHook(XtWidgetToApplicationContext(window->shell),
562 learnActionHook, window);
564 /* Extract accelerator texts from menu PushButtons */
565 XtVaGetValues(window->finishLearnItem, XmNacceleratorText, &xmFinish, NULL);
566 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
568 /* Translate Motif strings to char* */
569 cFinish = GetXmStringText(xmFinish);
570 cCancel = GetXmStringText(xmCancel);
572 /* Free Motif Strings */
573 XmStringFree(xmFinish);
574 XmStringFree(xmCancel);
576 /* Create message */
577 if (cFinish[0] == '\0') {
578 if (cCancel[0] == '\0') {
579 strncpy(message, "Learn Mode -- Use menu to finish or cancel",
580 MAX_LEARN_MSG_LEN);
581 message[MAX_LEARN_MSG_LEN - 1] = '\0';
583 else {
584 sprintf(message,
585 "Learn Mode -- Use menu to finish, press %s to cancel",
586 cCancel);
589 else {
590 if (cCancel[0] == '\0') {
591 sprintf(message,
592 "Learn Mode -- Press %s to finish, use menu to cancel",
593 cFinish);
596 else {
597 sprintf(message,
598 "Learn Mode -- Press %s to finish, %s to cancel",
599 cFinish,
600 cCancel);
604 /* Free C-strings */
605 XtFree(cFinish);
606 XtFree(cCancel);
608 /* Put up the learn-mode banner */
609 SetModeMessage(window, message);
612 void AddLastCommandActionHook(XtAppContext context)
614 XtAppAddActionHook(context, lastActionHook, NULL);
617 void FinishLearn(void)
619 WindowInfo *win;
621 /* If we're not in learn mode, return */
622 if (MacroRecordActionHook == 0)
623 return;
625 /* Remove the action hook */
626 XtRemoveActionHook(MacroRecordActionHook);
627 MacroRecordActionHook = 0;
629 /* Free the old learn/replay sequence */
630 if (ReplayMacro != NULL)
631 XtFree(ReplayMacro);
633 /* Store the finished action for the replay menu item */
634 ReplayMacro = BufGetAll(MacroRecordBuf);
636 /* Free the buffer used to accumulate the macro sequence */
637 BufFree(MacroRecordBuf);
639 /* Undim the menu items dimmed during learn */
640 for (win=WindowList; win!=NULL; win=win->next)
641 XtSetSensitive(win->learnItem, True);
642 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
643 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
645 /* Undim the replay and paste-macro buttons */
646 for (win=WindowList; win!=NULL; win=win->next)
647 XtSetSensitive(win->replayItem, True);
648 DimPasteReplayBtns(True);
650 /* Clear learn-mode banner */
651 ClearModeMessage(MacroRecordWindow);
655 ** Cancel Learn mode, or macro execution (they're bound to the same menu item)
657 void CancelMacroOrLearn(WindowInfo *window)
659 if (MacroRecordActionHook != 0)
660 cancelLearn();
661 else if (window->macroCmdData != NULL)
662 AbortMacroCommand(window);
665 static void cancelLearn(void)
667 WindowInfo *win;
669 /* If we're not in learn mode, return */
670 if (MacroRecordActionHook == 0)
671 return;
673 /* Remove the action hook */
674 XtRemoveActionHook(MacroRecordActionHook);
675 MacroRecordActionHook = 0;
677 /* Free the macro under construction */
678 BufFree(MacroRecordBuf);
680 /* Undim the menu items dimmed during learn */
681 for (win=WindowList; win!=NULL; win=win->next)
682 XtSetSensitive(win->learnItem, True);
683 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
684 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
686 /* Clear learn-mode banner */
687 ClearModeMessage(MacroRecordWindow);
691 ** Execute the learn/replay sequence stored in "window"
693 void Replay(WindowInfo *window)
695 Program *prog;
696 char *errMsg, *stoppedAt;
698 /* Verify that a replay macro exists and it's not empty and that */
699 /* we're not already running a macro */
700 if (ReplayMacro != NULL &&
701 ReplayMacro[0] != 0 &&
702 window->macroCmdData == NULL) {
703 /* Parse the replay macro (it's stored in text form) and compile it into
704 an executable program "prog" */
705 prog = ParseMacro(ReplayMacro, &errMsg, &stoppedAt);
706 if (prog == NULL) {
707 fprintf(stderr,
708 "NEdit internal error, learn/replay macro syntax error: %s\n",
709 errMsg);
710 return;
713 /* run the executable program */
714 runMacro(window, prog);
719 ** Read the initial NEdit macro file if one exists.
721 void ReadMacroInitFile(WindowInfo *window)
723 const char* autoloadName = GetRCFileName(AUTOLOAD_NM);
724 static int initFileLoaded = False;
726 /* GetRCFileName() might return NULL if an error occurs during
727 creation of the preference file directory. */
728 if (autoloadName != NULL && !initFileLoaded)
730 ReadMacroFile(window, autoloadName, False);
731 initFileLoaded = True;
736 ** Read an NEdit macro file. Extends the syntax of the macro parser with
737 ** define keyword, and allows intermixing of defines with immediate actions.
739 int ReadMacroFile(WindowInfo *window, const char *fileName, int warnNotExist)
741 int result;
742 char *fileString;
744 fileString = ReadAnyTextFile(fileName);
745 if (fileString == NULL){
746 if (errno != ENOENT || warnNotExist)
748 DialogF(DF_ERR, window->shell, 1, "Read Macro",
749 "Error reading macro file %s: %s", "dismiss", fileName,
750 #ifdef VMS
751 strerror(errno, vaxc$errno));
752 #else
753 strerror(errno));
754 #endif
756 return False;
759 /* Parse fileString */
760 result = readCheckMacroString(window->shell, fileString, window, fileName,
761 NULL);
762 XtFree(fileString);
763 return result;
767 ** Parse and execute a macro string including macro definitions. Report
768 ** parsing errors in a dialog posted over window->shell.
770 int ReadMacroString(WindowInfo *window, char *string, const char *errIn)
772 return readCheckMacroString(window->shell, string, window, errIn, NULL);
776 ** Check a macro string containing definitions for errors. Returns True
777 ** if macro compiled successfully. Returns False and puts up
778 ** a dialog explaining if macro did not compile successfully.
780 int CheckMacroString(Widget dialogParent, char *string, const char *errIn,
781 char **errPos)
783 return readCheckMacroString(dialogParent, string, NULL, errIn, errPos);
787 ** Parse and optionally execute a macro string including macro definitions.
788 ** Report parsing errors in a dialog posted over dialogParent, using the
789 ** string errIn to identify the entity being parsed (filename, macro string,
790 ** etc.). If runWindow is specified, runs the macro against the window. If
791 ** runWindow is passed as NULL, does parse only. If errPos is non-null,
792 ** returns a pointer to the error location in the string.
794 static int readCheckMacroString(Widget dialogParent, char *string,
795 WindowInfo *runWindow, const char *errIn, char **errPos)
797 char *stoppedAt, *inPtr, *namePtr, *errMsg;
798 char subrName[MAX_SYM_LEN];
799 Program *prog;
800 Symbol *sym;
801 DataValue subrPtr;
803 inPtr = string;
804 while (*inPtr != '\0') {
806 /* skip over white space and comments */
807 while (*inPtr==' ' || *inPtr=='\t' || *inPtr=='\n'|| *inPtr=='#') {
808 if (*inPtr == '#')
809 while (*inPtr != '\n' && *inPtr != '\0') inPtr++;
810 else
811 inPtr++;
813 if (*inPtr == '\0')
814 break;
816 /* look for define keyword, and compile and store defined routines */
817 if (!strncmp(inPtr, "define", 6) && (inPtr[6]==' ' || inPtr[6]=='\t')) {
818 inPtr += 6;
819 inPtr += strspn(inPtr, " \t\n");
820 namePtr = subrName;
821 while (isalnum((unsigned char)*inPtr) || *inPtr == '_')
822 *namePtr++ = *inPtr++;
823 *namePtr = '\0';
824 inPtr += strspn(inPtr, " \t\n");
825 if (*inPtr != '{') {
826 if (errPos != NULL) *errPos = stoppedAt;
827 return ParseError(dialogParent, string, inPtr,
828 errIn, "expected '{'");
830 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
831 if (prog == NULL) {
832 if (errPos != NULL) *errPos = stoppedAt;
833 return ParseError(dialogParent, string, stoppedAt,
834 errIn, errMsg);
836 if (runWindow != NULL) {
837 sym = LookupSymbol(subrName);
838 if (sym == NULL) {
839 subrPtr.val.prog = prog;
840 subrPtr.tag = NO_TAG;
841 sym = InstallSymbol(subrName, MACRO_FUNCTION_SYM, subrPtr);
842 } else {
843 if (sym->type == MACRO_FUNCTION_SYM)
844 FreeProgram(sym->value.val.prog);
845 else
846 sym->type = MACRO_FUNCTION_SYM;
847 sym->value.val.prog = prog;
850 inPtr = stoppedAt;
852 /* Parse and execute immediate (outside of any define) macro commands
853 and WAIT for them to finish executing before proceeding. Note that
854 the code below is not perfect. If you interleave code blocks with
855 definitions in a file which is loaded from another macro file, it
856 will probably run the code blocks in reverse order! */
857 } else {
858 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
859 if (prog == NULL) {
860 if (errPos != NULL) *errPos = stoppedAt;
861 return ParseError(dialogParent, string, stoppedAt,
862 errIn, errMsg);
864 if (runWindow != NULL) {
865 XEvent nextEvent;
866 if (runWindow->macroCmdData == NULL) {
867 runMacro(runWindow, prog);
868 while (runWindow->macroCmdData != NULL) {
869 XtAppNextEvent(XtWidgetToApplicationContext(
870 runWindow->shell), &nextEvent);
871 ServerDispatchEvent(&nextEvent);
873 } else
874 RunMacroAsSubrCall(prog);
876 inPtr = stoppedAt;
879 return True;
883 ** Run a pre-compiled macro, changing the interface state to reflect that
884 ** a macro is running, and handling preemption, resumption, and cancellation.
885 ** frees prog when macro execution is complete;
887 static void runMacro(WindowInfo *window, Program *prog)
889 DataValue result;
890 char *errMsg;
891 int stat;
892 macroCmdInfo *cmdData;
893 XmString s;
895 /* If a macro is already running, just call the program as a subroutine,
896 instead of starting a new one, so we don't have to keep a separate
897 context, and the macros will serialize themselves automatically */
898 if (window->macroCmdData != NULL) {
899 RunMacroAsSubrCall(prog);
900 return;
903 /* put up a watch cursor over the waiting window */
904 BeginWait(window->shell);
906 /* enable the cancel menu item */
907 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
908 s=XmStringCreateSimple("Cancel Macro"), NULL);
909 XmStringFree(s);
910 XtSetSensitive(window->cancelMacroItem, True);
912 /* Create a data structure for passing macro execution information around
913 amongst the callback routines which will process i/o and completion */
914 cmdData = (macroCmdInfo *)XtMalloc(sizeof(macroCmdInfo));
915 window->macroCmdData = cmdData;
916 cmdData->bannerIsUp = False;
917 cmdData->closeOnCompletion = False;
918 cmdData->program = prog;
919 cmdData->context = NULL;
920 cmdData->continueWorkProcID = 0;
921 cmdData->dialog = NULL;
923 /* Set up timer proc for putting up banner when macro takes too long */
924 cmdData->bannerTimeoutID = XtAppAddTimeOut(
925 XtWidgetToApplicationContext(window->shell), BANNER_WAIT_TIME,
926 bannerTimeoutProc, window);
928 /* Begin macro execution */
929 stat = ExecuteMacro(window, prog, 0, NULL, &result, &cmdData->context,
930 &errMsg);
932 if (stat == MACRO_ERROR)
934 finishMacroCmdExecution(window);
935 DialogF(DF_ERR, window->shell, 1, "Macro Error",
936 "Error executing macro: %s", "Dismiss", errMsg);
937 return;
940 if (stat == MACRO_DONE) {
941 finishMacroCmdExecution(window);
942 return;
944 if (stat == MACRO_TIME_LIMIT) {
945 ResumeMacroExecution(window);
946 return;
948 /* (stat == MACRO_PREEMPT) Macro was preempted */
952 ** Continue with macro execution after preemption. Called by the routines
953 ** whose actions cause preemption when they have completed their lengthy tasks.
954 ** Re-establishes macro execution work proc. Window must be the window in
955 ** which the macro is executing (the window to which macroCmdData is attached),
956 ** and not the window to which operations are focused.
958 void ResumeMacroExecution(WindowInfo *window)
960 macroCmdInfo *cmdData = (macroCmdInfo *)window->macroCmdData;
962 if (cmdData != NULL)
963 cmdData->continueWorkProcID = XtAppAddWorkProc(
964 XtWidgetToApplicationContext(window->shell),
965 continueWorkProc, window);
969 ** Cancel the macro command in progress (user cancellation via GUI)
971 void AbortMacroCommand(WindowInfo *window)
973 if (window->macroCmdData == NULL)
974 return;
976 /* If there's both a macro and a shell command executing, the shell command
977 must have been called from the macro. When called from a macro, shell
978 commands don't put up cancellation controls of their own, but rely
979 instead on the macro cancellation mechanism (here) */
980 #ifndef VMS
981 if (window->shellCmdData != NULL)
982 AbortShellCommand(window);
983 #endif
985 /* Free the continuation */
986 FreeRestartData(((macroCmdInfo *)window->macroCmdData)->context);
988 /* Kill the macro command */
989 finishMacroCmdExecution(window);
993 ** Call this before closing a window, to clean up macro references to the
994 ** window, stop any macro which might be running from it, free associated
995 ** memory, and check that a macro is not attempting to close the window from
996 ** which it is run. If this is being called from a macro, and the window
997 ** this routine is examining is the window from which the macro was run, this
998 ** routine will return False, and the caller must NOT CLOSE THE WINDOW.
999 ** Instead, empty it and make it Untitled, and let the macro completion
1000 ** process close the window when the macro is finished executing.
1002 int MacroWindowCloseActions(WindowInfo *window)
1004 macroCmdInfo *mcd, *cmdData = window->macroCmdData;
1005 WindowInfo *w;
1007 if (MacroRecordActionHook != 0 && MacroRecordWindow == window) {
1008 FinishLearn();
1011 /* If no macro is executing in the window, allow the close, but check
1012 if macros executing in other windows have it as focus. If so, set
1013 their focus back to the window from which they were originally run */
1014 if (cmdData == NULL) {
1015 for (w=WindowList; w!=NULL; w=w->next) {
1016 mcd = (macroCmdInfo *)w->macroCmdData;
1017 if (w == MacroRunWindow() && MacroFocusWindow() == window)
1018 SetMacroFocusWindow(MacroRunWindow());
1019 else if (mcd != NULL && mcd->context->focusWindow == window)
1020 mcd->context->focusWindow = mcd->context->runWindow;
1022 return True;
1025 /* If the macro currently running (and therefore calling us, because
1026 execution must otherwise return to the main loop to execute any
1027 commands), is running in this window, tell the caller not to close,
1028 and schedule window close on completion of macro */
1029 if (window == MacroRunWindow()) {
1030 cmdData->closeOnCompletion = True;
1031 return False;
1034 /* Free the continuation */
1035 FreeRestartData(cmdData->context);
1037 /* Kill the macro command */
1038 finishMacroCmdExecution(window);
1039 return True;
1043 ** Clean up after the execution of a macro command: free memory, and restore
1044 ** the user interface state.
1046 static void finishMacroCmdExecution(WindowInfo *window)
1048 macroCmdInfo *cmdData = window->macroCmdData;
1049 int closeOnCompletion = cmdData->closeOnCompletion;
1050 XmString s;
1051 XClientMessageEvent event;
1053 /* Cancel pending timeout and work proc */
1054 if (cmdData->bannerTimeoutID != 0)
1055 XtRemoveTimeOut(cmdData->bannerTimeoutID);
1056 if (cmdData->continueWorkProcID != 0)
1057 XtRemoveWorkProc(cmdData->continueWorkProcID);
1059 /* Clean up waiting-for-macro-command-to-complete mode */
1060 EndWait(window->shell);
1061 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
1062 s=XmStringCreateSimple("Cancel Learn"), NULL);
1063 XmStringFree(s);
1064 XtSetSensitive(window->cancelMacroItem, False);
1065 if (cmdData->bannerIsUp)
1066 ClearModeMessage(window);
1068 /* If a dialog was up, get rid of it */
1069 if (cmdData->dialog != NULL)
1070 XtDestroyWidget(XtParent(cmdData->dialog));
1072 /* Free execution information */
1073 FreeProgram(cmdData->program);
1074 XtFree((char *)cmdData);
1075 window->macroCmdData = NULL;
1077 /* If macro closed its own window, window was made empty and untitled,
1078 but close was deferred until completion. This is completion, so if
1079 the window is still empty, do the close */
1080 if (closeOnCompletion && !window->filenameSet && !window->fileChanged) {
1081 CloseWindow(window);
1082 window = NULL;
1085 /* If no other macros are executing, do garbage collection */
1086 SafeGC();
1088 /* In processing the .neditmacro file (and possibly elsewhere), there
1089 is an event loop which waits for macro completion. Send an event
1090 to wake up that loop, otherwise execution will stall until the user
1091 does something to the window. */
1092 if (!closeOnCompletion) {
1093 event.format = 8;
1094 event.type = ClientMessage;
1095 XSendEvent(XtDisplay(window->shell), XtWindow(window->shell), False,
1096 NoEventMask, (XEvent *)&event);
1101 ** Do garbage collection of strings if there are no macros currently
1102 ** executing. NEdit's macro language GC strategy is to call this routine
1103 ** whenever a macro completes. If other macros are still running (preempted
1104 ** or waiting for a shell command or dialog), this does nothing and therefore
1105 ** defers GC to the completion of the last macro out.
1107 void SafeGC(void)
1109 WindowInfo *win;
1111 for (win=WindowList; win!=NULL; win=win->next)
1112 if (win->macroCmdData != NULL || InSmartIndentMacros(win))
1113 return;
1114 GarbageCollectStrings();
1118 ** Executes macro string "macro" using the lastFocus pane in "window".
1119 ** Reports errors via a dialog posted over "window", integrating the name
1120 ** "errInName" into the message to help identify the source of the error.
1122 void DoMacro(WindowInfo *window, const char *macro, const char *errInName)
1124 Program *prog;
1125 char *errMsg, *stoppedAt, *tMacro;
1126 int macroLen;
1128 /* Add a terminating newline (which command line users are likely to omit
1129 since they are typically invoking a single routine) */
1130 macroLen = strlen(macro);
1131 tMacro = XtMalloc(strlen(macro)+2);
1132 strncpy(tMacro, macro, macroLen);
1133 tMacro[macroLen] = '\n';
1134 tMacro[macroLen+1] = '\0';
1136 /* Parse the macro and report errors if it fails */
1137 prog = ParseMacro(tMacro, &errMsg, &stoppedAt);
1138 if (prog == NULL) {
1139 ParseError(window->shell, tMacro, stoppedAt, errInName, errMsg);
1140 XtFree(tMacro);
1141 return;
1143 XtFree(tMacro);
1145 /* run the executable program (prog is freed upon completion) */
1146 runMacro(window, prog);
1150 ** Get the current Learn/Replay macro in text form. Returned string is a
1151 ** pointer to the stored macro and should not be freed by the caller (and
1152 ** will cease to exist when the next replay macro is installed)
1154 char *GetReplayMacro(void)
1156 return ReplayMacro;
1160 ** Present the user a dialog for "Repeat" command
1162 void RepeatDialog(WindowInfo *window)
1164 Widget form, selBox, radioBox, timesForm;
1165 repeatDialog *rd;
1166 Arg selBoxArgs[1];
1167 char *lastCmdLabel, *parenChar;
1168 XmString s1;
1169 int cmdNameLen;
1171 if (LastCommand == NULL)
1173 DialogF(DF_WARN, window->shell, 1, "Repeat Macro",
1174 "No previous commands or learn/\nreplay sequences to repeat",
1175 "Dismiss");
1176 return;
1179 /* Remeber the last command, since the user is allowed to work in the
1180 window while the dialog is up */
1181 rd = (repeatDialog *)XtMalloc(sizeof(repeatDialog));
1182 rd->lastCommand = XtNewString(LastCommand);
1184 /* make a label for the Last command item of the dialog, which includes
1185 the last executed action name */
1186 parenChar = strchr(LastCommand, '(');
1187 if (parenChar == NULL)
1188 return;
1189 cmdNameLen = parenChar-LastCommand;
1190 lastCmdLabel = XtMalloc(16 + cmdNameLen);
1191 strcpy(lastCmdLabel, "Last Command (");
1192 strncpy(&lastCmdLabel[14], LastCommand, cmdNameLen);
1193 strcpy(&lastCmdLabel[14 + cmdNameLen], ")");
1195 XtSetArg(selBoxArgs[0], XmNautoUnmanage, False);
1196 selBox = CreatePromptDialog(window->shell, "repeat", selBoxArgs, 1);
1197 rd->shell = XtParent(selBox);
1198 XtAddCallback(rd->shell, XmNdestroyCallback, repeatDestroyCB, rd);
1199 XtAddCallback(selBox, XmNokCallback, repeatOKCB, rd);
1200 XtAddCallback(selBox, XmNapplyCallback, repeatApplyCB, rd);
1201 XtAddCallback(selBox, XmNcancelCallback, repeatCancelCB, rd);
1202 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_TEXT));
1203 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_SELECTION_LABEL));
1204 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_HELP_BUTTON));
1205 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_APPLY_BUTTON));
1206 XtVaSetValues(XtParent(selBox), XmNtitle, "Repeat Macro", NULL);
1207 AddMotifCloseCallback(XtParent(selBox), repeatCancelCB, rd);
1209 form = XtVaCreateManagedWidget("form", xmFormWidgetClass, selBox, NULL);
1211 radioBox = XtVaCreateManagedWidget("cmdSrc", xmRowColumnWidgetClass, form,
1212 XmNradioBehavior, True,
1213 XmNorientation, XmHORIZONTAL,
1214 XmNpacking, XmPACK_TIGHT,
1215 XmNtopAttachment, XmATTACH_FORM,
1216 XmNleftAttachment, XmATTACH_FORM, NULL);
1217 rd->lastCmdToggle = XtVaCreateManagedWidget("lastCmdToggle",
1218 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1219 XmNlabelString, s1=XmStringCreateSimple(lastCmdLabel),
1220 XmNmnemonic, 'C', NULL);
1221 XmStringFree(s1);
1222 XtFree(lastCmdLabel);
1223 XtVaCreateManagedWidget("learnReplayToggle",
1224 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1225 XmNlabelString,
1226 s1=XmStringCreateSimple("Learn/Replay"),
1227 XmNmnemonic, 'L',
1228 XmNsensitive, ReplayMacro != NULL, NULL);
1229 XmStringFree(s1);
1231 timesForm = XtVaCreateManagedWidget("form", xmFormWidgetClass, form,
1232 XmNtopAttachment, XmATTACH_WIDGET,
1233 XmNtopWidget, radioBox,
1234 XmNtopOffset, 10,
1235 XmNleftAttachment, XmATTACH_FORM, NULL);
1236 radioBox = XtVaCreateManagedWidget("method", xmRowColumnWidgetClass,
1237 timesForm,
1238 XmNradioBehavior, True,
1239 XmNorientation, XmHORIZONTAL,
1240 XmNpacking, XmPACK_TIGHT,
1241 XmNtopAttachment, XmATTACH_FORM,
1242 XmNbottomAttachment, XmATTACH_FORM,
1243 XmNleftAttachment, XmATTACH_FORM, NULL);
1244 rd->inSelToggle = XtVaCreateManagedWidget("inSelToggle",
1245 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1246 XmNlabelString, s1=XmStringCreateSimple("In Selection"),
1247 XmNmnemonic, 'I', NULL);
1248 XmStringFree(s1);
1249 rd->toEndToggle = XtVaCreateManagedWidget("toEndToggle",
1250 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1251 XmNlabelString, s1=XmStringCreateSimple("To End"),
1252 XmNmnemonic, 'T', NULL);
1253 XmStringFree(s1);
1254 XtVaCreateManagedWidget("nTimesToggle",
1255 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1256 XmNlabelString, s1=XmStringCreateSimple("N Times"),
1257 XmNmnemonic, 'N',
1258 XmNset, True, NULL);
1259 XmStringFree(s1);
1260 rd->repeatText = XtVaCreateManagedWidget("repeatText", xmTextWidgetClass,
1261 timesForm,
1262 XmNcolumns, 5,
1263 XmNtopAttachment, XmATTACH_FORM,
1264 XmNbottomAttachment, XmATTACH_FORM,
1265 XmNleftAttachment, XmATTACH_WIDGET,
1266 XmNleftWidget, radioBox, NULL);
1267 RemapDeleteKey(rd->repeatText);
1269 /* Handle mnemonic selection of buttons and focus to dialog */
1270 AddDialogMnemonicHandler(form, FALSE);
1272 /* Set initial focus */
1273 #if XmVersion >= 1002
1274 XtVaSetValues(form, XmNinitialFocus, timesForm, NULL);
1275 XtVaSetValues(timesForm, XmNinitialFocus, rd->repeatText, NULL);
1276 #endif
1278 /* put up dialog */
1279 rd->forWindow = window;
1280 ManageDialogCenteredOnPointer(selBox);
1283 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData)
1285 repeatDialog *rd = (repeatDialog *)clientData;
1287 if (doRepeatDialogAction(rd, ((XmAnyCallbackStruct *)callData)->event))
1288 XtDestroyWidget(rd->shell);
1291 /* Note that the apply button is not managed in the repeat dialog. The dialog
1292 itself is capable of non-modal operation, but to be complete, it needs
1293 to dynamically update last command, dimming of learn/replay, possibly a
1294 stop button for the macro, and possibly in-selection with selection */
1295 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData)
1297 doRepeatDialogAction((repeatDialog *)clientData,
1298 ((XmAnyCallbackStruct *)callData)->event);
1301 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event)
1303 int nTimes;
1304 char nTimesStr[TYPE_INT_STR_SIZE(int)];
1305 char *params[2];
1307 /* Find out from the dialog how to repeat the command */
1308 if (XmToggleButtonGetState(rd->inSelToggle))
1310 if (!rd->forWindow->buffer->primary.selected)
1312 DialogF(DF_WARN, rd->shell, 1, "Repeat Macro",
1313 "No selection in window to repeat within", "Dismiss");
1314 XmProcessTraversal(rd->inSelToggle, XmTRAVERSE_CURRENT);
1315 return False;
1317 params[0] = "in_selection";
1318 } else if (XmToggleButtonGetState(rd->toEndToggle))
1320 params[0] = "to_end";
1321 } else
1323 if (GetIntTextWarn(rd->repeatText, &nTimes, "number of times", True)
1324 != TEXT_READ_OK)
1326 XmProcessTraversal(rd->repeatText, XmTRAVERSE_CURRENT);
1327 return False;
1329 sprintf(nTimesStr, "%d", nTimes);
1330 params[0] = nTimesStr;
1333 /* Figure out which command user wants to repeat */
1334 if (XmToggleButtonGetState(rd->lastCmdToggle))
1335 params[1] = XtNewString(rd->lastCommand);
1336 else {
1337 if (ReplayMacro == NULL)
1338 return False;
1339 params[1] = XtNewString(ReplayMacro);
1342 /* call the action routine repeat_macro to do the work */
1343 XtCallActionProc(rd->forWindow->lastFocus, "repeat_macro", event, params,2);
1344 XtFree(params[1]);
1345 return True;
1348 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData)
1350 repeatDialog *rd = (repeatDialog *)clientData;
1352 XtDestroyWidget(rd->shell);
1355 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1357 repeatDialog *rd = (repeatDialog *)clientData;
1359 XtFree(rd->lastCommand);
1360 XtFree((char *)rd);
1364 ** Dispatches a macro to which repeats macro command in "command", either
1365 ** an integer number of times ("how" == positive integer), or within a
1366 ** selected range ("how" == REPEAT_IN_SEL), or to the end of the window
1367 ** ("how == REPEAT_TO_END).
1369 ** Note that as with most macro routines, this returns BEFORE the macro is
1370 ** finished executing
1372 void RepeatMacro(WindowInfo *window, const char *command, int how)
1374 Program *prog;
1375 char *errMsg, *stoppedAt, *loopMacro, *loopedCmd;
1377 if (command == NULL)
1378 return;
1380 /* Wrap a for loop and counter/tests around the command */
1381 if (how == REPEAT_TO_END)
1382 loopMacro = "lastCursor=-1\nstartPos=$cursor\n\
1383 while($cursor>=startPos&&$cursor!=lastCursor){\nlastCursor=$cursor\n%s\n}\n";
1384 else if (how == REPEAT_IN_SEL)
1385 loopMacro = "selStart = $selection_start\nif (selStart == -1)\nreturn\n\
1386 selEnd = $selection_end\nset_cursor_pos(selStart)\nselect(0,0)\n\
1387 boundText = get_range(selEnd, selEnd+10)\n\
1388 while($cursor >= selStart && $cursor < selEnd && \\\n\
1389 get_range(selEnd, selEnd+10) == boundText) {\n\
1390 startLength = $text_length\n%s\n\
1391 selEnd += $text_length - startLength\n}\n";
1392 else
1393 loopMacro = "for(i=0;i<%d;i++){\n%s\n}\n";
1394 loopedCmd = XtMalloc(strlen(command) + strlen(loopMacro) + 25);
1395 if (how == REPEAT_TO_END || how == REPEAT_IN_SEL)
1396 sprintf(loopedCmd, loopMacro, command);
1397 else
1398 sprintf(loopedCmd, loopMacro, how, command);
1400 /* Parse the resulting macro into an executable program "prog" */
1401 prog = ParseMacro(loopedCmd, &errMsg, &stoppedAt);
1402 if (prog == NULL) {
1403 fprintf(stderr, "NEdit internal error, repeat macro syntax wrong: %s\n",
1404 errMsg);
1405 return;
1407 XtFree(loopedCmd);
1409 /* run the executable program */
1410 runMacro(window, prog);
1414 ** Macro recording action hook for Learn/Replay, added temporarily during
1415 ** learn.
1417 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
1418 XEvent *event, String *params, Cardinal *numParams)
1420 WindowInfo *window;
1421 int i;
1422 char *actionString;
1424 /* Select only actions in text panes in the window for which this
1425 action hook is recording macros (from clientData). */
1426 for (window=WindowList; window!=NULL; window=window->next) {
1427 if (window->textArea == w)
1428 break;
1429 for (i=0; i<window->nPanes; i++) {
1430 if (window->textPanes[i] == w)
1431 break;
1433 if (i < window->nPanes)
1434 break;
1436 if (window == NULL || window != (WindowInfo *)clientData)
1437 return;
1439 /* beep on un-recordable operations which require a mouse position, to
1440 remind the user that the action was not recorded */
1441 if (isMouseAction(actionName)) {
1442 XBell(XtDisplay(w), 0);
1443 return;
1446 /* Record the action and its parameters */
1447 actionString = actionToString(w, actionName, event, params, *numParams);
1448 if (actionString != NULL) {
1449 BufInsert(MacroRecordBuf, MacroRecordBuf->length, actionString);
1450 XtFree(actionString);
1455 ** Permanent action hook for remembering last action for possible replay
1457 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
1458 XEvent *event, String *params, Cardinal *numParams)
1460 WindowInfo *window;
1461 int i;
1462 char *actionString;
1464 /* Find the window to which this action belongs */
1465 for (window=WindowList; window!=NULL; window=window->next) {
1466 if (window->textArea == w)
1467 break;
1468 for (i=0; i<window->nPanes; i++) {
1469 if (window->textPanes[i] == w)
1470 break;
1472 if (i < window->nPanes)
1473 break;
1475 if (window == NULL)
1476 return;
1478 /* The last action is recorded for the benefit of repeating the last
1479 action. Don't record repeat_macro and wipe out the real action */
1480 if (!strcmp(actionName, "repeat_macro"))
1481 return;
1483 /* Record the action and its parameters */
1484 actionString = actionToString(w, actionName, event, params, *numParams);
1485 if (actionString != NULL) {
1486 if (LastCommand != NULL)
1487 XtFree(LastCommand);
1488 LastCommand = actionString;
1493 ** Create a macro string to represent an invocation of an action routine.
1494 ** Returns NULL for non-operational or un-recordable actions.
1496 static char *actionToString(Widget w, char *actionName, XEvent *event,
1497 String *params, Cardinal numParams)
1499 char chars[20], *charList[1], *outStr, *outPtr;
1500 KeySym keysym;
1501 int i, nChars, nParams, length, nameLength;
1502 #ifndef NO_XMIM
1503 int status;
1504 #endif
1506 if (isIgnoredAction(actionName) || isRedundantAction(actionName) ||
1507 isMouseAction(actionName))
1508 return NULL;
1510 /* Convert self_insert actions, to insert_string */
1511 if (!strcmp(actionName, "self_insert") ||
1512 !strcmp(actionName, "self-insert")) {
1513 actionName = "insert_string";
1514 #ifdef NO_XMIM
1515 nChars = XLookupString((XKeyEvent *)event, chars, 19, &keysym, NULL);
1516 if (nChars == 0)
1517 return NULL;
1518 #else
1520 nChars = XmImMbLookupString(w, (XKeyEvent *)event,
1521 chars, 19, &keysym, &status);
1522 if (nChars == 0 || status == XLookupNone ||
1523 status == XLookupKeySym || status == XBufferOverflow)
1524 return NULL;
1525 #endif
1526 chars[nChars] = '\0';
1527 charList[0] = chars;
1528 params = charList;
1529 nParams = 1;
1530 } else
1531 nParams = numParams;
1533 /* Figure out the length of string required */
1534 nameLength = strlen(actionName);
1535 length = nameLength + 3;
1536 for (i=0; i<nParams; i++)
1537 length += escapedStringLength(params[i]) + 4;
1539 /* Allocate the string and copy the information to it */
1540 outPtr = outStr = XtMalloc(length + 1);
1541 strcpy(outPtr, actionName);
1542 outPtr += nameLength;
1543 *outPtr++ = '(';
1544 for (i=0; i<nParams; i++) {
1545 *outPtr++ = '\"';
1546 outPtr += escapeStringChars(params[i], outPtr);
1547 *outPtr++ = '\"'; *outPtr++ = ','; *outPtr++ = ' ';
1549 if (nParams != 0)
1550 outPtr -= 2;
1551 *outPtr++ = ')'; *outPtr++ = '\n'; *outPtr++ = '\0';
1552 return outStr;
1555 static int isMouseAction(const char *action)
1557 int i;
1559 for (i=0; i<(int)XtNumber(MouseActions); i++)
1560 if (!strcmp(action, MouseActions[i]))
1561 return True;
1562 return False;
1565 static int isRedundantAction(const char *action)
1567 int i;
1569 for (i=0; i<(int)XtNumber(RedundantActions); i++)
1570 if (!strcmp(action, RedundantActions[i]))
1571 return True;
1572 return False;
1575 static int isIgnoredAction(const char *action)
1577 int i;
1579 for (i=0; i<(int)XtNumber(IgnoredActions); i++)
1580 if (!strcmp(action, IgnoredActions[i]))
1581 return True;
1582 return False;
1586 ** Timer proc for putting up the "Macro Command in Progress" banner if
1587 ** the process is taking too long.
1589 #define MAX_TIMEOUT_MSG_LEN (MAX_ACCEL_LEN + 60)
1590 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id)
1592 WindowInfo *window = (WindowInfo *)clientData;
1593 macroCmdInfo *cmdData = window->macroCmdData;
1594 XmString xmCancel;
1595 char *cCancel;
1596 char message[MAX_TIMEOUT_MSG_LEN];
1598 cmdData->bannerIsUp = True;
1600 /* Extract accelerator text from menu PushButtons */
1601 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
1603 /* Translate Motif string to char* */
1604 cCancel = GetXmStringText(xmCancel);
1606 /* Free Motif String */
1607 XmStringFree(xmCancel);
1609 /* Create message */
1610 if (cCancel[0] == '\0') {
1611 strncpy(message, "Macro Command in Progress", MAX_TIMEOUT_MSG_LEN);
1612 message[MAX_TIMEOUT_MSG_LEN - 1] = '\0';
1614 else {
1615 sprintf(message,
1616 "Macro Command in Progress -- Press %s to Cancel",
1617 cCancel);
1620 /* Free C-string */
1621 XtFree(cCancel);
1623 SetModeMessage(window, message);
1624 cmdData->bannerTimeoutID = 0;
1628 ** Work proc for continuing execution of a preempted macro.
1630 ** Xt WorkProcs are designed to run first-in first-out, which makes them
1631 ** very bad at sharing time between competing tasks. For this reason, it's
1632 ** usually bad to use work procs anywhere where their execution is likely to
1633 ** overlap. Using a work proc instead of a timer proc (which I usually
1634 ** prefer) here means macros will probably share time badly, but we're more
1635 ** interested in making the macros cancelable, and in continuing other work
1636 ** than having users run a bunch of them at once together.
1638 static Boolean continueWorkProc(XtPointer clientData)
1640 WindowInfo *window = (WindowInfo *)clientData;
1641 macroCmdInfo *cmdData = window->macroCmdData;
1642 char *errMsg;
1643 int stat;
1644 DataValue result;
1646 stat = ContinueMacro(cmdData->context, &result, &errMsg);
1647 if (stat == MACRO_ERROR)
1649 finishMacroCmdExecution(window);
1650 DialogF(DF_ERR, window->shell, 1, "Macro Error",
1651 "Error executing macro: %s", "Dismiss", errMsg);
1652 return True;
1653 } else if (stat == MACRO_DONE)
1655 finishMacroCmdExecution(window);
1656 return True;
1657 } else if (stat == MACRO_PREEMPT)
1659 cmdData->continueWorkProcID = 0;
1660 return True;
1663 /* Macro exceeded time slice, re-schedule it */
1664 if (stat != MACRO_TIME_LIMIT)
1665 return True; /* shouldn't happen */
1666 return False;
1670 ** Copy fromString to toString replacing special characters in strings, such
1671 ** that they can be read back by the macro parser's string reader. i.e. double
1672 ** quotes are replaced by \", backslashes are replaced with \\, C-std control
1673 ** characters like \n are replaced with their backslash counterparts. This
1674 ** routine should be kept reasonably in sync with yylex in parse.y. Companion
1675 ** routine escapedStringLength predicts the length needed to write the string
1676 ** when it is expanded with the additional characters. Returns the number
1677 ** of characters to which the string expanded.
1679 static int escapeStringChars(char *fromString, char *toString)
1681 char *e, *c, *outPtr = toString;
1683 /* substitute escape sequences */
1684 for (c=fromString; *c!='\0'; c++) {
1685 for (e=EscapeChars; *e!='\0'; e++) {
1686 if (*c == *e) {
1687 *outPtr++ = '\\';
1688 *outPtr++ = ReplaceChars[e-EscapeChars];
1689 break;
1692 if (*e == '\0')
1693 *outPtr++ = *c;
1695 *outPtr = '\0';
1696 return outPtr - toString;
1700 ** Predict the length of a string needed to hold a copy of "string" with
1701 ** special characters replaced with escape sequences by escapeStringChars.
1703 static int escapedStringLength(char *string)
1705 char *c, *e;
1706 int length = 0;
1708 /* calculate length and allocate returned string */
1709 for (c=string; *c!='\0'; c++) {
1710 for (e=EscapeChars; *e!='\0'; e++) {
1711 if (*c == *e) {
1712 length++;
1713 break;
1716 length++;
1718 return length;
1722 ** Built-in macro subroutine for getting the length of a string
1724 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
1725 DataValue *result, char **errMsg)
1727 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
1729 if (nArgs != 1)
1730 return wrongNArgsErr(errMsg);
1731 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1732 return False;
1733 result->tag = INT_TAG;
1734 result->val.n = strlen(string);
1735 return True;
1739 ** Built-in macro subroutines for min and max
1741 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
1742 DataValue *result, char **errMsg)
1744 int minVal, value, i;
1746 if (nArgs == 1)
1747 return tooFewArgsErr(errMsg);
1748 if (!readIntArg(argList[0], &minVal, errMsg))
1749 return False;
1750 for (i=0; i<nArgs; i++) {
1751 if (!readIntArg(argList[i], &value, errMsg))
1752 return False;
1753 minVal = value < minVal ? value : minVal;
1755 result->tag = INT_TAG;
1756 result->val.n = minVal;
1757 return True;
1759 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
1760 DataValue *result, char **errMsg)
1762 int maxVal, value, i;
1764 if (nArgs == 1)
1765 return tooFewArgsErr(errMsg);
1766 if (!readIntArg(argList[0], &maxVal, errMsg))
1767 return False;
1768 for (i=0; i<nArgs; i++) {
1769 if (!readIntArg(argList[i], &value, errMsg))
1770 return False;
1771 maxVal = value > maxVal ? value : maxVal;
1773 result->tag = INT_TAG;
1774 result->val.n = maxVal;
1775 return True;
1778 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
1779 DataValue *result, char **errMsg)
1781 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1782 WindowInfo *w;
1783 char fullname[MAXPATHLEN];
1785 /* Read the argument representing the window to focus to, and translate
1786 it into a pointer to a real WindowInfo */
1787 if (nArgs != 1)
1788 return wrongNArgsErr(errMsg);
1789 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1790 return False;
1791 else if (!strcmp(string, "last"))
1792 w = WindowList;
1793 else if (!strcmp(string, "next"))
1794 w = window->next;
1795 else {
1796 for (w=WindowList; w != NULL; w = w->next) {
1797 sprintf(fullname, "%s%s", w->path, w->filename);
1798 if (!strcmp(string, fullname))
1799 break;
1803 /* If no matching window was found, return empty string and do nothing */
1804 if (w == NULL) {
1805 result->tag = STRING_TAG;
1806 result->val.str = PERM_ALLOC_STR("");
1807 return True;
1810 /* Change the focused window to the requested one */
1811 SetMacroFocusWindow(w);
1813 /* Return the name of the window */
1814 result->tag = STRING_TAG;
1815 result->val.str = AllocString(strlen(w->path)+strlen(w->filename)+1);
1816 sprintf(result->val.str, "%s%s", w->path, w->filename);
1817 return True;
1821 ** Built-in macro subroutine for getting text from the current window's text
1822 ** buffer
1824 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1825 DataValue *result, char **errMsg)
1827 int from, to;
1828 textBuffer *buf = window->buffer;
1829 char *rangeText;
1831 /* Validate arguments and convert to int */
1832 if (nArgs != 2)
1833 return wrongNArgsErr(errMsg);
1834 if (!readIntArg(argList[0], &from, errMsg))
1835 return False;
1836 if (!readIntArg(argList[1], &to, errMsg))
1837 return False;
1838 if (from < 0) from = 0;
1839 if (from > buf->length) from = buf->length;
1840 if (to < 0) to = 0;
1841 if (to > buf->length) to = buf->length;
1842 if (from > to) {int temp = from; from = to; to = temp;}
1844 /* Copy text from buffer (this extra copy could be avoided if textBuf.c
1845 provided a routine for writing into a pre-allocated string) */
1846 result->tag = STRING_TAG;
1847 result->val.str = AllocString(to - from + 1);
1848 rangeText = BufGetRange(buf, from, to);
1849 BufUnsubstituteNullChars(rangeText, buf);
1850 strcpy(result->val.str, rangeText);
1851 XtFree(rangeText);
1852 return True;
1856 ** Built-in macro subroutine for getting a single character at the position
1857 ** given, from the current window
1859 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
1860 DataValue *result, char **errMsg)
1862 int pos;
1863 textBuffer *buf = window->buffer;
1865 /* Validate argument and convert it to int */
1866 if (nArgs != 1)
1867 return wrongNArgsErr(errMsg);
1868 if (!readIntArg(argList[0], &pos, errMsg))
1869 return False;
1870 if (pos < 0) pos = 0;
1871 if (pos > buf->length) pos = buf->length;
1873 /* Return the character in a pre-allocated string) */
1874 result->tag = STRING_TAG;
1875 result->val.str = AllocString(2);
1876 result->val.str[0] = BufGetCharacter(buf, pos);
1877 result->val.str[1] = '\0';
1878 BufUnsubstituteNullChars(result->val.str, buf);
1879 return True;
1883 ** Built-in macro subroutine for replacing text in the current window's text
1884 ** buffer
1886 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1887 DataValue *result, char **errMsg)
1889 int from, to;
1890 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1891 textBuffer *buf = window->buffer;
1893 /* Validate arguments and convert to int */
1894 if (nArgs != 3)
1895 return wrongNArgsErr(errMsg);
1896 if (!readIntArg(argList[0], &from, errMsg))
1897 return False;
1898 if (!readIntArg(argList[1], &to, errMsg))
1899 return False;
1900 if (!readStringArg(argList[2], &string, stringStorage, errMsg))
1901 return False;
1902 if (from < 0) from = 0;
1903 if (from > buf->length) from = buf->length;
1904 if (to < 0) to = 0;
1905 if (to > buf->length) to = buf->length;
1906 if (from > to) {int temp = from; from = to; to = temp;}
1908 /* Don't allow modifications if the window is read-only */
1909 if (IS_ANY_LOCKED(window->lockReasons)) {
1910 XBell(XtDisplay(window->shell), 0);
1911 result->tag = NO_TAG;
1912 return True;
1915 /* There are no null characters in the string (because macro strings
1916 still have null termination), but if the string contains the
1917 character used by the buffer for null substitution, it could
1918 theoretically become a null. In the highly unlikely event that
1919 all of the possible substitution characters in the buffer are used
1920 up, stop the macro and tell the user of the failure */
1921 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
1922 *errMsg = "Too much binary data in file";
1923 return False;
1926 /* Do the replace */
1927 BufReplace(buf, from, to, string);
1928 result->tag = NO_TAG;
1929 return True;
1933 ** Built-in macro subroutine for replacing the primary-selection selected
1934 ** text in the current window's text buffer
1936 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
1937 DataValue *result, char **errMsg)
1939 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1941 /* Validate argument and convert to string */
1942 if (nArgs != 1)
1943 return wrongNArgsErr(errMsg);
1944 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1945 return False;
1947 /* Don't allow modifications if the window is read-only */
1948 if (IS_ANY_LOCKED(window->lockReasons)) {
1949 XBell(XtDisplay(window->shell), 0);
1950 result->tag = NO_TAG;
1951 return True;
1954 /* There are no null characters in the string (because macro strings
1955 still have null termination), but if the string contains the
1956 character used by the buffer for null substitution, it could
1957 theoretically become a null. In the highly unlikely event that
1958 all of the possible substitution characters in the buffer are used
1959 up, stop the macro and tell the user of the failure */
1960 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
1961 *errMsg = "Too much binary data in file";
1962 return False;
1965 /* Do the replace */
1966 BufReplaceSelected(window->buffer, string);
1967 result->tag = NO_TAG;
1968 return True;
1972 ** Built-in macro subroutine for getting the text currently selected by
1973 ** the primary selection in the current window's text buffer, or in any
1974 ** part of screen if "any" argument is given
1976 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
1977 DataValue *result, char **errMsg)
1979 char *selText;
1981 /* Read argument list to check for "any" keyword, and get the appropriate
1982 selection */
1983 if (nArgs != 0 && nArgs != 1)
1984 return wrongNArgsErr(errMsg);
1985 if (nArgs == 1) {
1986 if (argList[0].tag != STRING_TAG || strcmp(argList[0].val.str, "any")) {
1987 *errMsg = "Unrecognized argument to %s";
1988 return False;
1990 selText = GetAnySelection(window);
1991 if (selText == NULL)
1992 selText = XtNewString("");
1993 } else {
1994 selText = BufGetSelectionText(window->buffer);
1995 BufUnsubstituteNullChars(selText, window->buffer);
1998 /* Return the text as an allocated string */
1999 result->tag = STRING_TAG;
2000 result->val.str = AllocString(strlen(selText) + 1);
2001 strcpy(result->val.str, selText);
2002 XtFree(selText);
2003 return True;
2007 ** Built-in macro subroutine for determining if implicit conversion of
2008 ** a string to number will succeed or fail
2010 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
2011 DataValue *result, char **errMsg)
2013 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
2015 if (nArgs != 1) {
2016 return wrongNArgsErr(errMsg);
2018 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
2019 return False;
2022 result->tag = INT_TAG;
2023 result->val.n = StringToNum(string, NULL);
2025 return True;
2029 ** Built-in macro subroutine for replacing a substring within another string
2031 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
2032 DataValue *result, char **errMsg)
2034 int from, to, length, replaceLen, outLen;
2035 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *replStr;
2037 /* Validate arguments and convert to int */
2038 if (nArgs != 4)
2039 return wrongNArgsErr(errMsg);
2040 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2041 return False;
2042 if (!readIntArg(argList[1], &from, errMsg))
2043 return False;
2044 if (!readIntArg(argList[2], &to, errMsg))
2045 return False;
2046 if (!readStringArg(argList[3], &replStr, stringStorage[1], errMsg))
2047 return False;
2048 length = strlen(string);
2049 if (from < 0) from = 0;
2050 if (from > length) from = length;
2051 if (to < 0) to = 0;
2052 if (to > length) to = length;
2053 if (from > to) {int temp = from; from = to; to = temp;}
2055 /* Allocate a new string and do the replacement */
2056 replaceLen = strlen(replStr);
2057 outLen = length - (to - from) + replaceLen;
2058 result->tag = STRING_TAG;
2059 result->val.str = AllocString(outLen+1);
2060 strncpy(result->val.str, string, from);
2061 strncpy(&result->val.str[from], replStr, replaceLen);
2062 strncpy(&result->val.str[from + replaceLen], &string[to], length - to);
2063 result->val.str[outLen] = '\0';
2064 return True;
2068 ** Built-in macro subroutine for getting a substring of a string
2070 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
2071 DataValue *result, char **errMsg)
2073 int from, to, length;
2074 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2076 /* Validate arguments and convert to int */
2077 if (nArgs != 3)
2078 return wrongNArgsErr(errMsg);
2079 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2080 return False;
2081 if (!readIntArg(argList[1], &from, errMsg))
2082 return False;
2083 if (!readIntArg(argList[2], &to, errMsg))
2084 return False;
2085 length = strlen(string);
2086 if (from < 0) from = 0;
2087 if (from > length) from = length;
2088 if (to < 0) to = 0;
2089 if (to > length) to = length;
2090 if (from > to) {int temp = from; from = to; to = temp;}
2092 /* Allocate a new string and copy the sub-string into it */
2093 result->tag = STRING_TAG;
2094 result->val.str = AllocString(to - from + 1);
2095 strncpy(result->val.str, &string[from], to - from);
2096 result->val.str[to - from] = '\0';
2097 return True;
2100 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
2101 DataValue *result, char **errMsg)
2103 int i, length;
2104 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2106 /* Validate arguments and convert to int */
2107 if (nArgs != 1)
2108 return wrongNArgsErr(errMsg);
2109 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2110 return False;
2111 length = strlen(string);
2113 /* Allocate a new string and copy an uppercased version of the string it */
2114 result->tag = STRING_TAG;
2115 result->val.str = AllocString(length + 1);
2116 for (i=0; i<length; i++)
2117 result->val.str[i] = toupper((unsigned char)string[i]);
2118 result->val.str[length] = '\0';
2119 return True;
2122 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
2123 DataValue *result, char **errMsg)
2125 int i, length;
2126 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2128 /* Validate arguments and convert to int */
2129 if (nArgs != 1)
2130 return wrongNArgsErr(errMsg);
2131 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2132 return False;
2133 length = strlen(string);
2135 /* Allocate a new string and copy an lowercased version of the string it */
2136 result->tag = STRING_TAG;
2137 result->val.str = AllocString(length + 1);
2138 for (i=0; i<length; i++)
2139 result->val.str[i] = tolower((unsigned char)string[i]);
2140 result->val.str[length] = '\0';
2141 return True;
2144 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
2145 DataValue *result, char **errMsg)
2147 long itemID = 0;
2148 XmString s;
2149 int stat;
2150 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2152 /* Get the string argument */
2153 if (nArgs != 1)
2154 return wrongNArgsErr(errMsg);
2155 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2156 return False;
2158 /* Use the XmClipboard routines to copy the text to the clipboard.
2159 If errors occur, just give up. */
2160 result->tag = NO_TAG;
2161 stat = XmClipboardStartCopy(TheDisplay, XtWindow(window->textArea),
2162 s=XmStringCreateSimple("NEdit"), XtLastTimestampProcessed(TheDisplay),
2163 window->textArea, NULL, &itemID);
2164 XmStringFree(s);
2165 if (stat != ClipboardSuccess)
2166 return True;
2167 if (XmClipboardCopy(TheDisplay, XtWindow(window->textArea), itemID, "STRING",
2168 string, strlen(string), 0, NULL) != ClipboardSuccess)
2169 return True;
2170 XmClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2171 return True;
2174 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2175 DataValue *result, char **errMsg)
2177 unsigned long length, retLength;
2178 long id = 0;
2180 /* Should have no arguments */
2181 if (nArgs != 0)
2182 return wrongNArgsErr(errMsg);
2184 /* Ask if there's a string in the clipboard, and get its length */
2185 if (XmClipboardInquireLength(TheDisplay, XtWindow(window->shell), "STRING",
2186 &length) != ClipboardSuccess) {
2187 result->tag = STRING_TAG;
2188 result->val.str = PERM_ALLOC_STR("");
2189 return True;
2192 /* Allocate a new string to hold the data */
2193 result->tag = STRING_TAG;
2194 result->val.str = AllocString((int)length + 1);
2196 /* Copy the clipboard contents to the string */
2197 if (XmClipboardRetrieve(TheDisplay, XtWindow(window->shell), "STRING",
2198 result->val.str, length, &retLength, &id) != ClipboardSuccess)
2199 retLength = 0;
2200 result->val.str[retLength] = '\0';
2202 return True;
2207 ** Built-in macro subroutine for reading the contents of a text file into
2208 ** a string. On success, returns 1 in $readStatus, and the contents of the
2209 ** file as a string in the subroutine return value. On failure, returns
2210 ** the empty string "" and an 0 $readStatus.
2212 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2213 DataValue *result, char **errMsg)
2215 char stringStorage[TYPE_INT_STR_SIZE(int)], *name;
2216 struct stat statbuf;
2217 FILE *fp;
2218 int readLen;
2220 /* Validate arguments and convert to int */
2221 if (nArgs != 1)
2222 return wrongNArgsErr(errMsg);
2223 if (!readStringArg(argList[0], &name, stringStorage, errMsg))
2224 return False;
2226 /* Read the whole file into an allocated string */
2227 if ((fp = fopen(name, "r")) == NULL)
2228 goto errorNoClose;
2229 if (fstat(fileno(fp), &statbuf) != 0)
2230 goto error;
2231 result->tag = STRING_TAG;
2232 result->val.str = AllocString(statbuf.st_size+1);
2233 readLen = fread(result->val.str, sizeof(char), statbuf.st_size+1, fp);
2234 if (ferror(fp))
2235 goto error;
2236 if(!feof(fp)){
2237 /* Couldn't trust file size. Use slower but more general method */
2238 int chunkSize = 1024;
2239 char *buffer;
2241 buffer = XtMalloc(readLen * sizeof(char));
2242 memcpy(buffer, result->val.str, readLen * sizeof(char));
2243 while (!feof(fp)){
2244 buffer = XtRealloc(buffer, (readLen+chunkSize)*sizeof(char));
2245 readLen += fread(&buffer[readLen], sizeof(char), chunkSize, fp);
2246 if (ferror(fp)){
2247 XtFree(buffer);
2248 goto error;
2251 result->val.str = AllocString(readLen + 1);
2252 memcpy(result->val.str, buffer, readLen * sizeof(char));
2253 XtFree(buffer);
2255 result->val.str[readLen] = '\0';
2256 fclose(fp);
2258 /* Return the results */
2259 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2260 ReturnGlobals[READ_STATUS]->value.val.n = True;
2261 return True;
2263 error:
2264 fclose(fp);
2266 errorNoClose:
2267 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2268 ReturnGlobals[READ_STATUS]->value.val.n = False;
2269 result->tag = STRING_TAG;
2270 result->val.str = PERM_ALLOC_STR("");
2271 return True;
2275 ** Built-in macro subroutines for writing or appending a string (parameter $1)
2276 ** to a file named in parameter $2. Returns 1 on successful write, or 0 if
2277 ** unsuccessful.
2279 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2280 DataValue *result, char **errMsg)
2282 return writeOrAppendFile(False, window, argList, nArgs, result, errMsg);
2285 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2286 DataValue *result, char **errMsg)
2288 return writeOrAppendFile(True, window, argList, nArgs, result, errMsg);
2291 static int writeOrAppendFile(int append, WindowInfo *window,
2292 DataValue *argList, int nArgs, DataValue *result, char **errMsg)
2294 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *name, *string;
2295 FILE *fp;
2297 /* Validate argument */
2298 if (nArgs != 2)
2299 return wrongNArgsErr(errMsg);
2300 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2301 return False;
2302 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg))
2303 return False;
2305 /* open the file */
2306 if ((fp = fopen(name, append ? "a" : "w")) == NULL) {
2307 result->tag = INT_TAG;
2308 result->val.n = False;
2309 return True;
2312 /* write the string to the file */
2313 fwrite(string, sizeof(char), strlen(string), fp);
2314 if (ferror(fp)) {
2315 fclose(fp);
2316 result->tag = INT_TAG;
2317 result->val.n = False;
2318 return True;
2320 fclose(fp);
2322 /* return the status */
2323 result->tag = INT_TAG;
2324 result->val.n = True;
2325 return True;
2329 ** Built-in macro subroutine for searching silently in a window without
2330 ** dialogs, beeps, or changes to the selection. Arguments are: $1: string to
2331 ** search for, $2: starting position. Optional arguments may include the
2332 ** strings: "wrap" to make the search wrap around the beginning or end of the
2333 ** string, "backward" or "forward" to change the search direction ("forward" is
2334 ** the default), "literal", "case" or "regex" to change the search type
2335 ** (default is "literal").
2337 ** Returns the starting position of the match, or -1 if nothing matched.
2338 ** also returns the ending position of the match in $searchEndPos
2340 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
2341 DataValue *result, char **errMsg)
2343 DataValue newArgList[9];
2344 int retVal;
2346 /* Use the search string routine, by adding the buffer contents as
2347 the string argument */
2348 if (nArgs > 8)
2349 return wrongNArgsErr(errMsg);
2350 newArgList[0].tag = STRING_TAG;
2351 newArgList[0].val.str = BufGetAll(window->buffer);
2352 memcpy(&newArgList[1], argList, nArgs * sizeof(DataValue));
2353 retVal = searchStringMS(window, newArgList, nArgs+1, result, errMsg);
2354 XtFree(newArgList[0].val.str);
2355 return retVal;
2359 ** Built-in macro subroutine for searching a string. Arguments are $1:
2360 ** string to search in, $2: string to search for, $3: starting position.
2361 ** Optional arguments may include the strings: "wrap" to make the search
2362 ** wrap around the beginning or end of the string, "backward" or "forward"
2363 ** to change the search direction ("forward" is the default), "literal",
2364 ** "case" or "regex" to change the search type (default is "literal").
2366 ** Returns the starting position of the match, or -1 if nothing matched.
2367 ** also returns the ending position of the match in $searchEndPos
2369 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2370 DataValue *result, char **errMsg)
2372 int beginPos, wrap, direction, found = False, foundStart, foundEnd, type;
2373 int skipSearch = False, len;
2374 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *searchStr;
2376 /* Validate arguments and convert to proper types */
2377 if (nArgs < 3)
2378 return tooFewArgsErr(errMsg);
2379 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2380 return False;
2381 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2382 return False;
2383 if (!readIntArg(argList[2], &beginPos, errMsg))
2384 return False;
2385 if (!readSearchArgs(&argList[3], nArgs-3, &direction, &type, &wrap, errMsg))
2386 return False;
2388 /* This is potentially costly, but it is necessary to protect us from
2389 illegal memory accesses if beginPos is too large (or negative). Note:
2390 matching at position "len" is allowed: a $ matches end of string. */
2391 len = beginPos ? strlen(string) : 0; /* avoid strlen if beginPos == 0 */
2392 if (beginPos > len) {
2393 if (direction == SEARCH_FORWARD) {
2394 if (wrap) {
2395 beginPos = 0; /* Wrap immediately */
2396 } else {
2397 found = False;
2398 skipSearch = True;
2400 } else {
2401 beginPos = len;
2403 } else if (beginPos < 0) {
2404 if (direction == SEARCH_BACKWARD) {
2405 if (wrap) {
2406 beginPos = len; /* Wrap immediately */
2407 } else {
2408 found = False;
2409 skipSearch = True;
2411 } else {
2412 beginPos = 0;
2416 if (!skipSearch)
2417 found = SearchString(string, searchStr, direction, type, wrap, beginPos,
2418 &foundStart, &foundEnd, NULL, NULL, GetWindowDelimiters(window));
2420 /* Return the results */
2421 ReturnGlobals[SEARCH_END]->value.tag = INT_TAG;
2422 ReturnGlobals[SEARCH_END]->value.val.n = found ? foundEnd : 0;
2423 result->tag = INT_TAG;
2424 result->val.n = found ? foundStart : -1;
2425 return True;
2429 ** Built-in macro subroutine for replacing all occurences of a search string in
2430 ** a string with a replacement string. Arguments are $1: string to search in,
2431 ** $2: string to search for, $3: replacement string. Also takes an optional
2432 ** search type: one of "literal", "case" or "regex" (default is "literal"), and
2433 ** an optional "copy" argument.
2435 ** Returns a new string with all of the replacements done. If no replacements
2436 ** were performed and "copy" was specified, returns a copy of the original
2437 ** string. Otherwise returns an empty string ("").
2439 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2440 DataValue *result, char **errMsg)
2442 char stringStorage[3][TYPE_INT_STR_SIZE(int)], *string, *searchStr, *replaceStr;
2443 char *argStr, *replacedStr;
2444 int searchType = SEARCH_LITERAL, copyStart, copyEnd;
2445 int replacedLen, replaceEnd, force=False, i;
2447 /* Validate arguments and convert to proper types */
2448 if (nArgs < 3 || nArgs > 5)
2449 return wrongNArgsErr(errMsg);
2450 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2451 return False;
2452 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2453 return False;
2454 if (!readStringArg(argList[2], &replaceStr, stringStorage[2], errMsg))
2455 return False;
2456 for (i = 3; i < nArgs; i++) {
2457 /* Read the optional search type and force arguments */
2458 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
2459 return False;
2460 if (!StringToSearchType(argStr, &searchType)) {
2461 /* It's not a search type. is it "copy"? */
2462 if (!strcmp(argStr, "copy")) {
2463 force = True;
2464 } else {
2465 *errMsg = "unrecognized argument to %s";
2466 return False;
2471 /* Do the replace */
2472 replacedStr = ReplaceAllInString(string, searchStr, replaceStr, searchType,
2473 &copyStart, &copyEnd, &replacedLen, GetWindowDelimiters(window));
2475 /* Return the results */
2476 result->tag = STRING_TAG;
2477 if (replacedStr == NULL) {
2478 if (force) {
2479 /* Just copy the original DataValue */
2480 result->val.str = argList[0].val.str;
2481 } else {
2482 result->val.str = PERM_ALLOC_STR("");
2484 } else {
2485 replaceEnd = copyStart + replacedLen;
2486 result->val.str = AllocString(replaceEnd + strlen(&string[copyEnd])+1);
2487 strncpy(result->val.str, string, copyStart);
2488 strcpy(&result->val.str[copyStart], replacedStr);
2489 strcpy(&result->val.str[replaceEnd], &string[copyEnd]);
2490 XtFree(replacedStr);
2492 return True;
2495 static int readSearchArgs(DataValue *argList, int nArgs, int *searchDirection,
2496 int *searchType, int *wrap, char **errMsg)
2498 int i;
2499 char *argStr, stringStorage[9][TYPE_INT_STR_SIZE(int)];
2501 *wrap = False;
2502 *searchDirection = SEARCH_FORWARD;
2503 *searchType = SEARCH_LITERAL;
2504 for (i=0; i<nArgs; i++) {
2505 if (!readStringArg(argList[i], &argStr, stringStorage[i], errMsg))
2506 return False;
2507 else if (!strcmp(argStr, "wrap"))
2508 *wrap = True;
2509 else if (!strcmp(argStr, "nowrap"))
2510 *wrap = False;
2511 else if (!strcmp(argStr, "backward"))
2512 *searchDirection = SEARCH_BACKWARD;
2513 else if (!strcmp(argStr, "forward"))
2514 *searchDirection = SEARCH_FORWARD;
2515 else if (!StringToSearchType(argStr, searchType)) {
2516 *errMsg = "Unrecognized argument to %s";
2517 return False;
2520 return True;
2523 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
2524 DataValue *result, char **errMsg)
2526 int pos;
2528 /* Get argument and convert to int */
2529 if (nArgs != 1)
2530 return wrongNArgsErr(errMsg);
2531 if (!readIntArg(argList[0], &pos, errMsg))
2532 return False;
2534 /* Set the position */
2535 TextSetCursorPos(window->lastFocus, pos);
2536 result->tag = NO_TAG;
2537 return True;
2540 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
2541 DataValue *result, char **errMsg)
2543 int start, end, startTmp;
2545 /* Get arguments and convert to int */
2546 if (nArgs != 2)
2547 return wrongNArgsErr(errMsg);
2548 if (!readIntArg(argList[0], &start, errMsg))
2549 return False;
2550 if (!readIntArg(argList[1], &end, errMsg))
2551 return False;
2553 /* Verify integrity of arguments */
2554 if (start > end) {
2555 startTmp = start;
2556 start = end;
2557 end = startTmp;
2559 if (start < 0) start = 0;
2560 if (start > window->buffer->length) start = window->buffer->length;
2561 if (end < 0) end = 0;
2562 if (end > window->buffer->length) end = window->buffer->length;
2564 /* Make the selection */
2565 BufSelect(window->buffer, start, end);
2566 result->tag = NO_TAG;
2567 return True;
2570 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
2571 DataValue *result, char **errMsg)
2573 int start, end, left, right;
2575 /* Get arguments and convert to int */
2576 if (nArgs != 4)
2577 return wrongNArgsErr(errMsg);
2578 if (!readIntArg(argList[0], &start, errMsg))
2579 return False;
2580 if (!readIntArg(argList[1], &end, errMsg))
2581 return False;
2582 if (!readIntArg(argList[2], &left, errMsg))
2583 return False;
2584 if (!readIntArg(argList[3], &right, errMsg))
2585 return False;
2587 /* Make the selection */
2588 BufRectSelect(window->buffer, start, end, left, right);
2589 result->tag = NO_TAG;
2590 return True;
2594 ** Macro subroutine to ring the bell
2596 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
2597 DataValue *result, char **errMsg)
2599 if (nArgs != 0)
2600 return wrongNArgsErr(errMsg);
2601 XBell(XtDisplay(window->shell), 0);
2602 result->tag = NO_TAG;
2603 return True;
2606 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
2607 DataValue *result, char **errMsg)
2609 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2610 int i;
2612 if (nArgs == 0)
2613 return tooFewArgsErr(errMsg);
2614 for (i=0; i<nArgs; i++) {
2615 if (!readStringArg(argList[i], &string, stringStorage, errMsg))
2616 return False;
2617 printf("%s%s", string, i==nArgs-1 ? "" : " ");
2619 fflush( stdout );
2620 result->tag = NO_TAG;
2621 return True;
2625 ** Built-in macro subroutine for getting the value of an environment variable
2627 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
2628 DataValue *result, char **errMsg)
2630 char *value;
2632 /* Get name of variable to get */
2633 if (nArgs != 1)
2634 return wrongNArgsErr(errMsg);
2635 if (argList[0].tag != STRING_TAG) {
2636 *errMsg = "argument to %s must be a string";
2637 return False;
2639 value = getenv(argList[0].val.str);
2640 if (value == NULL)
2641 value = "";
2643 /* Return the text as an allocated string */
2644 result->tag = STRING_TAG;
2645 result->val.str = AllocString(strlen(value) + 1);
2646 strcpy(result->val.str, value);
2647 return True;
2650 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
2651 DataValue *result, char **errMsg)
2653 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *cmdString, *inputString;
2655 if (nArgs != 2)
2656 return wrongNArgsErr(errMsg);
2657 if (!readStringArg(argList[0], &cmdString, stringStorage[0], errMsg))
2658 return False;
2659 if (!readStringArg(argList[1], &inputString, stringStorage[1], errMsg))
2660 return False;
2662 /* Shell command execution requires that the macro be suspended, so
2663 this subroutine can't be run if macro execution can't be interrupted */
2664 if (MacroRunWindow()->macroCmdData == NULL) {
2665 *errMsg = "%s can't be called from non-suspendable context";
2666 return False;
2669 #ifdef VMS
2670 *errMsg = "Shell commands not supported under VMS";
2671 return False;
2672 #else
2673 ShellCmdToMacroString(window, cmdString, inputString);
2674 result->tag = INT_TAG;
2675 result->val.n = 0;
2676 return True;
2677 #endif /*VMS*/
2681 ** Method used by ShellCmdToMacroString (called by shellCmdMS), for returning
2682 ** macro string and exit status after the execution of a shell command is
2683 ** complete. (Sorry about the poor modularity here, it's just not worth
2684 ** teaching other modules about macro return globals, since other than this,
2685 ** they're not used outside of macro.c)
2687 void ReturnShellCommandOutput(WindowInfo *window, const char *outText, int status)
2689 DataValue retVal;
2690 macroCmdInfo *cmdData = window->macroCmdData;
2692 if (cmdData == NULL)
2693 return;
2694 retVal.tag = STRING_TAG;
2695 retVal.val.str = AllocString(strlen(outText)+1);
2696 strcpy(retVal.val.str, outText);
2697 ModifyReturnedValue(cmdData->context, retVal);
2698 ReturnGlobals[SHELL_CMD_STATUS]->value.tag = INT_TAG;
2699 ReturnGlobals[SHELL_CMD_STATUS]->value.val.n = status;
2702 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2703 DataValue *result, char **errMsg)
2705 macroCmdInfo *cmdData;
2706 char stringStorage[9][TYPE_INT_STR_SIZE(int)], *btnLabels[8], *message;
2707 Arg al[20];
2708 int ac;
2709 Widget dialog, btn;
2710 int i, nBtns;
2711 XmString s1, s2;
2713 /* Ignore the focused window passed as the function argument and put
2714 the dialog up over the window which is executing the macro */
2715 window = MacroRunWindow();
2716 cmdData = window->macroCmdData;
2718 /* Dialogs require macro to be suspended and interleaved with other macros.
2719 This subroutine can't be run if macro execution can't be interrupted */
2720 if (!cmdData) {
2721 *errMsg = "%s can't be called from non-suspendable context";
2722 return False;
2725 /* Read and check the arguments. The first being the dialog message,
2726 and the rest being the button labels */
2727 if (nArgs == 0) {
2728 *errMsg = "%s subroutine called with no arguments";
2729 return False;
2731 if (!readStringArg(argList[0], &message, stringStorage[0], errMsg))
2732 return False;
2733 for (i=1; i<nArgs; i++)
2734 if (!readStringArg(argList[i], &btnLabels[i-1], stringStorage[i],
2735 errMsg))
2736 return False;
2737 if (nArgs == 1) {
2738 btnLabels[0] = "Dismiss";
2739 nBtns = 1;
2740 } else
2741 nBtns = nArgs - 1;
2743 /* Create the message box dialog widget and its dialog shell parent */
2744 ac = 0;
2745 XtSetArg(al[ac], XmNtitle, " "); ac++;
2746 XtSetArg(al[ac], XmNmessageString, s1=MKSTRING(message)); ac++;
2747 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0]));
2748 ac++;
2749 dialog = CreateMessageDialog(window->shell, "macroDialog", al, ac);
2750 XmStringFree(s1);
2751 XmStringFree(s2);
2752 AddMotifCloseCallback(XtParent(dialog), dialogCloseCB, window);
2753 XtAddCallback(dialog, XmNokCallback, dialogBtnCB, window);
2754 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2755 XmNuserData, (XtPointer)1, NULL);
2756 cmdData->dialog = dialog;
2758 /* Unmanage default buttons, except for "OK" */
2759 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2760 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2762 /* Make callback for the unmanaged cancel button (which can
2763 still get executed via the esc key) activate close box action */
2764 XtAddCallback(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2765 XmNactivateCallback, dialogCloseCB, window);
2767 /* Add user specified buttons (1st is already done) */
2768 for (i=1; i<nBtns; i++) {
2769 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2770 XmNlabelString, s1=XmStringCreateSimple(btnLabels[i]),
2771 XmNuserData, (XtPointer)(i+1), NULL);
2772 XtAddCallback(btn, XmNactivateCallback, dialogBtnCB, window);
2773 XmStringFree(s1);
2776 #ifdef LESSTIF_VERSION
2777 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2778 the escape key for closing the dialog (probably because the
2779 cancel button is not managed). */
2780 XtAddEventHandler(dialog, KeyPressMask, False, dialogEscCB,
2781 (XtPointer)window);
2782 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
2783 True, GrabModeAsync, GrabModeAsync);
2784 #endif /* LESSTIF_VERSION */
2786 /* Put up the dialog */
2787 ManageDialogCenteredOnPointer(dialog);
2789 /* Stop macro execution until the dialog is complete */
2790 PreemptMacro();
2792 /* Return placeholder result. Value will be changed by button callback */
2793 result->tag = INT_TAG;
2794 result->val.n = 0;
2795 return True;
2798 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData)
2800 WindowInfo *window = (WindowInfo *)clientData;
2801 macroCmdInfo *cmdData = window->macroCmdData;
2802 XtPointer userData;
2803 DataValue retVal;
2805 /* Return the index of the button which was pressed (stored in the userData
2806 field of the button widget). The 1st button, being a gadget, is not
2807 returned in w. */
2808 if (cmdData == NULL)
2809 return; /* shouldn't happen */
2810 if (XtClass(w) == xmPushButtonWidgetClass) {
2811 XtVaGetValues(w, XmNuserData, &userData, NULL);
2812 retVal.val.n = (int)userData;
2813 } else
2814 retVal.val.n = 1;
2815 retVal.tag = INT_TAG;
2816 ModifyReturnedValue(cmdData->context, retVal);
2818 /* Pop down the dialog */
2819 XtDestroyWidget(XtParent(cmdData->dialog));
2820 cmdData->dialog = NULL;
2822 /* Continue preempted macro execution */
2823 ResumeMacroExecution(window);
2826 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData)
2828 WindowInfo *window = (WindowInfo *)clientData;
2829 macroCmdInfo *cmdData = window->macroCmdData;
2830 DataValue retVal;
2832 /* Return 0 to show that the dialog was closed via the window close box */
2833 retVal.val.n = 0;
2834 retVal.tag = INT_TAG;
2835 ModifyReturnedValue(cmdData->context, retVal);
2837 /* Pop down the dialog */
2838 XtDestroyWidget(XtParent(cmdData->dialog));
2839 cmdData->dialog = NULL;
2841 /* Continue preempted macro execution */
2842 ResumeMacroExecution(window);
2845 #ifdef LESSTIF_VERSION
2846 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
2847 Boolean *cont)
2849 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
2850 return;
2851 if (clientData != NULL) {
2852 dialogCloseCB(w, (WindowInfo *)clientData, NULL);
2854 *cont = False;
2856 #endif /* LESSTIF_VERSION */
2858 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2859 DataValue *result, char **errMsg)
2861 macroCmdInfo *cmdData;
2862 char stringStorage[9][TYPE_INT_STR_SIZE(int)], *btnLabels[8], *message;
2863 Widget dialog, btn;
2864 int i, nBtns;
2865 XmString s1, s2;
2866 Arg al[20];
2867 int ac;
2869 /* Ignore the focused window passed as the function argument and put
2870 the dialog up over the window which is executing the macro */
2871 window = MacroRunWindow();
2872 cmdData = window->macroCmdData;
2874 /* Dialogs require macro to be suspended and interleaved with other macros.
2875 This subroutine can't be run if macro execution can't be interrupted */
2876 if (!cmdData) {
2877 *errMsg = "%s can't be called from non-suspendable context";
2878 return False;
2881 /* Read and check the arguments. The first being the dialog message,
2882 and the rest being the button labels */
2883 if (nArgs == 0) {
2884 *errMsg = "%s subroutine called with no arguments";
2885 return False;
2887 if (!readStringArg(argList[0], &message, stringStorage[0], errMsg))
2888 return False;
2889 for (i=1; i<nArgs; i++)
2890 if (!readStringArg(argList[i], &btnLabels[i-1], stringStorage[i],
2891 errMsg))
2892 return False;
2893 if (nArgs == 1) {
2894 btnLabels[0] = "Dismiss";
2895 nBtns = 1;
2896 } else
2897 nBtns = nArgs - 1;
2899 /* Create the selection box dialog widget and its dialog shell parent */
2900 ac = 0;
2901 XtSetArg(al[ac], XmNtitle, " "); ac++;
2902 XtSetArg(al[ac], XmNselectionLabelString, s1=MKSTRING(message)); ac++;
2903 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0]));
2904 ac++;
2905 dialog = CreatePromptDialog(window->shell, "macroStringDialog", al, ac);
2906 XmStringFree(s1);
2907 XmStringFree(s2);
2908 AddMotifCloseCallback(XtParent(dialog), stringDialogCloseCB, window);
2909 XtAddCallback(dialog, XmNokCallback, stringDialogBtnCB, window);
2910 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2911 XmNuserData, (XtPointer)1, NULL);
2912 cmdData->dialog = dialog;
2914 /* Unmanage unneded widgets */
2915 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2916 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2918 /* Make callback for the unmanaged cancel button (which can
2919 still get executed via the esc key) activate close box action */
2920 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2921 XmNactivateCallback, stringDialogCloseCB, window);
2923 /* Add user specified buttons (1st is already done). Selection box
2924 requires a place-holder widget to be added before buttons can be
2925 added, that's what the separator below is for */
2926 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
2927 for (i=1; i<nBtns; i++) {
2928 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2929 XmNlabelString, s1=XmStringCreateSimple(btnLabels[i]),
2930 XmNuserData, (XtPointer)(i+1), NULL);
2931 XtAddCallback(btn, XmNactivateCallback, stringDialogBtnCB, window);
2932 XmStringFree(s1);
2935 #ifdef LESSTIF_VERSION
2936 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2937 the escape key for closing the dialog (probably because the
2938 cancel button is not managed). */
2939 XtAddEventHandler(dialog, KeyPressMask, False, stringDialogEscCB,
2940 (XtPointer)window);
2941 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
2942 True, GrabModeAsync, GrabModeAsync);
2943 #endif /* LESSTIF_VERSION */
2945 /* Put up the dialog */
2946 ManageDialogCenteredOnPointer(dialog);
2948 /* Stop macro execution until the dialog is complete */
2949 PreemptMacro();
2951 /* Return placeholder result. Value will be changed by button callback */
2952 result->tag = INT_TAG;
2953 result->val.n = 0;
2954 return True;
2957 static void stringDialogBtnCB(Widget w, XtPointer clientData,
2958 XtPointer callData)
2960 WindowInfo *window = (WindowInfo *)clientData;
2961 macroCmdInfo *cmdData = window->macroCmdData;
2962 XtPointer userData;
2963 DataValue retVal;
2964 char *text;
2965 int btnNum;
2967 /* shouldn't happen, but would crash if it did */
2968 if (cmdData == NULL)
2969 return;
2971 /* Return the string entered in the selection text area */
2972 text = XmTextGetString(XmSelectionBoxGetChild(cmdData->dialog,
2973 XmDIALOG_TEXT));
2974 retVal.tag = STRING_TAG;
2975 retVal.val.str = AllocString(strlen(text)+1);
2976 strcpy(retVal.val.str, text);
2977 XtFree(text);
2978 ModifyReturnedValue(cmdData->context, retVal);
2980 /* Find the index of the button which was pressed (stored in the userData
2981 field of the button widget). The 1st button, being a gadget, is not
2982 returned in w. */
2983 if (XtClass(w) == xmPushButtonWidgetClass) {
2984 XtVaGetValues(w, XmNuserData, &userData, NULL);
2985 btnNum = (int)userData;
2986 } else
2987 btnNum = 1;
2989 /* Return the button number in the global variable $string_dialog_button */
2990 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
2991 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = btnNum;
2993 /* Pop down the dialog */
2994 XtDestroyWidget(XtParent(cmdData->dialog));
2995 cmdData->dialog = NULL;
2997 /* Continue preempted macro execution */
2998 ResumeMacroExecution(window);
3001 static void stringDialogCloseCB(Widget w, XtPointer clientData,
3002 XtPointer callData)
3004 WindowInfo *window = (WindowInfo *)clientData;
3005 macroCmdInfo *cmdData = window->macroCmdData;
3006 DataValue retVal;
3008 /* shouldn't happen, but would crash if it did */
3009 if (cmdData == NULL)
3010 return;
3012 /* Return an empty string */
3013 retVal.tag = STRING_TAG;
3014 retVal.val.str = PERM_ALLOC_STR("");
3015 ModifyReturnedValue(cmdData->context, retVal);
3017 /* Return button number 0 in the global variable $string_dialog_button */
3018 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3019 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = 0;
3021 /* Pop down the dialog */
3022 XtDestroyWidget(XtParent(cmdData->dialog));
3023 cmdData->dialog = NULL;
3025 /* Continue preempted macro execution */
3026 ResumeMacroExecution(window);
3029 #ifdef LESSTIF_VERSION
3030 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3031 Boolean *cont)
3033 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3034 return;
3035 if (clientData != NULL) {
3036 stringDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3038 *cont = False;
3040 #endif /* LESSTIF_VERSION */
3043 ** A subroutine to put up a calltip
3044 ** First arg is either text to be displayed or a key for tip/tag lookup.
3045 ** Optional second arg is the buffer position beneath which to display the
3046 ** upper-left corner of the tip. Default (or -1) puts it under the cursor.
3047 ** Additional optional arguments:
3048 ** "tipText": (default) Indicates first arg is text to be displayed in tip.
3049 ** "tipKey": Indicates first arg is key in calltips database. If key
3050 ** is not found in tip database then the tags database is also
3051 ** searched.
3052 ** "tagKey": Indicates first arg is key in tags database. (Skips
3053 ** search in calltips database.)
3054 ** "center": Horizontally center the calltip at the position
3055 ** "right": Put the right edge of the calltip at the position
3056 ** "center" and "right" cannot both be specified.
3057 ** "above": Place the calltip above the position
3058 ** "strict": Don't move the calltip to keep it on-screen and away
3059 ** from the cursor's line.
3061 ** Returns the new calltip's ID on success, 0 on failure.
3063 ** Does this need to go on IgnoredActions? I don't think so, since
3064 ** showing a calltip may be part of the action you want to learn.
3066 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3067 DataValue *result, char **errMsg)
3069 char stringStorage[TYPE_INT_STR_SIZE(int)], *tipText, *txtArg;
3070 Boolean anchored = False, lookup = True;
3071 int mode = -1, i;
3072 int anchorPos, hAlign = TIP_LEFT, vAlign = TIP_BELOW,
3073 alignMode = TIP_SLOPPY;
3075 /* Read and check the string */
3076 if (nArgs < 1) {
3077 *errMsg = "%s subroutine called with too few arguments";
3078 return False;
3080 if (nArgs > 6) {
3081 *errMsg = "%s subroutine called with too many arguments";
3082 return False;
3085 /* Read the tip text or key */
3086 if (!readStringArg(argList[0], &tipText, stringStorage, errMsg))
3087 return False;
3089 /* Read the anchor position (-1 for unanchored) */
3090 if (nArgs > 1) {
3091 if (!readIntArg(argList[1], &anchorPos, errMsg))
3092 return False;
3093 } else {
3094 anchorPos = -1;
3096 if (anchorPos >= 0) anchored = True;
3098 /* Any further args are directives for relative positioning */
3099 for (i = 2; i < nArgs; ++i) {
3100 if (!readStringArg(argList[i], &txtArg, stringStorage, errMsg)){
3101 return False;
3103 switch( txtArg[0] ) {
3104 case 'c':
3105 if (strcmp(txtArg, "center"))
3106 goto bad_arg;
3107 hAlign = TIP_CENTER;
3108 break;
3109 case 'r':
3110 if (strcmp(txtArg, "right"))
3111 goto bad_arg;
3112 hAlign = TIP_RIGHT;
3113 break;
3114 case 'a':
3115 if (strcmp(txtArg, "above"))
3116 goto bad_arg;
3117 vAlign = TIP_ABOVE;
3118 break;
3119 case 's':
3120 if (strcmp(txtArg, "strict"))
3121 goto bad_arg;
3122 alignMode = TIP_STRICT;
3123 break;
3124 case 't':
3125 if (!strcmp(txtArg, "tipText"))
3126 mode = -1;
3127 else if (!strcmp(txtArg, "tipKey"))
3128 mode = TIP;
3129 else if (!strcmp(txtArg, "tagKey"))
3130 mode = TIP_FROM_TAG;
3131 else
3132 goto bad_arg;
3133 break;
3134 default:
3135 goto bad_arg;
3139 result->tag = INT_TAG;
3140 if (mode < 0) lookup = False;
3141 /* Look up (maybe) a calltip and display it */
3142 result->val.n = ShowTipString( window, tipText, anchored, anchorPos, lookup,
3143 mode, hAlign, vAlign, alignMode );
3145 return True;
3147 bad_arg:
3148 /* This is how the (more informative) global var. version would work,
3149 assuming there was a global buffer called msg. */
3150 /* sprintf(msg, "unrecognized argument to %%s: \"%s\"", txtArg);
3151 *errMsg = msg; */
3152 *errMsg = "unrecognized argument to %s";
3153 return False;
3157 ** A subroutine to kill the current calltip
3159 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3160 DataValue *result, char **errMsg)
3162 int calltipID = 0;
3164 if (nArgs > 1) {
3165 *errMsg = "%s subroutine called with too many arguments";
3166 return False;
3168 if (nArgs > 0) {
3169 if (!readIntArg(argList[0], &calltipID, errMsg))
3170 return False;
3173 KillCalltip( window, calltipID );
3175 result->tag = NO_TAG;
3176 return True;
3180 * A subroutine to get the ID of the current calltip, or 0 if there is none.
3182 static int calltipIDMV(WindowInfo *window, DataValue *argList,
3183 int nArgs, DataValue *result, char **errMsg)
3185 result->tag = INT_TAG;
3186 result->val.n = GetCalltipID(window, 0);
3187 return True;
3190 /* T Balinski */
3191 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3192 DataValue *result, char **errMsg)
3194 macroCmdInfo *cmdData;
3195 char stringStorage[9][TYPE_INT_STR_SIZE(int)], *btnLabels[8], *message, *text;
3196 Widget dialog, btn;
3197 int i, nBtns;
3198 XmString s1, s2;
3199 long nlines = 0;
3200 char *p, *old_p, **text_lines, *tmp;
3201 int tmp_len;
3202 int n, is_last;
3203 XmString *test_strings;
3204 int tabDist;
3205 Arg al[20];
3206 int ac;
3209 /* Ignore the focused window passed as the function argument and put
3210 the dialog up over the window which is executing the macro */
3211 window = MacroRunWindow();
3212 cmdData = window->macroCmdData;
3214 /* Dialogs require macro to be suspended and interleaved with other macros.
3215 This subroutine can't be run if macro execution can't be interrupted */
3216 if (!cmdData) {
3217 *errMsg = "%s can't be called from non-suspendable context";
3218 return False;
3221 /* Read and check the arguments. The first being the dialog message,
3222 and the rest being the button labels */
3223 if (nArgs < 2) {
3224 *errMsg = "%s subroutine called with no message, string or arguments";
3225 return False;
3228 if (!readStringArg(argList[0], &message, stringStorage[0], errMsg))
3229 return False;
3231 if (!readStringArg(argList[1], &text, stringStorage[0], errMsg))
3232 return False;
3234 if (!text || text[0] == '\0') {
3235 *errMsg = "%s subroutine called with empty list data";
3236 return False;
3239 for (i=2; i<nArgs; i++)
3240 if (!readStringArg(argList[i], &btnLabels[i-2], stringStorage[i],
3241 errMsg))
3242 return False;
3243 if (nArgs == 2) {
3244 btnLabels[0] = "Dismiss";
3245 nBtns = 1;
3246 } else
3247 nBtns = nArgs - 2;
3249 /* count the lines in the text - add one for unterminated last line */
3250 nlines = 1;
3251 for (p = text; *p; p++)
3252 if (*p == '\n')
3253 nlines++;
3255 /* now set up arrays of pointers to lines */
3256 /* test_strings to hold the display strings (tab expanded) */
3257 /* text_lines to hold the original text lines (without the '\n's) */
3258 test_strings = (XmString *) XtMalloc(sizeof(XmString) * nlines);
3259 text_lines = (char **)XtMalloc(sizeof(char *) * (nlines + 1));
3260 for (n = 0; n < nlines; n++) {
3261 test_strings[n] = (XmString)0;
3262 text_lines[n] = (char *)0;
3264 text_lines[n] = (char *)0; /* make sure this is a null-terminated table */
3266 /* pick up the tabDist value */
3267 tabDist = window->buffer->tabDist;
3269 /* load the table */
3270 n = 0;
3271 is_last = 0;
3272 p = old_p = text;
3273 tmp_len = 0; /* current allocated size of temporary buffer tmp */
3274 tmp = malloc(1); /* temporary buffer into which to expand tabs */
3275 do {
3276 is_last = (*p == '\0');
3277 if (*p == '\n' || is_last) {
3278 *p = '\0';
3279 if (strlen(old_p) > 0) { /* only include non-empty lines */
3280 char *s, *t;
3281 int l;
3283 /* save the actual text line in text_lines[n] */
3284 text_lines[n] = (char *)XtMalloc(strlen(old_p) + 1);
3285 strcpy(text_lines[n], old_p);
3287 /* work out the tabs expanded length */
3288 for (s = old_p, l = 0; *s; s++)
3289 l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
3291 /* verify tmp is big enough then tab-expand old_p into tmp */
3292 if (l > tmp_len)
3293 tmp = realloc(tmp, (tmp_len = l) + 1);
3294 for (s = old_p, t = tmp, l = 0; *s; s++) {
3295 if (*s == '\t') {
3296 for (i = tabDist - (l % tabDist); i--; l++)
3297 *t++ = ' ';
3299 else {
3300 *t++ = *s;
3301 l++;
3304 *t = '\0';
3305 /* that's it: tmp is the tab-expanded version of old_p */
3306 test_strings[n] = MKSTRING(tmp);
3307 n++;
3309 old_p = p + 1;
3310 if (!is_last)
3311 *p = '\n'; /* put back our newline */
3313 p++;
3314 } while (!is_last);
3316 free(tmp); /* don't need this anymore */
3317 nlines = n;
3318 if (nlines == 0) {
3319 test_strings[0] = MKSTRING("");
3320 nlines = 1;
3323 /* Create the selection box dialog widget and its dialog shell parent */
3324 ac = 0;
3325 XtSetArg(al[ac], XmNtitle, " "); ac++;
3326 XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++;
3327 XtSetArg(al[ac], XmNlistItems, test_strings); ac++;
3328 XtSetArg(al[ac], XmNlistItemCount, nlines); ac++;
3329 XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++;
3330 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0])); ac++;
3331 dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac);
3332 AddMotifCloseCallback(XtParent(dialog), listDialogCloseCB, window);
3333 XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window);
3334 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3335 XmNuserData, (XtPointer)1, NULL);
3336 XmStringFree(s1);
3337 XmStringFree(s2);
3338 cmdData->dialog = dialog;
3340 /* forget lines stored in list */
3341 while (n--)
3342 XmStringFree(test_strings[n]);
3343 XtFree((char *)test_strings);
3345 /* modify the list */
3346 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST),
3347 XmNselectionPolicy, XmSINGLE_SELECT,
3348 XmNuserData, (XtPointer)text_lines, NULL);
3350 /* Unmanage unneeded widgets */
3351 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
3352 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3353 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3354 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
3355 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));
3357 /* Make callback for the unmanaged cancel button (which can
3358 still get executed via the esc key) activate close box action */
3359 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3360 XmNactivateCallback, listDialogCloseCB, window);
3362 /* Add user specified buttons (1st is already done). Selection box
3363 requires a place-holder widget to be added before buttons can be
3364 added, that's what the separator below is for */
3365 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3366 for (i=1; i<nBtns; i++) {
3367 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3368 XmNlabelString, s1=XmStringCreateSimple(btnLabels[i]),
3369 XmNuserData, (XtPointer)(i+1), NULL);
3370 XtAddCallback(btn, XmNactivateCallback, listDialogBtnCB, window);
3371 XmStringFree(s1);
3374 #ifdef LESSTIF_VERSION
3375 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3376 the escape key for closing the dialog. */
3377 XtAddEventHandler(dialog, KeyPressMask, False, listDialogEscCB,
3378 (XtPointer)window);
3379 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3380 True, GrabModeAsync, GrabModeAsync);
3381 #endif /* LESSTIF_VERSION */
3383 /* Put up the dialog */
3384 ManageDialogCenteredOnPointer(dialog);
3386 /* Stop macro execution until the dialog is complete */
3387 PreemptMacro();
3389 /* Return placeholder result. Value will be changed by button callback */
3390 result->tag = INT_TAG;
3391 result->val.n = 0;
3392 return True;
3395 static void listDialogBtnCB(Widget w, XtPointer clientData,
3396 XtPointer callData)
3398 WindowInfo *window = (WindowInfo *)clientData;
3399 macroCmdInfo *cmdData = window->macroCmdData;
3400 XtPointer userData;
3401 DataValue retVal;
3402 char *text;
3403 char **text_lines;
3404 int btnNum;
3405 int n_sel, *seltable, sel_index = 0;
3406 Widget theList;
3408 /* shouldn't happen, but would crash if it did */
3409 if (cmdData == NULL)
3410 return;
3412 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3413 /* Return the string selected in the selection list area */
3414 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3415 if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) {
3416 n_sel = 0;
3418 else {
3419 sel_index = seltable[0] - 1;
3420 XtFree((XtPointer)seltable);
3423 if (!n_sel) {
3424 text = PERM_ALLOC_STR("");
3426 else {
3427 text = AllocString(strlen((char *)text_lines[sel_index]) + 1);
3428 strcpy(text, text_lines[sel_index]);
3431 /* don't need text_lines anymore: free it */
3432 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3433 XtFree((XtPointer)text_lines[sel_index]);
3434 XtFree((XtPointer)text_lines);
3436 retVal.tag = STRING_TAG;
3437 retVal.val.str = text;
3438 ModifyReturnedValue(cmdData->context, retVal);
3440 /* Find the index of the button which was pressed (stored in the userData
3441 field of the button widget). The 1st button, being a gadget, is not
3442 returned in w. */
3443 if (XtClass(w) == xmPushButtonWidgetClass) {
3444 XtVaGetValues(w, XmNuserData, &userData, NULL);
3445 btnNum = (int)userData;
3446 } else
3447 btnNum = 1;
3449 /* Return the button number in the global variable $list_dialog_button */
3450 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3451 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = btnNum;
3453 /* Pop down the dialog */
3454 XtDestroyWidget(XtParent(cmdData->dialog));
3455 cmdData->dialog = NULL;
3457 /* Continue preempted macro execution */
3458 ResumeMacroExecution(window);
3461 static void listDialogCloseCB(Widget w, XtPointer clientData,
3462 XtPointer callData)
3464 WindowInfo *window = (WindowInfo *)clientData;
3465 macroCmdInfo *cmdData = window->macroCmdData;
3466 DataValue retVal;
3467 char **text_lines;
3468 int sel_index;
3469 Widget theList;
3471 /* shouldn't happen, but would crash if it did */
3472 if (cmdData == NULL)
3473 return;
3475 /* don't need text_lines anymore: retrieve it then free it */
3476 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3477 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3478 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3479 XtFree((XtPointer)text_lines[sel_index]);
3480 XtFree((XtPointer)text_lines);
3482 /* Return an empty string */
3483 retVal.tag = STRING_TAG;
3484 retVal.val.str = PERM_ALLOC_STR("");
3485 ModifyReturnedValue(cmdData->context, retVal);
3487 /* Return button number 0 in the global variable $list_dialog_button */
3488 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3489 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = 0;
3491 /* Pop down the dialog */
3492 XtDestroyWidget(XtParent(cmdData->dialog));
3493 cmdData->dialog = NULL;
3495 /* Continue preempted macro execution */
3496 ResumeMacroExecution(window);
3498 /* T Balinski End */
3500 #ifdef LESSTIF_VERSION
3501 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3502 Boolean *cont)
3504 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3505 return;
3506 if (clientData != NULL) {
3507 listDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3509 *cont = False;
3511 #endif /* LESSTIF_VERSION */
3514 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
3515 DataValue *result, char **errMsg)
3517 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3518 char *leftStr, *rightStr, *argStr;
3519 int considerCase = True;
3520 int i;
3521 int compareResult;
3523 if (nArgs < 2) {
3524 return(wrongNArgsErr(errMsg));
3526 if (!readStringArg(argList[0], &leftStr, stringStorage[0], errMsg))
3527 return False;
3528 if (!readStringArg(argList[1], &rightStr, stringStorage[1], errMsg))
3529 return False;
3530 for (i = 2; i < nArgs; ++i) {
3531 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
3532 return False;
3533 else if (!strcmp(argStr, "case"))
3534 considerCase = True;
3535 else if (!strcmp(argStr, "nocase"))
3536 considerCase = False;
3537 else {
3538 *errMsg = "Unrecognized argument to %s";
3539 return False;
3542 if (considerCase) {
3543 compareResult = strcmp(leftStr, rightStr);
3544 compareResult = (compareResult > 0) ? 1 : ((compareResult < 0) ? -1 : 0);
3546 else {
3547 compareResult = strCaseCmp(leftStr, rightStr);
3549 result->tag = INT_TAG;
3550 result->val.n = compareResult;
3551 return True;
3555 ** This function is intended to split strings into an array of substrings
3556 ** Importatnt note: It should always return at least one entry with key 0
3557 ** split("", ",") result[0] = ""
3558 ** split("1,2", ",") result[0] = "1" result[1] = "2"
3559 ** split("1,2,", ",") result[0] = "1" result[1] = "2" result[2] = ""
3561 ** This behavior is specifically important when used to break up
3562 ** array sub-scripts
3565 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
3566 DataValue *result, char **errMsg)
3568 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3569 char *sourceStr, *splitStr, *typeSplitStr;
3570 int searchType, beginPos, foundStart, foundEnd, strLength;
3571 int found, elementEnd, indexNum;
3572 char indexStr[28], *allocIndexStr;
3573 DataValue element;
3574 int elementLen;
3576 if (nArgs < 2) {
3577 return(wrongNArgsErr(errMsg));
3579 if (!readStringArg(argList[0], &sourceStr, stringStorage[0], errMsg)) {
3580 *errMsg = "first argument must be a string: %s";
3581 return(False);
3583 if (!readStringArg(argList[1], &splitStr, stringStorage[1], errMsg)) {
3584 splitStr = NULL;
3586 else {
3587 if (splitStr[0] == 0) {
3588 splitStr = NULL;
3591 if (splitStr == NULL) {
3592 *errMsg = "second argument must be a non-empty string: %s";
3593 return(False);
3595 if (nArgs > 2 && readStringArg(argList[2], &typeSplitStr, stringStorage[2], errMsg)) {
3596 if (!StringToSearchType(typeSplitStr, &searchType)) {
3597 *errMsg = "unrecognized argument to %s";
3598 return(False);
3601 else {
3602 searchType = SEARCH_LITERAL;
3605 result->tag = ARRAY_TAG;
3606 result->val.arrayPtr = ArrayNew();
3608 beginPos = 0;
3609 indexNum = 0;
3610 strLength = strlen(sourceStr);
3611 found = 1;
3612 while (found && beginPos < strLength) {
3613 sprintf(indexStr, "%d", indexNum);
3614 allocIndexStr = AllocString(strlen(indexStr) + 1);
3615 if (!allocIndexStr) {
3616 *errMsg = "array element failed to allocate key: %s";
3617 return(False);
3619 strcpy(allocIndexStr, indexStr);
3620 found = SearchString(sourceStr, splitStr, SEARCH_FORWARD, searchType,
3621 False, beginPos, &foundStart, &foundEnd,
3622 NULL, NULL, GetWindowDelimiters(window));
3623 elementEnd = found ? foundStart : strLength;
3624 elementLen = elementEnd - beginPos;
3625 element.tag = STRING_TAG;
3626 element.val.str = AllocString(elementLen + 1);
3627 if (!element.val.str) {
3628 *errMsg = "failed to allocate element value: %s";
3629 return(False);
3631 strncpy(element.val.str, &sourceStr[beginPos], elementLen);
3632 element.val.str[elementLen] = 0;
3634 if (!ArrayInsert(result, allocIndexStr, &element)) {
3635 M_ARRAY_INSERT_FAILURE();
3638 beginPos = found ? foundEnd : strLength;
3639 ++indexNum;
3641 if (found) {
3642 sprintf(indexStr, "%d", indexNum);
3643 allocIndexStr = AllocString(strlen(indexStr) + 1);
3644 if (!allocIndexStr) {
3645 *errMsg = "array element failed to allocate key: %s";
3646 return(False);
3648 strcpy(allocIndexStr, indexStr);
3649 element.tag = STRING_TAG;
3650 element.val.str = PERM_ALLOC_STR("");
3652 if (!ArrayInsert(result, allocIndexStr, &element)) {
3653 M_ARRAY_INSERT_FAILURE();
3656 return(True);
3660 ** Set the backlighting string resource for the current window. If no parameter
3661 ** is passed or the value "default" is passed, it attempts to set the preference
3662 ** value of the resource. If the empty string is passed, the backlighting string
3663 ** will be cleared, turning off backlighting.
3665 /* DISABLED for 5.4
3666 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
3667 int nArgs, DataValue *result, char **errMsg)
3669 char *backlightString;
3671 if (nArgs == 0) {
3672 backlightString = GetPrefBacklightCharTypes();
3674 else if (nArgs == 1) {
3675 if (argList[0].tag != STRING_TAG) {
3676 *errMsg = "%s not called with a string parameter";
3677 return False;
3679 backlightString = argList[0].val.str;
3681 else
3682 return wrongNArgsErr(errMsg);
3684 if (strcmp(backlightString, "default") == 0)
3685 backlightString = GetPrefBacklightCharTypes();
3686 if (backlightString && *backlightString == '\0') / * empty string param * /
3687 backlightString = NULL; / * turns of backlighting * /
3689 SetBacklightChars(window, backlightString);
3690 return True;
3691 } */
3693 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
3694 DataValue *result, char **errMsg)
3696 result->tag = INT_TAG;
3697 result->val.n = TextGetCursorPos(window->lastFocus);
3698 return True;
3701 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
3702 DataValue *result, char **errMsg)
3704 int line, cursorPos, colNum;
3706 result->tag = INT_TAG;
3707 cursorPos = TextGetCursorPos(window->lastFocus);
3708 if (!TextPosToLineAndCol(window->lastFocus, cursorPos, &line, &colNum))
3709 line = BufCountLines(window->buffer, 0, cursorPos) + 1;
3710 result->val.n = line;
3711 return True;
3714 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
3715 DataValue *result, char **errMsg)
3717 textBuffer *buf = window->buffer;
3718 int cursorPos;
3720 result->tag = INT_TAG;
3721 cursorPos = TextGetCursorPos(window->lastFocus);
3722 result->val.n = BufCountDispChars(buf, BufStartOfLine(buf, cursorPos),
3723 cursorPos);
3724 return True;
3727 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
3728 DataValue *result, char **errMsg)
3730 result->tag = STRING_TAG;
3731 result->val.str = AllocString(strlen(window->filename) + 1);
3732 strcpy(result->val.str, window->filename);
3733 return True;
3736 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
3737 DataValue *result, char **errMsg)
3739 result->tag = STRING_TAG;
3740 result->val.str = AllocString(strlen(window->path) + 1);
3741 strcpy(result->val.str, window->path);
3742 return True;
3745 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
3746 DataValue *result, char **errMsg)
3748 result->tag = INT_TAG;
3749 result->val.n = window->buffer->length;
3750 return True;
3753 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
3754 DataValue *result, char **errMsg)
3756 result->tag = INT_TAG;
3757 result->val.n = window->buffer->primary.selected ?
3758 window->buffer->primary.start : -1;
3759 return True;
3762 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
3763 DataValue *result, char **errMsg)
3765 result->tag = INT_TAG;
3766 result->val.n = window->buffer->primary.selected ?
3767 window->buffer->primary.end : -1;
3768 return True;
3771 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
3772 DataValue *result, char **errMsg)
3774 selection *sel = &window->buffer->primary;
3776 result->tag = INT_TAG;
3777 result->val.n = sel->selected && sel->rectangular ? sel->rectStart : -1;
3778 return True;
3781 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
3782 DataValue *result, char **errMsg)
3784 selection *sel = &window->buffer->primary;
3786 result->tag = INT_TAG;
3787 result->val.n = sel->selected && sel->rectangular ? sel->rectEnd : -1;
3788 return True;
3791 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
3792 DataValue *result, char **errMsg)
3794 int margin, nCols;
3796 XtVaGetValues(window->textArea, textNcolumns, &nCols,
3797 textNwrapMargin, &margin, NULL);
3798 result->tag = INT_TAG;
3799 result->val.n = margin == 0 ? nCols : margin;
3800 return True;
3803 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
3804 DataValue *result, char **errMsg)
3806 result->tag = INT_TAG;
3807 result->val.n = window->showStats ? 1 : 0;
3808 return True;
3811 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
3812 DataValue *result, char **errMsg)
3814 result->tag = INT_TAG;
3815 result->val.n = window->showISearchLine ? 1 : 0;
3816 return True;
3819 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
3820 DataValue *result, char **errMsg)
3822 result->tag = INT_TAG;
3823 result->val.n = window->showLineNumbers ? 1 : 0;
3824 return True;
3827 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
3828 DataValue *result, char **errMsg)
3830 char *res = NULL;
3832 switch (window->indentStyle) {
3833 case NO_AUTO_INDENT:
3834 res = PERM_ALLOC_STR("off");
3835 break;
3836 case AUTO_INDENT:
3837 res = PERM_ALLOC_STR("on");
3838 break;
3839 case SMART_INDENT:
3840 res = PERM_ALLOC_STR("smart");
3841 break;
3842 default:
3843 *errMsg = "Invalid indent style value encountered in %s";
3844 return False;
3845 break;
3847 result->tag = STRING_TAG;
3848 result->val.str = res;
3849 return True;
3852 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
3853 DataValue *result, char **errMsg)
3855 char *res = NULL;
3857 switch (window->wrapMode) {
3858 case NO_WRAP:
3859 res = PERM_ALLOC_STR("none");
3860 break;
3861 case NEWLINE_WRAP:
3862 res = PERM_ALLOC_STR("auto");
3863 break;
3864 case CONTINUOUS_WRAP:
3865 res = PERM_ALLOC_STR("continuous");
3866 break;
3867 default:
3868 *errMsg = "Invalid wrap style value encountered in %s";
3869 return False;
3870 break;
3872 result->tag = STRING_TAG;
3873 result->val.str = res;
3874 return True;
3877 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
3878 DataValue *result, char **errMsg)
3880 result->tag = INT_TAG;
3881 result->val.n = window->highlightSyntax ? 1 : 0;
3882 return True;
3885 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
3886 DataValue *result, char **errMsg)
3888 result->tag = INT_TAG;
3889 result->val.n = window->saveOldVersion ? 1 : 0;
3890 return True;
3893 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
3894 DataValue *result, char **errMsg)
3896 result->tag = INT_TAG;
3897 result->val.n = window->autoSave ? 1 : 0;
3898 return True;
3901 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
3902 DataValue *result, char **errMsg)
3904 char *res = NULL;
3906 switch (window->showMatchingStyle) {
3907 case NO_FLASH:
3908 res = PERM_ALLOC_STR(NO_FLASH_STRING);
3909 break;
3910 case FLASH_DELIMIT:
3911 res = PERM_ALLOC_STR(FLASH_DELIMIT_STRING);
3912 break;
3913 case FLASH_RANGE:
3914 res = PERM_ALLOC_STR(FLASH_RANGE_STRING);
3915 break;
3916 default:
3917 *errMsg = "Invalid match flashing style value encountered in %s";
3918 return False;
3919 break;
3921 result->tag = STRING_TAG;
3922 result->val.str = res;
3923 return True;
3926 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
3927 DataValue *result, char **errMsg)
3929 result->tag = INT_TAG;
3930 result->val.n = window->overstrike ? 1 : 0;
3931 return True;
3934 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
3935 DataValue *result, char **errMsg)
3937 result->tag = INT_TAG;
3938 result->val.n = (IS_ANY_LOCKED(window->lockReasons)) ? 1 : 0;
3939 return True;
3942 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
3943 DataValue *result, char **errMsg)
3945 result->tag = INT_TAG;
3946 result->val.n = (IS_USER_LOCKED(window->lockReasons)) ? 1 : 0;
3947 return True;
3950 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
3951 DataValue *result, char **errMsg)
3953 char *res = NULL;
3955 switch (window->fileFormat) {
3956 case UNIX_FILE_FORMAT:
3957 res = PERM_ALLOC_STR("unix");
3958 break;
3959 case DOS_FILE_FORMAT:
3960 res = PERM_ALLOC_STR("dos");
3961 break;
3962 case MAC_FILE_FORMAT:
3963 res = PERM_ALLOC_STR("macintosh");
3964 break;
3965 default:
3966 *errMsg = "Invalid linefeed style value encountered in %s";
3967 return False;
3969 result->tag = STRING_TAG;
3970 result->val.str = res;
3971 return True;
3974 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
3975 DataValue *result, char **errMsg)
3977 result->tag = STRING_TAG;
3978 result->val.str = AllocString(strlen(window->fontName) + 1);
3979 strcpy(result->val.str, window->fontName);
3980 return True;
3983 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
3984 DataValue *result, char **errMsg)
3986 result->tag = STRING_TAG;
3987 result->val.str = AllocString(strlen(window->italicFontName) + 1);
3988 strcpy(result->val.str, window->italicFontName);
3989 return True;
3992 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
3993 DataValue *result, char **errMsg)
3995 result->tag = STRING_TAG;
3996 result->val.str = AllocString(strlen(window->boldFontName) + 1);
3997 strcpy(result->val.str, window->boldFontName);
3998 return True;
4001 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4002 DataValue *result, char **errMsg)
4004 result->tag = STRING_TAG;
4005 result->val.str = AllocString(strlen(window->boldItalicFontName) + 1);
4006 strcpy(result->val.str, window->boldItalicFontName);
4007 return True;
4010 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
4011 DataValue *result, char **errMsg)
4013 result->tag = STRING_TAG;
4014 result->val.str = PERM_ALLOC_STR(ARRAY_DIM_SEP);
4015 return True;
4018 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4019 DataValue *result, char **errMsg)
4021 result->tag = INT_TAG;
4022 result->val.n = TextGetMinFontWidth(window->textArea, window->highlightSyntax);
4023 return True;
4026 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4027 DataValue *result, char **errMsg)
4029 result->tag = INT_TAG;
4030 result->val.n = TextGetMaxFontWidth(window->textArea, window->highlightSyntax);
4031 return True;
4034 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4035 DataValue *result, char **errMsg)
4037 result->tag = INT_TAG;
4038 result->val.n = TextFirstVisibleLine(window->lastFocus);
4039 return True;
4042 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
4043 DataValue *result, char **errMsg)
4045 result->tag = INT_TAG;
4046 result->val.n = TextNumVisibleLines(window->lastFocus);
4047 return True;
4050 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4051 DataValue *result, char **errMsg)
4053 result->tag = INT_TAG;
4054 result->val.n = TextVisibleWidth(window->lastFocus);
4055 return True;
4058 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
4059 DataValue *result, char **errMsg)
4061 result->tag = INT_TAG;
4062 result->val.n = WidgetToPaneIndex(window, window->lastFocus) + 1;
4063 return True;
4066 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
4067 DataValue *result, char **errMsg)
4069 result->tag = INT_TAG;
4070 result->val.n = window->nPanes + 1;
4071 return True;
4074 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
4075 DataValue *result, char **errMsg)
4077 result->tag = ARRAY_TAG;
4078 result->val.arrayPtr = NULL;
4079 return True;
4082 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4083 DataValue *result, char **errMsg)
4085 char *serverName = GetPrefServerName();
4087 result->tag = STRING_TAG;
4088 result->val.str = AllocString(strlen(serverName) + 1);
4089 strcpy(result->val.str, serverName);
4090 return True;
4093 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4094 DataValue *result, char **errMsg)
4096 result->tag = INT_TAG;
4097 result->val.n = window->buffer->tabDist;
4098 return True;
4101 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4102 DataValue *result, char **errMsg)
4104 int dist;
4106 XtVaGetValues(window->textArea, textNemulateTabs, &dist, NULL);
4107 result->tag = INT_TAG;
4108 result->val.n = dist == 0 ? -1 : dist;
4109 return True;
4112 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
4113 DataValue *result, char **errMsg)
4115 result->tag = INT_TAG;
4116 result->val.n = window->buffer->useTabs;
4117 return True;
4120 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
4121 DataValue *result, char **errMsg)
4123 result->tag = INT_TAG;
4124 result->val.n = window->fileChanged;
4125 return True;
4128 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4129 DataValue *result, char **errMsg)
4131 char *lmName = LanguageModeName(window->languageMode);
4133 if (lmName == NULL)
4134 lmName = "Plain";
4135 result->tag = STRING_TAG;
4136 result->val.str = AllocString(strlen(lmName) + 1);
4137 strcpy(result->val.str, lmName);
4138 return True;
4141 /* DISABLED for 5.4
4142 static int backlightStringMV(WindowInfo *window, DataValue *argList,
4143 int nArgs, DataValue *result, char **errMsg)
4145 char *backlightString = window->backlightCharTypes;
4147 result->tag = STRING_TAG;
4148 if (!backlightString || !window->backlightChars)
4149 backlightString = "";
4150 result->val.str = AllocString(strlen(backlightString) + 1);
4151 strcpy(result->val.str, backlightString);
4152 return True;
4153 } */
4155 /* -------------------------------------------------------------------------- */
4158 ** Range set macro variables and functions
4161 static int rangesetListMV(WindowInfo *window, DataValue *argList, int nArgs,
4162 DataValue *result, char **errMsg)
4164 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4165 unsigned char *rangesetList;
4166 char *allocIndexStr;
4167 char indexStr[4] ;
4168 int nRangesets, i;
4169 DataValue element;
4171 result->tag = ARRAY_TAG;
4172 result->val.arrayPtr = ArrayNew();
4174 if (rangesetTable == NULL) {
4175 return True;
4178 rangesetList = RangesetGetList(rangesetTable);
4179 nRangesets = strlen((char*)rangesetList);
4180 for(i = 0; i < nRangesets; i++) {
4181 element.tag = INT_TAG;
4182 element.val.n = rangesetList[i];
4184 sprintf(indexStr, "%d", nRangesets - i - 1);
4185 allocIndexStr = AllocString(strlen(indexStr) + 1);
4186 if (allocIndexStr == NULL)
4187 M_FAILURE("Failed to allocate array key in %s");
4188 strcpy(allocIndexStr, indexStr);
4190 if (!ArrayInsert(result, allocIndexStr, &element))
4191 M_FAILURE("Failed to insert array element in %s");
4194 return True;
4199 ** Built-in macro subroutine to create a new rangeset or rangesets.
4200 ** If called with one argument: $1 is the number of rangesets required and
4201 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4202 ** (or an empty array if the requested number of rangesets are not available).
4203 ** If called with no arguments, returns a single rangeset label (not an array),
4204 ** or an empty string if there are no rangesets available.
4206 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
4207 DataValue *result, char **errMsg)
4209 char label;
4210 int i, nRangesetsRequired;
4211 DataValue element;
4212 char indexStr[3], *allocIndexStr;
4214 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4216 if (nArgs > 1)
4217 return wrongNArgsErr(errMsg);
4219 if (rangesetTable == NULL) {
4220 window->buffer->rangesetTable = rangesetTable =
4221 RangesetTableAlloc(window->buffer);
4224 if (nArgs == 0) {
4225 label = RangesetCreate(rangesetTable);
4227 result->tag = INT_TAG;
4228 result->val.n = label;
4229 return True;
4231 else {
4232 if (!readIntArg(argList[0], &nRangesetsRequired, errMsg))
4233 return False;
4235 result->tag = ARRAY_TAG;
4236 result->val.arrayPtr = ArrayNew();
4238 if (nRangesetsRequired > nRangesetsAvailable(rangesetTable))
4239 return True;
4241 for (i = 0; i < nRangesetsRequired; i++) {
4242 element.tag = INT_TAG;
4243 element.val.n = RangesetCreate(rangesetTable);
4245 sprintf(indexStr, "%d", i);
4246 allocIndexStr = AllocString(strlen(indexStr) + 1);
4247 if (!allocIndexStr) {
4248 *errMsg = "Array element failed to allocate key: %s";
4249 return(False);
4251 strcpy(allocIndexStr, indexStr);
4252 ArrayInsert(result, allocIndexStr, &element);
4255 return True;
4261 ** Built-in macro subroutine for forgetting a range set.
4264 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
4265 DataValue *result, char **errMsg)
4267 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4268 DataValue *array;
4269 DataValue element;
4270 char keyString[3];
4271 char deleteLabels[N_RANGESETS];
4272 int i, arraySize;
4274 if (nArgs != 1) {
4275 return wrongNArgsErr(errMsg);
4278 if (argList[0].tag == ARRAY_TAG) {
4279 array = &argList[0];
4280 arraySize = ArraySize(array);
4282 if (arraySize > N_RANGESETS) {
4283 M_FAILURE("Too many elements in array in %s");
4286 for (i = 0; i < arraySize; i++) {
4287 sprintf(keyString, "%d", i);
4289 if (!ArrayGet(array, keyString, &element)) {
4290 M_FAILURE("Invalid key in array in %s");
4293 if (element.tag != INT_TAG
4294 || !RangesetLabelOK(element.val.n)) {
4295 M_FAILURE("Invalid rangeset label in array in %s");
4298 deleteLabels[i] = element.val.n;
4301 for (i = 0; i < arraySize; i++) {
4302 RangesetForget(rangesetTable, deleteLabels[i]);
4306 else {
4307 if (argList[0].tag != INT_TAG
4308 || !RangesetLabelOK(argList[0].val.n)) {
4309 M_FAILURE("Invalid rangeset label in %s");
4312 if(rangesetTable != NULL) {
4313 RangesetForget(rangesetTable, argList[0].val.n);
4317 /* set up result */
4318 result->tag = NO_TAG;
4319 return True;}
4323 ** Built-in macro subroutine for adding to a range set. Arguments are $1: range
4324 ** set label (one integer), then either (a) $2: source range set label,
4325 ** (b) $2: int start-range, $3: int end-range, (c) nothing (use selection
4326 ** if any to specify range to add - must not be rectangular). Returns the
4327 ** index of the newly added range (cases b and c), or 0 (case a).
4330 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
4331 DataValue *result, char **errMsg)
4333 textBuffer *buffer = window->buffer;
4334 RangesetTable *rangesetTable = buffer->rangesetTable;
4335 Rangeset *targetRangeset, *sourceRangeset;
4336 int start, end, isRect, rectStart, rectEnd, maxpos, index;
4338 if (nArgs < 1 || nArgs > 3)
4339 return wrongNArgsErr(errMsg);
4341 if (argList[0].tag != INT_TAG
4342 || !RangesetLabelOK(argList[0].val.n)) {
4343 M_FAILURE("First parameter is an invalid rangeset label in %s");
4346 if (rangesetTable == NULL) {
4347 M_FAILURE("Rangeset does not exist in %s");
4350 targetRangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4352 if (targetRangeset == NULL) {
4353 M_FAILURE("Rangeset does not exist in %s");
4356 start = end = -1;
4358 if (nArgs == 1) {
4359 /* pick up current selection in this window */
4360 if (!BufGetSelectionPos(buffer, &start, &end,
4361 &isRect, &rectStart, &rectEnd) || isRect) {
4362 M_FAILURE("Selection missing or rectangular in call to %s");
4364 if (!RangesetAddBetween(targetRangeset, start, end)) {
4365 M_FAILURE("Failure to add selection in %s");
4369 if (nArgs == 2) {
4370 /* add ranges taken from a second set */
4371 if (argList[1].tag != INT_TAG
4372 || !RangesetLabelOK(argList[1].val.n)) {
4373 M_FAILURE("Second parameter is an invalid rangeset label in %s");
4376 sourceRangeset = RangesetFetch(rangesetTable, argList[1].val.n);
4377 if (sourceRangeset == NULL) {
4378 M_FAILURE("Second rangeset does not exist in %s");
4381 if (!RangesetAdd(targetRangeset, sourceRangeset)) {
4382 M_FAILURE("Failed to merge rangesets in %s");
4386 if (nArgs == 3) {
4387 /* add a range bounded by the start and end positions in $2, $3 */
4388 if (!readIntArg(argList[1], &start, errMsg)) {
4389 return False;
4391 if (!readIntArg(argList[2], &end, errMsg)) {
4392 return False;
4395 /* make sure range is in order and fits buffer size */
4396 maxpos = buffer->gapEnd - buffer->gapStart + buffer->length;
4397 if (start < 0) start = 0;
4398 if (start > maxpos) start = maxpos;
4399 if (end < 0) end = 0;
4400 if (end > maxpos) end = maxpos;
4401 if (start > end) {int temp = start; start = end; end = temp;}
4403 if (!RangesetAddBetween(targetRangeset, start, end)) {
4404 M_FAILURE("Failed to add range in %s");
4408 /* (to) which range did we just add? */
4409 if (nArgs != 2 && start >= 0) {
4410 start = (start + end) / 2; /* "middle" of added range */
4411 index = 1 + RangesetFindRangeOfPos(targetRangeset, start, False);
4413 else {
4414 index = 0;
4417 /* set up result */
4418 result->tag = INT_TAG;
4419 result->val.n = index;
4420 return True;
4425 ** Built-in macro subroutine for removing from a range set. Almost identical to
4426 ** rangesetAddMS() - only changes are from RangesetAdd()/RangesetAddBetween()
4427 ** to RangesetSubtract()/RangesetSubtractBetween(), the handling of an
4428 ** undefined destination range, and that it returns no value.
4431 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
4432 DataValue *result, char **errMsg)
4434 textBuffer *buffer = window->buffer;
4435 RangesetTable *rangesetTable = buffer->rangesetTable;
4436 Rangeset *targetRangeset, *sourceRangeset;
4437 int start, end, isRect, rectStart, rectEnd, maxpos;
4439 if (nArgs < 1 || nArgs > 3) {
4440 return wrongNArgsErr(errMsg);
4443 if (argList[0].tag != INT_TAG
4444 || !RangesetLabelOK(argList[0].val.n)) {
4445 M_FAILURE("First parameter is an invalid rangeset label in %s");
4448 if (rangesetTable == NULL) {
4449 M_FAILURE("Rangeset does not exist in %s");
4452 targetRangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4453 if (targetRangeset == NULL) {
4454 M_FAILURE("Rangeset does not exist in %s");
4457 if (nArgs == 1) {
4458 /* remove current selection in this window */
4459 if (!BufGetSelectionPos(buffer, &start, &end, &isRect, &rectStart, &rectEnd)
4460 || isRect) {
4461 M_FAILURE("Selection missing or rectangular in call to %s");
4463 RangesetRemoveBetween(targetRangeset, start, end);
4466 if (nArgs == 2) {
4467 /* remove ranges taken from a second set */
4468 if (argList[1].tag != INT_TAG
4469 || !RangesetLabelOK(argList[1].val.n)) {
4470 M_FAILURE("Second parameter is an invalid rangeset label in %s");
4473 sourceRangeset = RangesetFetch(rangesetTable, argList[1].val.n);
4474 if (sourceRangeset == NULL) {
4475 M_FAILURE("Second rangeset does not exist in %s");
4477 RangesetRemove(targetRangeset, sourceRangeset);
4480 if (nArgs == 3) {
4481 /* remove a range bounded by the start and end positions in $2, $3 */
4482 if (!readIntArg(argList[1], &start, errMsg))
4483 return False;
4484 if (!readIntArg(argList[2], &end, errMsg))
4485 return False;
4487 /* make sure range is in order and fits buffer size */
4488 maxpos = buffer->gapEnd - buffer->gapStart + buffer->length;
4489 if (start < 0) start = 0;
4490 if (start > maxpos) start = maxpos;
4491 if (end < 0) end = 0;
4492 if (end > maxpos) end = maxpos;
4493 if (start > end) {int temp = start; start = end; end = temp;}
4495 RangesetRemoveBetween(targetRangeset, start, end);
4498 /* set up result */
4499 result->tag = NO_TAG;
4500 return True;
4505 ** Built-in macro subroutine to invert a range set. Argument is $1: range set
4506 ** label (one alphabetic character). Returns nothing. Fails if range set
4507 ** undefined.
4510 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
4511 DataValue *result, char **errMsg)
4514 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4515 Rangeset *rangeset;
4517 if (nArgs != 1)
4518 return wrongNArgsErr(errMsg);
4520 if (argList[0].tag != INT_TAG
4521 || !RangesetLabelOK(argList[0].val.n)) {
4522 M_FAILURE("First parameter is an invalid rangeset label in %s");
4525 if (rangesetTable == NULL) {
4526 M_FAILURE("Rangeset does not exist in %s");
4529 rangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4530 if (rangeset == NULL) {
4531 M_FAILURE("Rangeset does not exist in %s");
4534 if (RangesetInverse(rangeset) < 0) {
4535 M_FAILURE("Problem inverting rangeset in %s");
4538 /* set up result */
4539 result->tag = NO_TAG;
4540 return True;
4545 ** Built-in macro subroutine for finding out info about a rangeset. Takes one
4546 ** argument of a rangeset label. Returns an array with the following keys:
4547 ** defined, count, color, mode.
4550 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
4551 DataValue *result, char **errMsg)
4553 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4554 Rangeset *rangeset = NULL;
4555 unsigned char label;
4556 int count, defined;
4557 char *color, *mode;
4558 DataValue element;
4560 static char *definedIndex = "\001defined";
4561 static char *countIndex = "\001count";
4562 static char *colorIndex = "\001color";
4563 static char *modeIndex = "\001mode";
4565 if (nArgs != 1)
4566 return wrongNArgsErr(errMsg);
4568 if (argList[0].tag != INT_TAG
4569 || !RangesetLabelOK(argList[0].val.n)) {
4570 M_FAILURE("First parameter is an invalid rangeset label in %s");
4573 label = argList[0].val.n;
4575 if (rangesetTable != NULL) {
4576 rangeset = RangesetFetch(rangesetTable, label);
4579 RangesetGetInfo(rangeset, &defined, &label, &count, &color, &mode);
4581 /* set up result */
4582 result->tag = ARRAY_TAG;
4583 result->val.arrayPtr = ArrayNew();
4585 element.tag = INT_TAG;
4586 element.val.n = defined;
4587 if (!ArrayInsert(result, definedIndex+1, &element))
4588 M_FAILURE("Failed to insert array element \"defined\" in %s");
4590 element.tag = INT_TAG;
4591 element.val.n = count;
4592 if (!ArrayInsert(result, countIndex+1, &element))
4593 M_FAILURE("Failed to insert array element \"count\" in %s");
4595 element.tag = STRING_TAG;
4596 element.val.str = AllocString(strlen(color) + 1);
4597 if (element.val.str == NULL)
4598 M_FAILURE("Failed to allocate array value \"color\" in %s");
4599 strcpy(element.val.str, color);
4600 if (!ArrayInsert(result, colorIndex+1, &element))
4601 M_FAILURE("Failed to insert array element \"color\" in %s");
4603 element.tag = STRING_TAG;
4604 element.val.str = AllocString(strlen(mode) + 1);
4605 if (element.val.str == NULL)
4606 M_FAILURE("Failed to allocate array value \"mode\" in %s");
4607 strcpy(element.val.str, mode);
4608 if (!ArrayInsert(result, modeIndex+1, &element))
4609 M_FAILURE("Failed to insert array element \"mode\" in %s");
4611 return True;
4615 ** Built-in macro subroutine for finding the extent of a range in a set.
4616 ** If only one parameter is supplied, use the spanning range of all
4617 ** ranges, otherwise select the individual range specified. Returns
4618 ** an array with the keys "start" and "end" and values
4621 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
4622 DataValue *result, char **errMsg)
4624 textBuffer *buffer = window->buffer;
4625 RangesetTable *rangesetTable = buffer->rangesetTable;
4626 Rangeset *rangeset;
4627 int start, end, dummy, rangeIndex, ok;
4628 DataValue element;
4630 static char *startIndex = "\001start";
4631 static char *endIndex = "\001end";
4633 if (nArgs < 1 || nArgs > 2) {
4634 return wrongNArgsErr(errMsg);
4637 if (argList[0].tag != INT_TAG
4638 || !RangesetLabelOK(argList[0].val.n)) {
4639 M_FAILURE("First parameter is an invalid rangeset label in %s");
4642 if (rangesetTable == NULL) {
4643 M_FAILURE("Rangeset does not exist in %s");
4646 ok = False;
4647 rangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4648 if (rangeset != NULL) {
4649 if (nArgs == 1) {
4650 rangeIndex = RangesetGetNRanges(rangeset) - 1;
4651 ok = RangesetFindRangeNo(rangeset, 0, &start, &dummy);
4652 ok &= RangesetFindRangeNo(rangeset, rangeIndex, &dummy, &end);
4653 rangeIndex = -1;
4655 else if (nArgs == 2) {
4656 if (!readIntArg(argList[1], &rangeIndex, errMsg)) {
4657 return False;
4659 ok = RangesetFindRangeNo(rangeset, rangeIndex-1, &start, &end);
4663 /* set up result */
4664 result->tag = ARRAY_TAG;
4665 result->val.arrayPtr = ArrayNew();
4667 if (!ok)
4668 return True;
4670 element.tag = INT_TAG;
4671 element.val.n = start;
4672 if (!ArrayInsert(result, startIndex+1, &element))
4673 M_FAILURE("Failed to insert array element \"start\" in %s");
4675 element.tag = INT_TAG;
4676 element.val.n = end;
4677 if (!ArrayInsert(result, endIndex+1, &element))
4678 M_FAILURE("Failed to insert array element \"end\" in %s");
4680 return True;
4684 ** Built-in macro subroutine for checking a position against a range. If only
4685 ** one parameter is supplied, the current cursor position is used. Returns
4686 ** false (zero) if not in a range, range index (1-based) if in a range;
4687 ** fails if parameters were bad.
4690 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
4691 int nArgs, DataValue *result, char **errMsg)
4693 textBuffer *buffer = window->buffer;
4694 RangesetTable *rangesetTable = buffer->rangesetTable;
4695 Rangeset *rangeset;
4696 int pos, rangeIndex, maxpos;
4698 if (nArgs < 1 || nArgs > 2) {
4699 return wrongNArgsErr(errMsg);
4702 if (argList[0].tag != INT_TAG
4703 || !RangesetLabelOK(argList[0].val.n)) {
4704 M_FAILURE("First parameter is an invalid rangeset label in %s");
4707 if (rangesetTable == NULL) {
4708 M_FAILURE("Rangeset does not exist in %s");
4711 rangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4712 if (rangeset == NULL) {
4713 M_FAILURE("Rangeset does not exist in %s");
4716 if (nArgs == 1) {
4717 pos = TextGetCursorPos(window->lastFocus);
4719 else if (nArgs == 2) {
4720 if (!readIntArg(argList[1], &pos, errMsg))
4721 return False;
4724 maxpos = buffer->gapEnd - buffer->gapStart + buffer->length;
4725 if (pos < 0 || pos > maxpos) {
4726 rangeIndex = 0;
4728 else {
4729 rangeIndex = RangesetFindRangeOfPos(rangeset, pos, False) + 1;
4732 /* set up result */
4733 result->tag = INT_TAG;
4734 result->val.n = rangeIndex;
4735 return True;
4739 ** Set the color of a range set's ranges. it is ignored if the color cannot be
4740 ** found/applied. If no color is applied, any current color is removed. Returns
4741 ** true if the rangeset is valid.
4744 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
4745 int nArgs, DataValue *result, char **errMsg)
4747 textBuffer *buffer = window->buffer;
4748 RangesetTable *rangesetTable = buffer->rangesetTable;
4749 Rangeset *rangeset;
4750 char *color_name;
4752 if (nArgs != 2) {
4753 return wrongNArgsErr(errMsg);
4756 if (argList[0].tag != INT_TAG
4757 || !RangesetLabelOK(argList[0].val.n)) {
4758 M_FAILURE("First parameter is an invalid rangeset label in %s");
4761 if (rangesetTable == NULL) {
4762 M_FAILURE("Rangeset does not exist in %s");
4765 rangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4766 if (rangeset == NULL) {
4767 M_FAILURE("Rangeset does not exist in %s");
4770 color_name = "";
4771 if (rangeset != NULL) {
4772 if (argList[1].tag != STRING_TAG) {
4773 M_FAILURE("Second parameter is not a color name string in %s");
4775 color_name = argList[1].val.str;
4778 RangesetAssignColorName(rangeset, color_name);
4780 /* set up result */
4781 result->tag = NO_TAG;
4782 return True;
4786 ** Change a range's modification response. Returns true if the rangeset is
4787 ** valid and the response type name is valid.
4790 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
4791 int nArgs, DataValue *result, char **errMsg)
4793 textBuffer *buffer = window->buffer;
4794 RangesetTable *rangesetTable = buffer->rangesetTable;
4795 Rangeset *rangeset;
4796 char *update_fn_name;
4797 int ok;
4799 if (nArgs < 1 || nArgs > 2) {
4800 return wrongNArgsErr(errMsg);
4803 if (argList[0].tag != INT_TAG
4804 || !RangesetLabelOK(argList[0].val.n)) {
4805 M_FAILURE("First parameter is an invalid rangeset label in %s");
4808 if (rangesetTable == NULL) {
4809 M_FAILURE("Rangeset does not exist in %s");
4812 rangeset = RangesetFetch(rangesetTable, argList[0].val.n);
4813 if (rangeset == NULL) {
4814 M_FAILURE("Rangeset does not exist in %s");
4817 update_fn_name = "";
4818 if (rangeset != NULL) {
4819 if (nArgs == 2) {
4820 if (argList[1].tag != STRING_TAG) {
4821 M_FAILURE("Second parameter is not a string in %s");
4823 update_fn_name = argList[1].val.str;
4827 ok = RangesetChangeModifyResponse(rangeset, update_fn_name);
4829 if (!ok) {
4830 M_FAILURE("Second parameter is not a valid mode in %s");
4833 /* set up result */
4834 result->tag = NO_TAG;
4835 return True;
4838 /* -------------------------------------------------------------------------- */
4842 ** Routines to get details directly from the window.
4846 ** Returns an array containing information about the style of position $1
4847 ** or name $1.
4848 ** ["style"] Name of style
4849 ** ["color"] Color of style
4850 ** ["rgb"] RGB representation of color of style
4851 ** ["bold"] '1' if style is bold, '0' otherwise
4852 ** ["italic"] '1' if style is italic, '0' otherwise
4853 ** ["background"] Background color of style if specified
4854 ** ["back_rgb"] RGB representation of background color of style
4857 static int getStyleMS(WindowInfo *window, DataValue *argList, int nArgs,
4858 DataValue *result, char **errMsg)
4860 int styleCode=0;
4861 char *styleName;
4863 DataValue DV;
4865 char colorValue[20];
4866 int r, g, b;
4868 /* Validate number of arguments */
4869 if (nArgs != 1) {
4870 return wrongNArgsErr(errMsg);
4873 /* Prepare result */
4874 result->tag = ARRAY_TAG;
4875 result->val.arrayPtr = NULL;
4877 /* Convert argument to whatever its type is */
4878 if (argList[0].tag == STRING_TAG) {
4879 styleName = argList[0].val.str;
4880 if (!NamedStyleExists(styleName)) {
4881 /* if the given name is invalid we just return an empty array. */
4882 return True;
4885 else {
4886 int cursorPos;
4887 textBuffer *buf = window->buffer;
4889 if (!readIntArg(argList[0], &cursorPos, errMsg)) {
4890 return False;
4893 /* Verify sane cursor position */
4894 if ((cursorPos < 0) || (cursorPos >= buf->length))
4896 /* If the position is not legal, we cannot guess anything about
4897 the style, so we return an empty array. */
4898 return True;
4901 /* Determine style name */
4902 styleCode = HighlightCodeOfPos(window, cursorPos);
4903 if (styleCode == 0) {
4904 /* if there is no style we just return an empty array. */
4905 return True;
4907 styleName = AllocStringCpy(HighlightStyleOfCode(window, styleCode));
4910 /* initialize array */
4911 result->val.arrayPtr = ArrayNew();
4913 /* the following array entries will be strings */
4914 DV.tag = STRING_TAG;
4916 /* insert style name */
4917 DV.val.str = styleName;
4918 M_STR_ALLOC_ASSERT(DV);
4919 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
4920 M_ARRAY_INSERT_FAILURE();
4923 /* insert color name */
4924 DV.val.str = AllocStringCpy(ColorOfNamedStyle(styleName));
4925 M_STR_ALLOC_ASSERT(DV);
4926 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &DV)) {
4927 M_ARRAY_INSERT_FAILURE();
4930 /* Prepare array element for color value */
4931 HighlightColorValueOfCode(window, styleCode, &r, &g, &b);
4932 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
4933 DV.val.str = AllocStringCpy(colorValue);
4934 M_STR_ALLOC_ASSERT(DV);
4935 if (!ArrayInsert(result, PERM_ALLOC_STR("rgb"), &DV)) {
4936 M_ARRAY_INSERT_FAILURE();
4939 /* Prepare array element for background color name */
4940 DV.val.str = AllocStringCpy(BgColorOfNamedStyle(styleName));
4941 M_STR_ALLOC_ASSERT(DV);
4942 if (!ArrayInsert(result, PERM_ALLOC_STR("background"), &DV)) {
4943 M_ARRAY_INSERT_FAILURE();
4946 /* Prepare array element for background color value */
4947 GetHighlightBGColorOfCode(window, styleCode,&r,&g,&b);
4948 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
4949 DV.val.str = AllocStringCpy(colorValue);
4950 M_STR_ALLOC_ASSERT(DV);
4951 if (!ArrayInsert(result, PERM_ALLOC_STR("back_rgb"), &DV)) {
4952 M_ARRAY_INSERT_FAILURE();
4955 /* the following array entries will be integers */
4956 DV.tag = INT_TAG;
4958 /* Put boldness value in array */
4959 DV.val.n = FontOfNamedStyleIsBold(styleName);
4960 if (!ArrayInsert(result, PERM_ALLOC_STR("bold"), &DV)) {
4961 M_ARRAY_INSERT_FAILURE();
4964 /* Put italicity value in array */
4965 DV.val.n = FontOfNamedStyleIsItalic(styleName);
4966 if (!ArrayInsert(result, PERM_ALLOC_STR("italic"), &DV)) {
4967 M_ARRAY_INSERT_FAILURE();
4970 return True;
4974 ** Returns an array containing information about a highligting pattern. The
4975 ** single parameter contains the position this information is requested for.
4976 ** The returned array looks like this:
4977 ** ["pattern"] Name of pattern
4978 ** ["style"] Name of style
4979 ** ["extension"] Distance this style continues
4981 ** A second option is to call get_pattern() with a pattern name, to learn
4982 ** about a patterns style. In this case, the 'extension' element is not set.
4984 static int getPatternMS(WindowInfo *window, DataValue *argList, int nArgs,
4985 DataValue *result, char **errMsg)
4987 int cursorPos = -1;
4988 textBuffer *buffer = window->buffer;
4990 int styleCode = 0;
4991 char* styleName = NULL;
4992 char* patternName = NULL;
4993 highlightPattern* pattern = NULL;
4995 Boolean extensionRequired = True;
4996 DataValue DV;
4997 int checkCode;
4999 /* Begin of building the result. */
5000 result->tag = ARRAY_TAG;
5001 result->val.arrayPtr = NULL;
5003 /* Validate number of arguments */
5004 if (nArgs != 1)
5006 return wrongNArgsErr(errMsg);
5009 /* Convert argument to whatever its type is and set styleName and
5010 patternName accordingly. */
5011 if (argList[0].tag == INT_TAG)
5013 /* The most straightforward case: Get a pattern, style and extension
5014 for a cursor position. */
5015 if (!readIntArg(argList[0], &cursorPos, errMsg))
5017 return False;
5020 /* Verify sane cursor position
5021 * You would expect that buffer->length would be among the sane
5022 * positions, but we have n characters and n+1 cursor positions. */
5023 if ((cursorPos < 0) || (cursorPos >= buffer->length))
5025 /* If the position is not legal, we cannot guess anything about
5026 the style, so we return an empty array. */
5027 return True;
5030 /* Determine style name */
5031 styleCode = HighlightCodeOfPos(window, cursorPos);
5032 if (styleCode == 0)
5034 /* if there is no style we just return an empty array. */
5035 return True;
5038 styleName = AllocStringCpy(HighlightStyleOfCode(window, styleCode));
5039 patternName = AllocStringCpy(HighlightNameOfCode(window, styleCode));
5040 } else if (argList[0].tag == STRING_TAG)
5042 /* This is used to learn about a pattern's style. */
5043 patternName = argList[0].val.str;
5044 pattern = FindPatternOfWindow(window, patternName);
5045 if (pattern == NULL)
5047 /* The pattern's name is unknown. */
5048 return True;
5050 styleName = AllocStringCpy(pattern->style);
5051 extensionRequired = False; /* no position -> no extension */
5052 } else
5054 *errMsg = "Position or pattern name string expected as parameter to %s";
5055 return False;
5058 /* initialize array */
5059 result->val.arrayPtr = ArrayNew();
5061 /* the following array entries will be strings */
5062 DV.tag = STRING_TAG;
5064 /* insert pattern name */
5065 DV.val.str = patternName;
5066 M_STR_ALLOC_ASSERT(DV);
5067 if (!ArrayInsert(result, PERM_ALLOC_STR("pattern"), &DV))
5069 M_ARRAY_INSERT_FAILURE();
5072 /* insert style name */
5073 DV.val.str = styleName;
5074 M_STR_ALLOC_ASSERT(DV);
5075 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV))
5077 M_ARRAY_INSERT_FAILURE();
5080 /* the following array entry will be integer */
5081 DV.tag = INT_TAG;
5083 /* insert extent */
5084 if (extensionRequired)
5086 checkCode = 0;
5087 DV.val.n = HighlightLengthOfCodeFromPos(window, cursorPos, &checkCode);
5088 if (!ArrayInsert(result, PERM_ALLOC_STR("extension"), &DV))
5090 M_ARRAY_INSERT_FAILURE();
5094 return True;
5097 static int wrongNArgsErr(char **errMsg)
5099 *errMsg = "Wrong number of arguments to function %s";
5100 return False;
5103 static int tooFewArgsErr(char **errMsg)
5105 *errMsg = "Too few arguments to function %s";
5106 return False;
5110 ** strCaseCmp compares its arguments and returns 0 if the two strings
5111 ** are equal IGNORING case differences. Otherwise returns 1 or -1
5112 ** depending on relative comparison.
5114 static int strCaseCmp(char *str1, char *str2)
5116 char *c1, *c2;
5118 for (c1 = str1, c2 = str2;
5119 (*c1 != '\0' && *c2 != '\0')
5120 && toupper((unsigned char)*c1) == toupper((unsigned char)*c2);
5121 ++c1, ++c2)
5125 if (((unsigned char)toupper((unsigned char)*c1))
5126 > ((unsigned char)toupper((unsigned char)*c2)))
5128 return(1);
5129 } else if (((unsigned char)toupper((unsigned char)*c1))
5130 < ((unsigned char)toupper((unsigned char)*c2)))
5132 return(-1);
5133 } else
5135 return(0);
5140 ** Get an integer value from a tagged DataValue structure. Return True
5141 ** if conversion succeeded, and store result in *result, otherwise
5142 ** return False with an error message in *errMsg.
5144 static int readIntArg(DataValue dv, int *result, char **errMsg)
5146 char *c;
5148 if (dv.tag == INT_TAG) {
5149 *result = dv.val.n;
5150 return True;
5151 } else if (dv.tag == STRING_TAG) {
5152 for (c=dv.val.str; *c != '\0'; c++) {
5153 if (!(isdigit((unsigned char)*c) || *c == ' ' || *c == '\t')) {
5154 goto typeError;
5157 sscanf(dv.val.str, "%d", result);
5158 return True;
5161 typeError:
5162 *errMsg = "%s called with non-integer argument";
5163 return False;
5167 ** Get an string value from a tagged DataValue structure. Return True
5168 ** if conversion succeeded, and store result in *result, otherwise
5169 ** return False with an error message in *errMsg. If an integer value
5170 ** is converted, write the string in the space provided by "stringStorage",
5171 ** which must be large enough to handle ints of the maximum size.
5173 static int readStringArg(DataValue dv, char **result, char *stringStorage,
5174 char **errMsg)
5176 if (dv.tag == STRING_TAG) {
5177 *result = dv.val.str;
5178 return True;
5179 } else if (dv.tag == INT_TAG) {
5180 sprintf(stringStorage, "%d", dv.val.n);
5181 *result = stringStorage;
5182 return True;
5184 *errMsg = "%s called with unknown object";
5185 return False;