Commit SF patch #970491: Forbid deletion of the "Plain" Text
[nedit.git] / source / macro.c
blob94e78abeba5a47f228ace4f75a3d2bcbbeb955c4
1 static const char CVSID[] = "$Id: macro.c,v 1.86 2004/06/10 17:01:26 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.rep) { *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 rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
364 DataValue *result, char **errMsg);
365 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
366 DataValue *result, char **errMsg);
367 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
368 DataValue *result, char **errMsg);
369 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
370 DataValue *result, char **errMsg);
371 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
372 DataValue *result, char **errMsg);
373 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
374 DataValue *result, char **errMsg);
375 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
376 int nArgs, DataValue *result, char **errMsg);
377 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
378 int nArgs, DataValue *result, char **errMsg);
379 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
380 int nArgs, DataValue *result, char **errMsg);
381 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
382 int nArgs, DataValue *result, char **errMsg);
384 static int fillPatternResult(DataValue *result, char **errMsg, WindowInfo *window,
385 char *patternName, Boolean preallocatedPatternName, Boolean includeName,
386 char *styleName, int bufferPos);
387 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
388 DataValue *result, char **errMsg);
389 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
390 DataValue *result, char **errMsg);
392 static int fillStyleResult(DataValue *result, char **errMsg,
393 WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
394 Boolean includeName, int patCode, int bufferPos);
395 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
396 DataValue *result, char **errMsg);
397 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
398 DataValue *result, char **errMsg);
400 /* Built-in subroutines and variables for the macro language */
401 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS,
402 dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS,
403 setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS,
404 searchStringMS, substringMS, replaceSubstringMS, readFileMS,
405 writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS,
406 replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS,
407 shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS,
408 tolowerMS, listDialogMS, getenvMS,
409 stringCompareMS, splitMS, calltipMS, killCalltipMS,
410 /* DISABLED for 5.4 setBacklightStringMS,*/
411 rangesetCreateMS, rangesetDestroyMS,
412 rangesetAddMS, rangesetSubtractMS, rangesetInvertMS,
413 rangesetInfoMS, rangesetRangeMS, rangesetIncludesPosMS,
414 rangesetSetColorMS, rangesetSetNameMS, rangesetSetModeMS,
415 rangesetGetByNameMS,
416 getPatternByNameMS, getPatternAtPosMS,
417 getStyleByNameMS, getStyleAtPosMS
419 #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs)
420 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print",
421 "dialog", "string_dialog", "replace_range", "replace_selection",
422 "set_cursor_pos", "get_character", "min", "max", "search",
423 "search_string", "substring", "replace_substring", "read_file",
424 "write_file", "append_file", "beep", "get_selection", "valid_number",
425 "replace_in_string", "select", "select_rectangle", "focus_window",
426 "shell_command", "string_to_clipboard", "clipboard_to_string",
427 "toupper", "tolower", "list_dialog", "getenv",
428 "string_compare", "split", "calltip", "kill_calltip",
429 /* DISABLED for 5.4 "set_backlight_string", */
430 "rangeset_create", "rangeset_destroy",
431 "rangeset_add", "rangeset_subtract", "rangeset_invert",
432 "rangeset_info", "rangeset_range", "rangeset_includes",
433 "rangeset_set_color", "rangeset_set_name", "rangeset_set_mode",
434 "rangeset_get_by_name",
435 "get_pattern_by_name", "get_pattern_at_pos",
436 "get_style_by_name", "get_style_at_pos"
438 static BuiltInSubr SpecialVars[] = {cursorMV, lineMV, columnMV,
439 fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV,
440 selectionLeftMV, selectionRightMV, wrapMarginMV, tabDistMV,
441 emTabDistMV, useTabsMV, languageModeMV, modifiedMV,
442 statisticsLineMV, incSearchLineMV, showLineNumbersMV,
443 autoIndentMV, wrapTextMV, highlightSyntaxMV,
444 makeBackupCopyMV, incBackupMV, showMatchingMV,
445 overTypeModeMV, readOnlyMV, lockedMV, fileFormatMV,
446 fontNameMV, fontNameItalicMV,
447 fontNameBoldMV, fontNameBoldItalicMV, subscriptSepMV,
448 minFontWidthMV, maxFontWidthMV, topLineMV, numDisplayLinesMV,
449 displayWidthMV, activePaneMV, nPanesMV, emptyArrayMV,
450 serverNameMV, calltipIDMV,
451 /* DISABLED for 5.4 backlightStringMV, */
452 rangesetListMV
454 #define N_SPECIAL_VARS (sizeof SpecialVars/sizeof *SpecialVars)
455 static const char *SpecialVarNames[N_SPECIAL_VARS] = {"$cursor", "$line", "$column",
456 "$file_name", "$file_path", "$text_length", "$selection_start",
457 "$selection_end", "$selection_left", "$selection_right",
458 "$wrap_margin", "$tab_dist", "$em_tab_dist", "$use_tabs",
459 "$language_mode", "$modified",
460 "$statistics_line", "$incremental_search_line", "$show_line_numbers",
461 "$auto_indent", "$wrap_text", "$highlight_syntax",
462 "$make_backup_copy", "$incremental_backup", "$show_matching",
463 "$overtype_mode", "$read_only", "$locked", "$file_format",
464 "$font_name", "$font_name_italic",
465 "$font_name_bold", "$font_name_bold_italic", "$sub_sep",
466 "$min_font_width", "$max_font_width", "$top_line", "$n_display_lines",
467 "$display_width", "$active_pane", "$n_panes", "$empty_array",
468 "$server_name", "$calltip_ID",
469 /* DISABLED for 5.4 "$backlight_string", */
470 "$rangeset_list"
473 /* Global symbols for returning values from built-in functions */
474 #define N_RETURN_GLOBALS 5
475 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS,
476 SHELL_CMD_STATUS, LIST_DIALOG_BUTTON};
477 static const char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button",
478 "$search_end", "$read_status", "$shell_cmd_status",
479 "$list_dialog_button"};
480 static Symbol *ReturnGlobals[N_RETURN_GLOBALS];
482 /* List of actions not useful when learning a macro sequence (also see below) */
483 static char* IgnoredActions[] = {"focusIn", "focusOut"};
485 /* List of actions intended to be attached to mouse buttons, which the user
486 must be warned can't be recorded in a learn/replay sequence */
487 static const char* MouseActions[] = {"grab_focus", "extend_adjust", "extend_start",
488 "extend_end", "secondary_or_drag_adjust", "secondary_adjust",
489 "secondary_or_drag_start", "secondary_start", "move_destination",
490 "move_to", "move_to_or_end_drag", "copy_to", "copy_to_or_end_drag",
491 "exchange", "process_bdrag", "mouse_pan"};
493 /* List of actions to not record because they
494 generate further actions, more suitable for recording */
495 static const char* RedundantActions[] = {"open_dialog", "save_as_dialog",
496 "revert_to_saved_dialog", "include_file_dialog", "load_macro_file_dialog",
497 "load_tags_file_dialog", "find_dialog", "replace_dialog",
498 "goto_line_number_dialog", "mark_dialog", "goto_mark_dialog",
499 "control_code_dialog", "filter_selection_dialog", "execute_command_dialog",
500 "repeat_dialog", "start_incremental_find"};
502 /* The last command executed (used by the Repeat command) */
503 static char *LastCommand = NULL;
505 /* The current macro to execute on Replay command */
506 static char *ReplayMacro = NULL;
508 /* Buffer where macro commands are recorded in Learn mode */
509 static textBuffer *MacroRecordBuf = NULL;
511 /* Action Hook id for recording actions for Learn mode */
512 static XtActionHookId MacroRecordActionHook = 0;
514 /* Window where macro recording is taking place */
515 static WindowInfo *MacroRecordWindow = NULL;
517 /* Arrays for translating escape characters in escapeStringChars */
518 static char ReplaceChars[] = "\\\"ntbrfav";
519 static char EscapeChars[] = "\\\"\n\t\b\r\f\a\v";
522 ** Install built-in macro subroutines and special variables for accessing
523 ** editor information
525 void RegisterMacroSubroutines(void)
527 static DataValue subrPtr = {NO_TAG, {0}}, noValue = {NO_TAG, {0}};
528 unsigned i;
530 /* Install symbols for built-in routines and variables, with pointers
531 to the appropriate c routines to do the work */
532 for (i=0; i<N_MACRO_SUBRS; i++) {
533 subrPtr.val.subr = MacroSubrs[i];
534 InstallSymbol(MacroSubrNames[i], C_FUNCTION_SYM, subrPtr);
536 for (i=0; i<N_SPECIAL_VARS; i++) {
537 subrPtr.val.subr = SpecialVars[i];
538 InstallSymbol(SpecialVarNames[i], PROC_VALUE_SYM, subrPtr);
541 /* Define global variables used for return values, remember their
542 locations so they can be set without a LookupSymbol call */
543 for (i=0; i<N_RETURN_GLOBALS; i++)
544 ReturnGlobals[i] = InstallSymbol(ReturnGlobalNames[i], GLOBAL_SYM,
545 noValue);
548 #define MAX_LEARN_MSG_LEN ((2 * MAX_ACCEL_LEN) + 60)
549 void BeginLearn(WindowInfo *window)
551 WindowInfo *win;
552 XmString s;
553 XmString xmFinish;
554 XmString xmCancel;
555 char *cFinish;
556 char *cCancel;
557 char message[MAX_LEARN_MSG_LEN];
559 /* If we're already in learn mode, return */
560 if (MacroRecordActionHook != 0)
561 return;
563 /* dim the inappropriate menus and items, and undim finish and cancel */
564 for (win=WindowList; win!=NULL; win=win->next) {
565 if (!IsTopDocument(win))
566 continue;
567 XtSetSensitive(win->learnItem, False);
569 SetSensitive(window, window->finishLearnItem, True);
570 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
571 s=XmStringCreateSimple("Cancel Learn"), NULL);
572 XmStringFree(s);
573 SetSensitive(window, window->cancelMacroItem, True);
575 /* Mark the window where learn mode is happening */
576 MacroRecordWindow = window;
578 /* Allocate a text buffer for accumulating the macro strings */
579 MacroRecordBuf = BufCreate();
581 /* Add the action hook for recording the actions */
582 MacroRecordActionHook =
583 XtAppAddActionHook(XtWidgetToApplicationContext(window->shell),
584 learnActionHook, window);
586 /* Extract accelerator texts from menu PushButtons */
587 XtVaGetValues(window->finishLearnItem, XmNacceleratorText, &xmFinish, NULL);
588 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
590 /* Translate Motif strings to char* */
591 cFinish = GetXmStringText(xmFinish);
592 cCancel = GetXmStringText(xmCancel);
594 /* Free Motif Strings */
595 XmStringFree(xmFinish);
596 XmStringFree(xmCancel);
598 /* Create message */
599 if (cFinish[0] == '\0') {
600 if (cCancel[0] == '\0') {
601 strncpy(message, "Learn Mode -- Use menu to finish or cancel",
602 MAX_LEARN_MSG_LEN);
603 message[MAX_LEARN_MSG_LEN - 1] = '\0';
605 else {
606 sprintf(message,
607 "Learn Mode -- Use menu to finish, press %s to cancel",
608 cCancel);
611 else {
612 if (cCancel[0] == '\0') {
613 sprintf(message,
614 "Learn Mode -- Press %s to finish, use menu to cancel",
615 cFinish);
618 else {
619 sprintf(message,
620 "Learn Mode -- Press %s to finish, %s to cancel",
621 cFinish,
622 cCancel);
626 /* Free C-strings */
627 XtFree(cFinish);
628 XtFree(cCancel);
630 /* Put up the learn-mode banner */
631 SetModeMessage(window, message);
634 void AddLastCommandActionHook(XtAppContext context)
636 XtAppAddActionHook(context, lastActionHook, NULL);
639 void FinishLearn(void)
641 WindowInfo *win;
643 /* If we're not in learn mode, return */
644 if (MacroRecordActionHook == 0)
645 return;
647 /* Remove the action hook */
648 XtRemoveActionHook(MacroRecordActionHook);
649 MacroRecordActionHook = 0;
651 /* Free the old learn/replay sequence */
652 if (ReplayMacro != NULL)
653 XtFree(ReplayMacro);
655 /* Store the finished action for the replay menu item */
656 ReplayMacro = BufGetAll(MacroRecordBuf);
658 /* Free the buffer used to accumulate the macro sequence */
659 BufFree(MacroRecordBuf);
661 /* Undim the menu items dimmed during learn */
662 for (win=WindowList; win!=NULL; win=win->next) {
663 if (!IsTopDocument(win))
664 continue;
665 XtSetSensitive(win->learnItem, True);
667 if (IsTopDocument(MacroRecordWindow)) {
668 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
669 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
672 /* Undim the replay and paste-macro buttons */
673 for (win=WindowList; win!=NULL; win=win->next) {
674 if (!IsTopDocument(win))
675 continue;
676 XtSetSensitive(win->replayItem, True);
678 DimPasteReplayBtns(True);
680 /* Clear learn-mode banner */
681 ClearModeMessage(MacroRecordWindow);
685 ** Cancel Learn mode, or macro execution (they're bound to the same menu item)
687 void CancelMacroOrLearn(WindowInfo *window)
689 if (MacroRecordActionHook != 0)
690 cancelLearn();
691 else if (window->macroCmdData != NULL)
692 AbortMacroCommand(window);
695 static void cancelLearn(void)
697 WindowInfo *win;
699 /* If we're not in learn mode, return */
700 if (MacroRecordActionHook == 0)
701 return;
703 /* Remove the action hook */
704 XtRemoveActionHook(MacroRecordActionHook);
705 MacroRecordActionHook = 0;
707 /* Free the macro under construction */
708 BufFree(MacroRecordBuf);
710 /* Undim the menu items dimmed during learn */
711 for (win=WindowList; win!=NULL; win=win->next) {
712 if (!IsTopDocument(win))
713 continue;
714 XtSetSensitive(win->learnItem, True);
716 if (IsTopDocument(MacroRecordWindow)) {
717 XtSetSensitive(MacroRecordWindow->finishLearnItem, False);
718 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False);
721 /* Clear learn-mode banner */
722 ClearModeMessage(MacroRecordWindow);
726 ** Execute the learn/replay sequence stored in "window"
728 void Replay(WindowInfo *window)
730 Program *prog;
731 char *errMsg, *stoppedAt;
733 /* Verify that a replay macro exists and it's not empty and that */
734 /* we're not already running a macro */
735 if (ReplayMacro != NULL &&
736 ReplayMacro[0] != 0 &&
737 window->macroCmdData == NULL) {
738 /* Parse the replay macro (it's stored in text form) and compile it into
739 an executable program "prog" */
740 prog = ParseMacro(ReplayMacro, &errMsg, &stoppedAt);
741 if (prog == NULL) {
742 fprintf(stderr,
743 "NEdit internal error, learn/replay macro syntax error: %s\n",
744 errMsg);
745 return;
748 /* run the executable program */
749 runMacro(window, prog);
754 ** Read the initial NEdit macro file if one exists.
756 void ReadMacroInitFile(WindowInfo *window)
758 const char* autoloadName = GetRCFileName(AUTOLOAD_NM);
759 static int initFileLoaded = False;
761 /* GetRCFileName() might return NULL if an error occurs during
762 creation of the preference file directory. */
763 if (autoloadName != NULL && !initFileLoaded)
765 ReadMacroFile(window, autoloadName, False);
766 initFileLoaded = True;
771 ** Read an NEdit macro file. Extends the syntax of the macro parser with
772 ** define keyword, and allows intermixing of defines with immediate actions.
774 int ReadMacroFile(WindowInfo *window, const char *fileName, int warnNotExist)
776 int result;
777 char *fileString;
779 fileString = ReadAnyTextFile(fileName);
780 if (fileString == NULL){
781 if (errno != ENOENT || warnNotExist)
783 DialogF(DF_ERR, window->shell, 1, "Read Macro",
784 "Error reading macro file %s: %s", "dismiss", fileName,
785 #ifdef VMS
786 strerror(errno, vaxc$errno));
787 #else
788 strerror(errno));
789 #endif
791 return False;
794 /* Parse fileString */
795 result = readCheckMacroString(window->shell, fileString, window, fileName,
796 NULL);
797 XtFree(fileString);
798 return result;
802 ** Parse and execute a macro string including macro definitions. Report
803 ** parsing errors in a dialog posted over window->shell.
805 int ReadMacroString(WindowInfo *window, char *string, const char *errIn)
807 return readCheckMacroString(window->shell, string, window, errIn, NULL);
811 ** Check a macro string containing definitions for errors. Returns True
812 ** if macro compiled successfully. Returns False and puts up
813 ** a dialog explaining if macro did not compile successfully.
815 int CheckMacroString(Widget dialogParent, char *string, const char *errIn,
816 char **errPos)
818 return readCheckMacroString(dialogParent, string, NULL, errIn, errPos);
822 ** Parse and optionally execute a macro string including macro definitions.
823 ** Report parsing errors in a dialog posted over dialogParent, using the
824 ** string errIn to identify the entity being parsed (filename, macro string,
825 ** etc.). If runWindow is specified, runs the macro against the window. If
826 ** runWindow is passed as NULL, does parse only. If errPos is non-null,
827 ** returns a pointer to the error location in the string.
829 static int readCheckMacroString(Widget dialogParent, char *string,
830 WindowInfo *runWindow, const char *errIn, char **errPos)
832 char *stoppedAt, *inPtr, *namePtr, *errMsg;
833 char subrName[MAX_SYM_LEN];
834 Program *prog;
835 Symbol *sym;
836 DataValue subrPtr;
838 inPtr = string;
839 while (*inPtr != '\0') {
841 /* skip over white space and comments */
842 while (*inPtr==' ' || *inPtr=='\t' || *inPtr=='\n'|| *inPtr=='#') {
843 if (*inPtr == '#')
844 while (*inPtr != '\n' && *inPtr != '\0') inPtr++;
845 else
846 inPtr++;
848 if (*inPtr == '\0')
849 break;
851 /* look for define keyword, and compile and store defined routines */
852 if (!strncmp(inPtr, "define", 6) && (inPtr[6]==' ' || inPtr[6]=='\t')) {
853 inPtr += 6;
854 inPtr += strspn(inPtr, " \t\n");
855 namePtr = subrName;
856 while (isalnum((unsigned char)*inPtr) || *inPtr == '_')
857 *namePtr++ = *inPtr++;
858 *namePtr = '\0';
859 inPtr += strspn(inPtr, " \t\n");
860 if (*inPtr != '{') {
861 if (errPos != NULL) *errPos = stoppedAt;
862 return ParseError(dialogParent, string, inPtr,
863 errIn, "expected '{'");
865 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
866 if (prog == NULL) {
867 if (errPos != NULL) *errPos = stoppedAt;
868 return ParseError(dialogParent, string, stoppedAt,
869 errIn, errMsg);
871 if (runWindow != NULL) {
872 sym = LookupSymbol(subrName);
873 if (sym == NULL) {
874 subrPtr.val.prog = prog;
875 subrPtr.tag = NO_TAG;
876 sym = InstallSymbol(subrName, MACRO_FUNCTION_SYM, subrPtr);
877 } else {
878 if (sym->type == MACRO_FUNCTION_SYM)
879 FreeProgram(sym->value.val.prog);
880 else
881 sym->type = MACRO_FUNCTION_SYM;
882 sym->value.val.prog = prog;
885 inPtr = stoppedAt;
887 /* Parse and execute immediate (outside of any define) macro commands
888 and WAIT for them to finish executing before proceeding. Note that
889 the code below is not perfect. If you interleave code blocks with
890 definitions in a file which is loaded from another macro file, it
891 will probably run the code blocks in reverse order! */
892 } else {
893 prog = ParseMacro(inPtr, &errMsg, &stoppedAt);
894 if (prog == NULL) {
895 if (errPos != NULL) *errPos = stoppedAt;
896 return ParseError(dialogParent, string, stoppedAt,
897 errIn, errMsg);
899 if (runWindow != NULL) {
900 XEvent nextEvent;
901 if (runWindow->macroCmdData == NULL) {
902 runMacro(runWindow, prog);
903 while (runWindow->macroCmdData != NULL) {
904 XtAppNextEvent(XtWidgetToApplicationContext(
905 runWindow->shell), &nextEvent);
906 ServerDispatchEvent(&nextEvent);
908 } else
909 RunMacroAsSubrCall(prog);
911 inPtr = stoppedAt;
914 return True;
918 ** Run a pre-compiled macro, changing the interface state to reflect that
919 ** a macro is running, and handling preemption, resumption, and cancellation.
920 ** frees prog when macro execution is complete;
922 static void runMacro(WindowInfo *window, Program *prog)
924 DataValue result;
925 char *errMsg;
926 int stat;
927 macroCmdInfo *cmdData;
928 XmString s;
930 /* If a macro is already running, just call the program as a subroutine,
931 instead of starting a new one, so we don't have to keep a separate
932 context, and the macros will serialize themselves automatically */
933 if (window->macroCmdData != NULL) {
934 RunMacroAsSubrCall(prog);
935 return;
938 /* put up a watch cursor over the waiting window */
939 BeginWait(window->shell);
941 /* enable the cancel menu item */
942 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
943 s=XmStringCreateSimple("Cancel Macro"), NULL);
944 XmStringFree(s);
945 SetSensitive(window, window->cancelMacroItem, True);
947 /* Create a data structure for passing macro execution information around
948 amongst the callback routines which will process i/o and completion */
949 cmdData = (macroCmdInfo *)XtMalloc(sizeof(macroCmdInfo));
950 window->macroCmdData = cmdData;
951 cmdData->bannerIsUp = False;
952 cmdData->closeOnCompletion = False;
953 cmdData->program = prog;
954 cmdData->context = NULL;
955 cmdData->continueWorkProcID = 0;
956 cmdData->dialog = NULL;
958 /* Set up timer proc for putting up banner when macro takes too long */
959 cmdData->bannerTimeoutID = XtAppAddTimeOut(
960 XtWidgetToApplicationContext(window->shell), BANNER_WAIT_TIME,
961 bannerTimeoutProc, window);
963 /* Begin macro execution */
964 stat = ExecuteMacro(window, prog, 0, NULL, &result, &cmdData->context,
965 &errMsg);
967 if (stat == MACRO_ERROR)
969 finishMacroCmdExecution(window);
970 DialogF(DF_ERR, window->shell, 1, "Macro Error",
971 "Error executing macro: %s", "Dismiss", errMsg);
972 return;
975 if (stat == MACRO_DONE) {
976 finishMacroCmdExecution(window);
977 return;
979 if (stat == MACRO_TIME_LIMIT) {
980 ResumeMacroExecution(window);
981 return;
983 /* (stat == MACRO_PREEMPT) Macro was preempted */
987 ** Continue with macro execution after preemption. Called by the routines
988 ** whose actions cause preemption when they have completed their lengthy tasks.
989 ** Re-establishes macro execution work proc. Window must be the window in
990 ** which the macro is executing (the window to which macroCmdData is attached),
991 ** and not the window to which operations are focused.
993 void ResumeMacroExecution(WindowInfo *window)
995 macroCmdInfo *cmdData = (macroCmdInfo *)window->macroCmdData;
997 if (cmdData != NULL)
998 cmdData->continueWorkProcID = XtAppAddWorkProc(
999 XtWidgetToApplicationContext(window->shell),
1000 continueWorkProc, window);
1004 ** Cancel the macro command in progress (user cancellation via GUI)
1006 void AbortMacroCommand(WindowInfo *window)
1008 if (window->macroCmdData == NULL)
1009 return;
1011 /* If there's both a macro and a shell command executing, the shell command
1012 must have been called from the macro. When called from a macro, shell
1013 commands don't put up cancellation controls of their own, but rely
1014 instead on the macro cancellation mechanism (here) */
1015 #ifndef VMS
1016 if (window->shellCmdData != NULL)
1017 AbortShellCommand(window);
1018 #endif
1020 /* Free the continuation */
1021 FreeRestartData(((macroCmdInfo *)window->macroCmdData)->context);
1023 /* Kill the macro command */
1024 finishMacroCmdExecution(window);
1028 ** Call this before closing a window, to clean up macro references to the
1029 ** window, stop any macro which might be running from it, free associated
1030 ** memory, and check that a macro is not attempting to close the window from
1031 ** which it is run. If this is being called from a macro, and the window
1032 ** this routine is examining is the window from which the macro was run, this
1033 ** routine will return False, and the caller must NOT CLOSE THE WINDOW.
1034 ** Instead, empty it and make it Untitled, and let the macro completion
1035 ** process close the window when the macro is finished executing.
1037 int MacroWindowCloseActions(WindowInfo *window)
1039 macroCmdInfo *mcd, *cmdData = window->macroCmdData;
1040 WindowInfo *w;
1042 if (MacroRecordActionHook != 0 && MacroRecordWindow == window) {
1043 FinishLearn();
1046 /* If no macro is executing in the window, allow the close, but check
1047 if macros executing in other windows have it as focus. If so, set
1048 their focus back to the window from which they were originally run */
1049 if (cmdData == NULL) {
1050 for (w=WindowList; w!=NULL; w=w->next) {
1051 mcd = (macroCmdInfo *)w->macroCmdData;
1052 if (w == MacroRunWindow() && MacroFocusWindow() == window)
1053 SetMacroFocusWindow(MacroRunWindow());
1054 else if (mcd != NULL && mcd->context->focusWindow == window)
1055 mcd->context->focusWindow = mcd->context->runWindow;
1057 return True;
1060 /* If the macro currently running (and therefore calling us, because
1061 execution must otherwise return to the main loop to execute any
1062 commands), is running in this window, tell the caller not to close,
1063 and schedule window close on completion of macro */
1064 if (window == MacroRunWindow()) {
1065 cmdData->closeOnCompletion = True;
1066 return False;
1069 /* Free the continuation */
1070 FreeRestartData(cmdData->context);
1072 /* Kill the macro command */
1073 finishMacroCmdExecution(window);
1074 return True;
1078 ** Clean up after the execution of a macro command: free memory, and restore
1079 ** the user interface state.
1081 static void finishMacroCmdExecution(WindowInfo *window)
1083 macroCmdInfo *cmdData = window->macroCmdData;
1084 int closeOnCompletion = cmdData->closeOnCompletion;
1085 XmString s;
1086 XClientMessageEvent event;
1088 /* Cancel pending timeout and work proc */
1089 if (cmdData->bannerTimeoutID != 0)
1090 XtRemoveTimeOut(cmdData->bannerTimeoutID);
1091 if (cmdData->continueWorkProcID != 0)
1092 XtRemoveWorkProc(cmdData->continueWorkProcID);
1094 /* Clean up waiting-for-macro-command-to-complete mode */
1095 EndWait(window->shell);
1096 XtVaSetValues(window->cancelMacroItem, XmNlabelString,
1097 s=XmStringCreateSimple("Cancel Learn"), NULL);
1098 XmStringFree(s);
1099 SetSensitive(window, window->cancelMacroItem, False);
1100 if (cmdData->bannerIsUp)
1101 ClearModeMessage(window);
1103 /* If a dialog was up, get rid of it */
1104 if (cmdData->dialog != NULL)
1105 XtDestroyWidget(XtParent(cmdData->dialog));
1107 /* Free execution information */
1108 FreeProgram(cmdData->program);
1109 XtFree((char *)cmdData);
1110 window->macroCmdData = NULL;
1112 /* If macro closed its own window, window was made empty and untitled,
1113 but close was deferred until completion. This is completion, so if
1114 the window is still empty, do the close */
1115 if (closeOnCompletion && !window->filenameSet && !window->fileChanged) {
1116 CloseWindow(window);
1117 window = NULL;
1120 /* If no other macros are executing, do garbage collection */
1121 SafeGC();
1123 /* In processing the .neditmacro file (and possibly elsewhere), there
1124 is an event loop which waits for macro completion. Send an event
1125 to wake up that loop, otherwise execution will stall until the user
1126 does something to the window. */
1127 if (!closeOnCompletion) {
1128 event.format = 8;
1129 event.type = ClientMessage;
1130 XSendEvent(XtDisplay(window->shell), XtWindow(window->shell), False,
1131 NoEventMask, (XEvent *)&event);
1136 ** Do garbage collection of strings if there are no macros currently
1137 ** executing. NEdit's macro language GC strategy is to call this routine
1138 ** whenever a macro completes. If other macros are still running (preempted
1139 ** or waiting for a shell command or dialog), this does nothing and therefore
1140 ** defers GC to the completion of the last macro out.
1142 void SafeGC(void)
1144 WindowInfo *win;
1146 for (win=WindowList; win!=NULL; win=win->next)
1147 if (win->macroCmdData != NULL || InSmartIndentMacros(win))
1148 return;
1149 GarbageCollectStrings();
1153 ** Executes macro string "macro" using the lastFocus pane in "window".
1154 ** Reports errors via a dialog posted over "window", integrating the name
1155 ** "errInName" into the message to help identify the source of the error.
1157 void DoMacro(WindowInfo *window, const char *macro, const char *errInName)
1159 Program *prog;
1160 char *errMsg, *stoppedAt, *tMacro;
1161 int macroLen;
1163 /* Add a terminating newline (which command line users are likely to omit
1164 since they are typically invoking a single routine) */
1165 macroLen = strlen(macro);
1166 tMacro = XtMalloc(strlen(macro)+2);
1167 strncpy(tMacro, macro, macroLen);
1168 tMacro[macroLen] = '\n';
1169 tMacro[macroLen+1] = '\0';
1171 /* Parse the macro and report errors if it fails */
1172 prog = ParseMacro(tMacro, &errMsg, &stoppedAt);
1173 if (prog == NULL) {
1174 ParseError(window->shell, tMacro, stoppedAt, errInName, errMsg);
1175 XtFree(tMacro);
1176 return;
1178 XtFree(tMacro);
1180 /* run the executable program (prog is freed upon completion) */
1181 runMacro(window, prog);
1185 ** Get the current Learn/Replay macro in text form. Returned string is a
1186 ** pointer to the stored macro and should not be freed by the caller (and
1187 ** will cease to exist when the next replay macro is installed)
1189 char *GetReplayMacro(void)
1191 return ReplayMacro;
1195 ** Present the user a dialog for "Repeat" command
1197 void RepeatDialog(WindowInfo *window)
1199 Widget form, selBox, radioBox, timesForm;
1200 repeatDialog *rd;
1201 Arg selBoxArgs[1];
1202 char *lastCmdLabel, *parenChar;
1203 XmString s1;
1204 int cmdNameLen;
1206 if (LastCommand == NULL)
1208 DialogF(DF_WARN, window->shell, 1, "Repeat Macro",
1209 "No previous commands or learn/\nreplay sequences to repeat",
1210 "Dismiss");
1211 return;
1214 /* Remeber the last command, since the user is allowed to work in the
1215 window while the dialog is up */
1216 rd = (repeatDialog *)XtMalloc(sizeof(repeatDialog));
1217 rd->lastCommand = XtNewString(LastCommand);
1219 /* make a label for the Last command item of the dialog, which includes
1220 the last executed action name */
1221 parenChar = strchr(LastCommand, '(');
1222 if (parenChar == NULL)
1223 return;
1224 cmdNameLen = parenChar-LastCommand;
1225 lastCmdLabel = XtMalloc(16 + cmdNameLen);
1226 strcpy(lastCmdLabel, "Last Command (");
1227 strncpy(&lastCmdLabel[14], LastCommand, cmdNameLen);
1228 strcpy(&lastCmdLabel[14 + cmdNameLen], ")");
1230 XtSetArg(selBoxArgs[0], XmNautoUnmanage, False);
1231 selBox = CreatePromptDialog(window->shell, "repeat", selBoxArgs, 1);
1232 rd->shell = XtParent(selBox);
1233 XtAddCallback(rd->shell, XmNdestroyCallback, repeatDestroyCB, rd);
1234 XtAddCallback(selBox, XmNokCallback, repeatOKCB, rd);
1235 XtAddCallback(selBox, XmNapplyCallback, repeatApplyCB, rd);
1236 XtAddCallback(selBox, XmNcancelCallback, repeatCancelCB, rd);
1237 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_TEXT));
1238 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_SELECTION_LABEL));
1239 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_HELP_BUTTON));
1240 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_APPLY_BUTTON));
1241 XtVaSetValues(XtParent(selBox), XmNtitle, "Repeat Macro", NULL);
1242 AddMotifCloseCallback(XtParent(selBox), repeatCancelCB, rd);
1244 form = XtVaCreateManagedWidget("form", xmFormWidgetClass, selBox, NULL);
1246 radioBox = XtVaCreateManagedWidget("cmdSrc", xmRowColumnWidgetClass, form,
1247 XmNradioBehavior, True,
1248 XmNorientation, XmHORIZONTAL,
1249 XmNpacking, XmPACK_TIGHT,
1250 XmNtopAttachment, XmATTACH_FORM,
1251 XmNleftAttachment, XmATTACH_FORM, NULL);
1252 rd->lastCmdToggle = XtVaCreateManagedWidget("lastCmdToggle",
1253 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1254 XmNlabelString, s1=XmStringCreateSimple(lastCmdLabel),
1255 XmNmnemonic, 'C', NULL);
1256 XmStringFree(s1);
1257 XtFree(lastCmdLabel);
1258 XtVaCreateManagedWidget("learnReplayToggle",
1259 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1260 XmNlabelString,
1261 s1=XmStringCreateSimple("Learn/Replay"),
1262 XmNmnemonic, 'L',
1263 XmNsensitive, ReplayMacro != NULL, NULL);
1264 XmStringFree(s1);
1266 timesForm = XtVaCreateManagedWidget("form", xmFormWidgetClass, form,
1267 XmNtopAttachment, XmATTACH_WIDGET,
1268 XmNtopWidget, radioBox,
1269 XmNtopOffset, 10,
1270 XmNleftAttachment, XmATTACH_FORM, NULL);
1271 radioBox = XtVaCreateManagedWidget("method", xmRowColumnWidgetClass,
1272 timesForm,
1273 XmNradioBehavior, True,
1274 XmNorientation, XmHORIZONTAL,
1275 XmNpacking, XmPACK_TIGHT,
1276 XmNtopAttachment, XmATTACH_FORM,
1277 XmNbottomAttachment, XmATTACH_FORM,
1278 XmNleftAttachment, XmATTACH_FORM, NULL);
1279 rd->inSelToggle = XtVaCreateManagedWidget("inSelToggle",
1280 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1281 XmNlabelString, s1=XmStringCreateSimple("In Selection"),
1282 XmNmnemonic, 'I', NULL);
1283 XmStringFree(s1);
1284 rd->toEndToggle = XtVaCreateManagedWidget("toEndToggle",
1285 xmToggleButtonWidgetClass, radioBox, XmNset, False,
1286 XmNlabelString, s1=XmStringCreateSimple("To End"),
1287 XmNmnemonic, 'T', NULL);
1288 XmStringFree(s1);
1289 XtVaCreateManagedWidget("nTimesToggle",
1290 xmToggleButtonWidgetClass, radioBox, XmNset, True,
1291 XmNlabelString, s1=XmStringCreateSimple("N Times"),
1292 XmNmnemonic, 'N',
1293 XmNset, True, NULL);
1294 XmStringFree(s1);
1295 rd->repeatText = XtVaCreateManagedWidget("repeatText", xmTextWidgetClass,
1296 timesForm,
1297 XmNcolumns, 5,
1298 XmNtopAttachment, XmATTACH_FORM,
1299 XmNbottomAttachment, XmATTACH_FORM,
1300 XmNleftAttachment, XmATTACH_WIDGET,
1301 XmNleftWidget, radioBox, NULL);
1302 RemapDeleteKey(rd->repeatText);
1304 /* Handle mnemonic selection of buttons and focus to dialog */
1305 AddDialogMnemonicHandler(form, FALSE);
1307 /* Set initial focus */
1308 #if XmVersion >= 1002
1309 XtVaSetValues(form, XmNinitialFocus, timesForm, NULL);
1310 XtVaSetValues(timesForm, XmNinitialFocus, rd->repeatText, NULL);
1311 #endif
1313 /* put up dialog */
1314 rd->forWindow = window;
1315 ManageDialogCenteredOnPointer(selBox);
1318 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData)
1320 repeatDialog *rd = (repeatDialog *)clientData;
1322 if (doRepeatDialogAction(rd, ((XmAnyCallbackStruct *)callData)->event))
1323 XtDestroyWidget(rd->shell);
1326 /* Note that the apply button is not managed in the repeat dialog. The dialog
1327 itself is capable of non-modal operation, but to be complete, it needs
1328 to dynamically update last command, dimming of learn/replay, possibly a
1329 stop button for the macro, and possibly in-selection with selection */
1330 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData)
1332 doRepeatDialogAction((repeatDialog *)clientData,
1333 ((XmAnyCallbackStruct *)callData)->event);
1336 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event)
1338 int nTimes;
1339 char nTimesStr[TYPE_INT_STR_SIZE(int)];
1340 char *params[2];
1342 /* Find out from the dialog how to repeat the command */
1343 if (XmToggleButtonGetState(rd->inSelToggle))
1345 if (!rd->forWindow->buffer->primary.selected)
1347 DialogF(DF_WARN, rd->shell, 1, "Repeat Macro",
1348 "No selection in window to repeat within", "Dismiss");
1349 XmProcessTraversal(rd->inSelToggle, XmTRAVERSE_CURRENT);
1350 return False;
1352 params[0] = "in_selection";
1353 } else if (XmToggleButtonGetState(rd->toEndToggle))
1355 params[0] = "to_end";
1356 } else
1358 if (GetIntTextWarn(rd->repeatText, &nTimes, "number of times", True)
1359 != TEXT_READ_OK)
1361 XmProcessTraversal(rd->repeatText, XmTRAVERSE_CURRENT);
1362 return False;
1364 sprintf(nTimesStr, "%d", nTimes);
1365 params[0] = nTimesStr;
1368 /* Figure out which command user wants to repeat */
1369 if (XmToggleButtonGetState(rd->lastCmdToggle))
1370 params[1] = XtNewString(rd->lastCommand);
1371 else {
1372 if (ReplayMacro == NULL)
1373 return False;
1374 params[1] = XtNewString(ReplayMacro);
1377 /* call the action routine repeat_macro to do the work */
1378 XtCallActionProc(rd->forWindow->lastFocus, "repeat_macro", event, params,2);
1379 XtFree(params[1]);
1380 return True;
1383 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData)
1385 repeatDialog *rd = (repeatDialog *)clientData;
1387 XtDestroyWidget(rd->shell);
1390 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1392 repeatDialog *rd = (repeatDialog *)clientData;
1394 XtFree(rd->lastCommand);
1395 XtFree((char *)rd);
1399 ** Dispatches a macro to which repeats macro command in "command", either
1400 ** an integer number of times ("how" == positive integer), or within a
1401 ** selected range ("how" == REPEAT_IN_SEL), or to the end of the window
1402 ** ("how == REPEAT_TO_END).
1404 ** Note that as with most macro routines, this returns BEFORE the macro is
1405 ** finished executing
1407 void RepeatMacro(WindowInfo *window, const char *command, int how)
1409 Program *prog;
1410 char *errMsg, *stoppedAt, *loopMacro, *loopedCmd;
1412 if (command == NULL)
1413 return;
1415 /* Wrap a for loop and counter/tests around the command */
1416 if (how == REPEAT_TO_END)
1417 loopMacro = "lastCursor=-1\nstartPos=$cursor\n\
1418 while($cursor>=startPos&&$cursor!=lastCursor){\nlastCursor=$cursor\n%s\n}\n";
1419 else if (how == REPEAT_IN_SEL)
1420 loopMacro = "selStart = $selection_start\nif (selStart == -1)\nreturn\n\
1421 selEnd = $selection_end\nset_cursor_pos(selStart)\nselect(0,0)\n\
1422 boundText = get_range(selEnd, selEnd+10)\n\
1423 while($cursor >= selStart && $cursor < selEnd && \\\n\
1424 get_range(selEnd, selEnd+10) == boundText) {\n\
1425 startLength = $text_length\n%s\n\
1426 selEnd += $text_length - startLength\n}\n";
1427 else
1428 loopMacro = "for(i=0;i<%d;i++){\n%s\n}\n";
1429 loopedCmd = XtMalloc(strlen(command) + strlen(loopMacro) + 25);
1430 if (how == REPEAT_TO_END || how == REPEAT_IN_SEL)
1431 sprintf(loopedCmd, loopMacro, command);
1432 else
1433 sprintf(loopedCmd, loopMacro, how, command);
1435 /* Parse the resulting macro into an executable program "prog" */
1436 prog = ParseMacro(loopedCmd, &errMsg, &stoppedAt);
1437 if (prog == NULL) {
1438 fprintf(stderr, "NEdit internal error, repeat macro syntax wrong: %s\n",
1439 errMsg);
1440 return;
1442 XtFree(loopedCmd);
1444 /* run the executable program */
1445 runMacro(window, prog);
1449 ** Macro recording action hook for Learn/Replay, added temporarily during
1450 ** learn.
1452 static void learnActionHook(Widget w, XtPointer clientData, String actionName,
1453 XEvent *event, String *params, Cardinal *numParams)
1455 WindowInfo *window;
1456 int i;
1457 char *actionString;
1459 /* Select only actions in text panes in the window for which this
1460 action hook is recording macros (from clientData). */
1461 for (window=WindowList; window!=NULL; window=window->next) {
1462 if (window->textArea == w)
1463 break;
1464 for (i=0; i<window->nPanes; i++) {
1465 if (window->textPanes[i] == w)
1466 break;
1468 if (i < window->nPanes)
1469 break;
1471 if (window == NULL || window != (WindowInfo *)clientData)
1472 return;
1474 /* beep on un-recordable operations which require a mouse position, to
1475 remind the user that the action was not recorded */
1476 if (isMouseAction(actionName)) {
1477 XBell(XtDisplay(w), 0);
1478 return;
1481 /* Record the action and its parameters */
1482 actionString = actionToString(w, actionName, event, params, *numParams);
1483 if (actionString != NULL) {
1484 BufInsert(MacroRecordBuf, MacroRecordBuf->length, actionString);
1485 XtFree(actionString);
1490 ** Permanent action hook for remembering last action for possible replay
1492 static void lastActionHook(Widget w, XtPointer clientData, String actionName,
1493 XEvent *event, String *params, Cardinal *numParams)
1495 WindowInfo *window;
1496 int i;
1497 char *actionString;
1499 /* Find the window to which this action belongs */
1500 for (window=WindowList; window!=NULL; window=window->next) {
1501 if (window->textArea == w)
1502 break;
1503 for (i=0; i<window->nPanes; i++) {
1504 if (window->textPanes[i] == w)
1505 break;
1507 if (i < window->nPanes)
1508 break;
1510 if (window == NULL)
1511 return;
1513 /* The last action is recorded for the benefit of repeating the last
1514 action. Don't record repeat_macro and wipe out the real action */
1515 if (!strcmp(actionName, "repeat_macro"))
1516 return;
1518 /* Record the action and its parameters */
1519 actionString = actionToString(w, actionName, event, params, *numParams);
1520 if (actionString != NULL) {
1521 if (LastCommand != NULL)
1522 XtFree(LastCommand);
1523 LastCommand = actionString;
1528 ** Create a macro string to represent an invocation of an action routine.
1529 ** Returns NULL for non-operational or un-recordable actions.
1531 static char *actionToString(Widget w, char *actionName, XEvent *event,
1532 String *params, Cardinal numParams)
1534 char chars[20], *charList[1], *outStr, *outPtr;
1535 KeySym keysym;
1536 int i, nChars, nParams, length, nameLength;
1537 #ifndef NO_XMIM
1538 int status;
1539 #endif
1541 if (isIgnoredAction(actionName) || isRedundantAction(actionName) ||
1542 isMouseAction(actionName))
1543 return NULL;
1545 /* Convert self_insert actions, to insert_string */
1546 if (!strcmp(actionName, "self_insert") ||
1547 !strcmp(actionName, "self-insert")) {
1548 actionName = "insert_string";
1549 #ifdef NO_XMIM
1550 nChars = XLookupString((XKeyEvent *)event, chars, 19, &keysym, NULL);
1551 if (nChars == 0)
1552 return NULL;
1553 #else
1555 nChars = XmImMbLookupString(w, (XKeyEvent *)event,
1556 chars, 19, &keysym, &status);
1557 if (nChars == 0 || status == XLookupNone ||
1558 status == XLookupKeySym || status == XBufferOverflow)
1559 return NULL;
1560 #endif
1561 chars[nChars] = '\0';
1562 charList[0] = chars;
1563 params = charList;
1564 nParams = 1;
1565 } else
1566 nParams = numParams;
1568 /* Figure out the length of string required */
1569 nameLength = strlen(actionName);
1570 length = nameLength + 3;
1571 for (i=0; i<nParams; i++)
1572 length += escapedStringLength(params[i]) + 4;
1574 /* Allocate the string and copy the information to it */
1575 outPtr = outStr = XtMalloc(length + 1);
1576 strcpy(outPtr, actionName);
1577 outPtr += nameLength;
1578 *outPtr++ = '(';
1579 for (i=0; i<nParams; i++) {
1580 *outPtr++ = '\"';
1581 outPtr += escapeStringChars(params[i], outPtr);
1582 *outPtr++ = '\"'; *outPtr++ = ','; *outPtr++ = ' ';
1584 if (nParams != 0)
1585 outPtr -= 2;
1586 *outPtr++ = ')'; *outPtr++ = '\n'; *outPtr++ = '\0';
1587 return outStr;
1590 static int isMouseAction(const char *action)
1592 int i;
1594 for (i=0; i<(int)XtNumber(MouseActions); i++)
1595 if (!strcmp(action, MouseActions[i]))
1596 return True;
1597 return False;
1600 static int isRedundantAction(const char *action)
1602 int i;
1604 for (i=0; i<(int)XtNumber(RedundantActions); i++)
1605 if (!strcmp(action, RedundantActions[i]))
1606 return True;
1607 return False;
1610 static int isIgnoredAction(const char *action)
1612 int i;
1614 for (i=0; i<(int)XtNumber(IgnoredActions); i++)
1615 if (!strcmp(action, IgnoredActions[i]))
1616 return True;
1617 return False;
1621 ** Timer proc for putting up the "Macro Command in Progress" banner if
1622 ** the process is taking too long.
1624 #define MAX_TIMEOUT_MSG_LEN (MAX_ACCEL_LEN + 60)
1625 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id)
1627 WindowInfo *window = (WindowInfo *)clientData;
1628 macroCmdInfo *cmdData = window->macroCmdData;
1629 XmString xmCancel;
1630 char *cCancel;
1631 char message[MAX_TIMEOUT_MSG_LEN];
1633 cmdData->bannerIsUp = True;
1635 /* Extract accelerator text from menu PushButtons */
1636 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL);
1638 /* Translate Motif string to char* */
1639 cCancel = GetXmStringText(xmCancel);
1641 /* Free Motif String */
1642 XmStringFree(xmCancel);
1644 /* Create message */
1645 if (cCancel[0] == '\0') {
1646 strncpy(message, "Macro Command in Progress", MAX_TIMEOUT_MSG_LEN);
1647 message[MAX_TIMEOUT_MSG_LEN - 1] = '\0';
1649 else {
1650 sprintf(message,
1651 "Macro Command in Progress -- Press %s to Cancel",
1652 cCancel);
1655 /* Free C-string */
1656 XtFree(cCancel);
1658 SetModeMessage(window, message);
1659 cmdData->bannerTimeoutID = 0;
1663 ** Work proc for continuing execution of a preempted macro.
1665 ** Xt WorkProcs are designed to run first-in first-out, which makes them
1666 ** very bad at sharing time between competing tasks. For this reason, it's
1667 ** usually bad to use work procs anywhere where their execution is likely to
1668 ** overlap. Using a work proc instead of a timer proc (which I usually
1669 ** prefer) here means macros will probably share time badly, but we're more
1670 ** interested in making the macros cancelable, and in continuing other work
1671 ** than having users run a bunch of them at once together.
1673 static Boolean continueWorkProc(XtPointer clientData)
1675 WindowInfo *window = (WindowInfo *)clientData;
1676 macroCmdInfo *cmdData = window->macroCmdData;
1677 char *errMsg;
1678 int stat;
1679 DataValue result;
1681 stat = ContinueMacro(cmdData->context, &result, &errMsg);
1682 if (stat == MACRO_ERROR)
1684 finishMacroCmdExecution(window);
1685 DialogF(DF_ERR, window->shell, 1, "Macro Error",
1686 "Error executing macro: %s", "Dismiss", errMsg);
1687 return True;
1688 } else if (stat == MACRO_DONE)
1690 finishMacroCmdExecution(window);
1691 return True;
1692 } else if (stat == MACRO_PREEMPT)
1694 cmdData->continueWorkProcID = 0;
1695 return True;
1698 /* Macro exceeded time slice, re-schedule it */
1699 if (stat != MACRO_TIME_LIMIT)
1700 return True; /* shouldn't happen */
1701 return False;
1705 ** Copy fromString to toString replacing special characters in strings, such
1706 ** that they can be read back by the macro parser's string reader. i.e. double
1707 ** quotes are replaced by \", backslashes are replaced with \\, C-std control
1708 ** characters like \n are replaced with their backslash counterparts. This
1709 ** routine should be kept reasonably in sync with yylex in parse.y. Companion
1710 ** routine escapedStringLength predicts the length needed to write the string
1711 ** when it is expanded with the additional characters. Returns the number
1712 ** of characters to which the string expanded.
1714 static int escapeStringChars(char *fromString, char *toString)
1716 char *e, *c, *outPtr = toString;
1718 /* substitute escape sequences */
1719 for (c=fromString; *c!='\0'; c++) {
1720 for (e=EscapeChars; *e!='\0'; e++) {
1721 if (*c == *e) {
1722 *outPtr++ = '\\';
1723 *outPtr++ = ReplaceChars[e-EscapeChars];
1724 break;
1727 if (*e == '\0')
1728 *outPtr++ = *c;
1730 *outPtr = '\0';
1731 return outPtr - toString;
1735 ** Predict the length of a string needed to hold a copy of "string" with
1736 ** special characters replaced with escape sequences by escapeStringChars.
1738 static int escapedStringLength(char *string)
1740 char *c, *e;
1741 int length = 0;
1743 /* calculate length and allocate returned string */
1744 for (c=string; *c!='\0'; c++) {
1745 for (e=EscapeChars; *e!='\0'; e++) {
1746 if (*c == *e) {
1747 length++;
1748 break;
1751 length++;
1753 return length;
1757 ** Built-in macro subroutine for getting the length of a string
1759 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs,
1760 DataValue *result, char **errMsg)
1762 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
1764 if (nArgs != 1)
1765 return wrongNArgsErr(errMsg);
1766 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1767 return False;
1768 result->tag = INT_TAG;
1769 result->val.n = strlen(string);
1770 return True;
1774 ** Built-in macro subroutines for min and max
1776 static int minMS(WindowInfo *window, DataValue *argList, int nArgs,
1777 DataValue *result, char **errMsg)
1779 int minVal, value, i;
1781 if (nArgs == 1)
1782 return tooFewArgsErr(errMsg);
1783 if (!readIntArg(argList[0], &minVal, errMsg))
1784 return False;
1785 for (i=0; i<nArgs; i++) {
1786 if (!readIntArg(argList[i], &value, errMsg))
1787 return False;
1788 minVal = value < minVal ? value : minVal;
1790 result->tag = INT_TAG;
1791 result->val.n = minVal;
1792 return True;
1794 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs,
1795 DataValue *result, char **errMsg)
1797 int maxVal, value, i;
1799 if (nArgs == 1)
1800 return tooFewArgsErr(errMsg);
1801 if (!readIntArg(argList[0], &maxVal, errMsg))
1802 return False;
1803 for (i=0; i<nArgs; i++) {
1804 if (!readIntArg(argList[i], &value, errMsg))
1805 return False;
1806 maxVal = value > maxVal ? value : maxVal;
1808 result->tag = INT_TAG;
1809 result->val.n = maxVal;
1810 return True;
1813 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs,
1814 DataValue *result, char **errMsg)
1816 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1817 WindowInfo *w;
1818 char fullname[MAXPATHLEN];
1820 /* Read the argument representing the window to focus to, and translate
1821 it into a pointer to a real WindowInfo */
1822 if (nArgs != 1)
1823 return wrongNArgsErr(errMsg);
1824 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1825 return False;
1826 else if (!strcmp(string, "last"))
1827 w = WindowList;
1828 else if (!strcmp(string, "next"))
1829 w = window->next;
1830 else {
1831 for (w=WindowList; w != NULL; w = w->next) {
1832 sprintf(fullname, "%s%s", w->path, w->filename);
1833 if (!strcmp(string, fullname))
1834 break;
1838 /* If no matching window was found, return empty string and do nothing */
1839 if (w == NULL) {
1840 result->tag = STRING_TAG;
1841 result->val.str.rep = PERM_ALLOC_STR("");
1842 result->val.str.len = 0;
1843 return True;
1846 /* Change the focused window to the requested one */
1847 SetMacroFocusWindow(w);
1849 /* turn on syntax highlight that might have been deferred */
1850 if (w->highlightSyntax && w->highlightData==NULL)
1851 StartHighlighting(w, False);
1853 /* Return the name of the window */
1854 result->tag = STRING_TAG;
1855 AllocNString(&result->val.str, strlen(w->path)+strlen(w->filename)+1);
1856 sprintf(result->val.str.rep, "%s%s", w->path, w->filename);
1857 return True;
1861 ** Built-in macro subroutine for getting text from the current window's text
1862 ** buffer
1864 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1865 DataValue *result, char **errMsg)
1867 int from, to;
1868 textBuffer *buf = window->buffer;
1869 char *rangeText;
1871 /* Validate arguments and convert to int */
1872 if (nArgs != 2)
1873 return wrongNArgsErr(errMsg);
1874 if (!readIntArg(argList[0], &from, errMsg))
1875 return False;
1876 if (!readIntArg(argList[1], &to, errMsg))
1877 return False;
1878 if (from < 0) from = 0;
1879 if (from > buf->length) from = buf->length;
1880 if (to < 0) to = 0;
1881 if (to > buf->length) to = buf->length;
1882 if (from > to) {int temp = from; from = to; to = temp;}
1884 /* Copy text from buffer (this extra copy could be avoided if textBuf.c
1885 provided a routine for writing into a pre-allocated string) */
1886 result->tag = STRING_TAG;
1887 AllocNString(&result->val.str, to - from + 1);
1888 rangeText = BufGetRange(buf, from, to);
1889 BufUnsubstituteNullChars(rangeText, buf);
1890 strcpy(result->val.str.rep, rangeText);
1891 /* Note: after the un-substitution, it is possible that strlen() != len,
1892 but that's because strlen() can't deal with 0-characters. */
1893 XtFree(rangeText);
1894 return True;
1898 ** Built-in macro subroutine for getting a single character at the position
1899 ** given, from the current window
1901 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs,
1902 DataValue *result, char **errMsg)
1904 int pos;
1905 textBuffer *buf = window->buffer;
1907 /* Validate argument and convert it to int */
1908 if (nArgs != 1)
1909 return wrongNArgsErr(errMsg);
1910 if (!readIntArg(argList[0], &pos, errMsg))
1911 return False;
1912 if (pos < 0) pos = 0;
1913 if (pos > buf->length) pos = buf->length;
1915 /* Return the character in a pre-allocated string) */
1916 result->tag = STRING_TAG;
1917 AllocNString(&result->val.str, 2);
1918 result->val.str.rep[0] = BufGetCharacter(buf, pos);
1919 BufUnsubstituteNullChars(result->val.str.rep, buf);
1920 /* Note: after the un-substitution, it is possible that strlen() != len,
1921 but that's because strlen() can't deal with 0-characters. */
1922 return True;
1926 ** Built-in macro subroutine for replacing text in the current window's text
1927 ** buffer
1929 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
1930 DataValue *result, char **errMsg)
1932 int from, to;
1933 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1934 textBuffer *buf = window->buffer;
1936 /* Validate arguments and convert to int */
1937 if (nArgs != 3)
1938 return wrongNArgsErr(errMsg);
1939 if (!readIntArg(argList[0], &from, errMsg))
1940 return False;
1941 if (!readIntArg(argList[1], &to, errMsg))
1942 return False;
1943 if (!readStringArg(argList[2], &string, stringStorage, errMsg))
1944 return False;
1945 if (from < 0) from = 0;
1946 if (from > buf->length) from = buf->length;
1947 if (to < 0) to = 0;
1948 if (to > buf->length) to = buf->length;
1949 if (from > to) {int temp = from; from = to; to = temp;}
1951 /* Don't allow modifications if the window is read-only */
1952 if (IS_ANY_LOCKED(window->lockReasons)) {
1953 XBell(XtDisplay(window->shell), 0);
1954 result->tag = NO_TAG;
1955 return True;
1958 /* There are no null characters in the string (because macro strings
1959 still have null termination), but if the string contains the
1960 character used by the buffer for null substitution, it could
1961 theoretically become a null. In the highly unlikely event that
1962 all of the possible substitution characters in the buffer are used
1963 up, stop the macro and tell the user of the failure */
1964 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
1965 *errMsg = "Too much binary data in file";
1966 return False;
1969 /* Do the replace */
1970 BufReplace(buf, from, to, string);
1971 result->tag = NO_TAG;
1972 return True;
1976 ** Built-in macro subroutine for replacing the primary-selection selected
1977 ** text in the current window's text buffer
1979 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
1980 DataValue *result, char **errMsg)
1982 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
1984 /* Validate argument and convert to string */
1985 if (nArgs != 1)
1986 return wrongNArgsErr(errMsg);
1987 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
1988 return False;
1990 /* Don't allow modifications if the window is read-only */
1991 if (IS_ANY_LOCKED(window->lockReasons)) {
1992 XBell(XtDisplay(window->shell), 0);
1993 result->tag = NO_TAG;
1994 return True;
1997 /* There are no null characters in the string (because macro strings
1998 still have null termination), but if the string contains the
1999 character used by the buffer for null substitution, it could
2000 theoretically become a null. In the highly unlikely event that
2001 all of the possible substitution characters in the buffer are used
2002 up, stop the macro and tell the user of the failure */
2003 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) {
2004 *errMsg = "Too much binary data in file";
2005 return False;
2008 /* Do the replace */
2009 BufReplaceSelected(window->buffer, string);
2010 result->tag = NO_TAG;
2011 return True;
2015 ** Built-in macro subroutine for getting the text currently selected by
2016 ** the primary selection in the current window's text buffer, or in any
2017 ** part of screen if "any" argument is given
2019 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs,
2020 DataValue *result, char **errMsg)
2022 char *selText;
2024 /* Read argument list to check for "any" keyword, and get the appropriate
2025 selection */
2026 if (nArgs != 0 && nArgs != 1)
2027 return wrongNArgsErr(errMsg);
2028 if (nArgs == 1) {
2029 if (argList[0].tag != STRING_TAG || strcmp(argList[0].val.str.rep, "any")) {
2030 *errMsg = "Unrecognized argument to %s";
2031 return False;
2033 selText = GetAnySelection(window);
2034 if (selText == NULL)
2035 selText = XtNewString("");
2036 } else {
2037 selText = BufGetSelectionText(window->buffer);
2038 BufUnsubstituteNullChars(selText, window->buffer);
2041 /* Return the text as an allocated string */
2042 result->tag = STRING_TAG;
2043 AllocNStringCpy(&result->val.str, selText);
2044 XtFree(selText);
2045 return True;
2049 ** Built-in macro subroutine for determining if implicit conversion of
2050 ** a string to number will succeed or fail
2052 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs,
2053 DataValue *result, char **errMsg)
2055 char *string, stringStorage[TYPE_INT_STR_SIZE(int)];
2057 if (nArgs != 1) {
2058 return wrongNArgsErr(errMsg);
2060 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) {
2061 return False;
2064 result->tag = INT_TAG;
2065 result->val.n = StringToNum(string, NULL);
2067 return True;
2071 ** Built-in macro subroutine for replacing a substring within another string
2073 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
2074 DataValue *result, char **errMsg)
2076 int from, to, length, replaceLen, outLen;
2077 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *replStr;
2079 /* Validate arguments and convert to int */
2080 if (nArgs != 4)
2081 return wrongNArgsErr(errMsg);
2082 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2083 return False;
2084 if (!readIntArg(argList[1], &from, errMsg))
2085 return False;
2086 if (!readIntArg(argList[2], &to, errMsg))
2087 return False;
2088 if (!readStringArg(argList[3], &replStr, stringStorage[1], errMsg))
2089 return False;
2090 length = strlen(string);
2091 if (from < 0) from = 0;
2092 if (from > length) from = length;
2093 if (to < 0) to = 0;
2094 if (to > length) to = length;
2095 if (from > to) {int temp = from; from = to; to = temp;}
2097 /* Allocate a new string and do the replacement */
2098 replaceLen = strlen(replStr);
2099 outLen = length - (to - from) + replaceLen;
2100 result->tag = STRING_TAG;
2101 AllocNString(&result->val.str, outLen+1);
2102 strncpy(result->val.str.rep, string, from);
2103 strncpy(&result->val.str.rep[from], replStr, replaceLen);
2104 strncpy(&result->val.str.rep[from + replaceLen], &string[to], length - to);
2105 return True;
2109 ** Built-in macro subroutine for getting a substring of a string
2111 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs,
2112 DataValue *result, char **errMsg)
2114 int from, to, length;
2115 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2117 /* Validate arguments and convert to int */
2118 if (nArgs != 3)
2119 return wrongNArgsErr(errMsg);
2120 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2121 return False;
2122 if (!readIntArg(argList[1], &from, errMsg))
2123 return False;
2124 if (!readIntArg(argList[2], &to, errMsg))
2125 return False;
2126 length = strlen(string);
2127 if (from < 0) from = 0;
2128 if (from > length) from = length;
2129 if (to < 0) to = 0;
2130 if (to > length) to = length;
2131 if (from > to) {int temp = from; from = to; to = temp;}
2133 /* Allocate a new string and copy the sub-string into it */
2134 result->tag = STRING_TAG;
2135 AllocNStringNCpy(&result->val.str, &string[from], to - from);
2136 return True;
2139 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs,
2140 DataValue *result, char **errMsg)
2142 int i, length;
2143 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2145 /* Validate arguments and convert to int */
2146 if (nArgs != 1)
2147 return wrongNArgsErr(errMsg);
2148 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2149 return False;
2150 length = strlen(string);
2152 /* Allocate a new string and copy an uppercased version of the string it */
2153 result->tag = STRING_TAG;
2154 AllocNString(&result->val.str, length + 1);
2155 for (i=0; i<length; i++)
2156 result->val.str.rep[i] = toupper((unsigned char)string[i]);
2157 return True;
2160 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs,
2161 DataValue *result, char **errMsg)
2163 int i, length;
2164 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2166 /* Validate arguments and convert to int */
2167 if (nArgs != 1)
2168 return wrongNArgsErr(errMsg);
2169 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2170 return False;
2171 length = strlen(string);
2173 /* Allocate a new string and copy an lowercased version of the string it */
2174 result->tag = STRING_TAG;
2175 AllocNString(&result->val.str, length + 1);
2176 for (i=0; i<length; i++)
2177 result->val.str.rep[i] = tolower((unsigned char)string[i]);
2178 return True;
2181 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs,
2182 DataValue *result, char **errMsg)
2184 long itemID = 0;
2185 XmString s;
2186 int stat;
2187 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2189 /* Get the string argument */
2190 if (nArgs != 1)
2191 return wrongNArgsErr(errMsg);
2192 if (!readStringArg(argList[0], &string, stringStorage, errMsg))
2193 return False;
2195 /* Use the XmClipboard routines to copy the text to the clipboard.
2196 If errors occur, just give up. */
2197 result->tag = NO_TAG;
2198 stat = XmClipboardStartCopy(TheDisplay, XtWindow(window->textArea),
2199 s=XmStringCreateSimple("NEdit"), XtLastTimestampProcessed(TheDisplay),
2200 window->textArea, NULL, &itemID);
2201 XmStringFree(s);
2202 if (stat != ClipboardSuccess)
2203 return True;
2204 if (XmClipboardCopy(TheDisplay, XtWindow(window->textArea), itemID, "STRING",
2205 string, strlen(string), 0, NULL) != ClipboardSuccess)
2206 return True;
2207 XmClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID);
2208 return True;
2211 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2212 DataValue *result, char **errMsg)
2214 unsigned long length, retLength;
2215 long id = 0;
2217 /* Should have no arguments */
2218 if (nArgs != 0)
2219 return wrongNArgsErr(errMsg);
2221 /* Ask if there's a string in the clipboard, and get its length */
2222 if (XmClipboardInquireLength(TheDisplay, XtWindow(window->shell), "STRING",
2223 &length) != ClipboardSuccess) {
2224 result->tag = STRING_TAG;
2225 result->val.str.rep = PERM_ALLOC_STR("");
2226 result->val.str.len = 0;
2227 return True;
2230 /* Allocate a new string to hold the data */
2231 result->tag = STRING_TAG;
2232 AllocNString(&result->val.str, (int)length + 1);
2234 /* Copy the clipboard contents to the string */
2235 if (XmClipboardRetrieve(TheDisplay, XtWindow(window->shell), "STRING",
2236 result->val.str.rep, length, &retLength, &id) != ClipboardSuccess)
2237 retLength = 0;
2238 result->val.str.rep[retLength] = '\0';
2239 result->val.str.len = retLength;
2241 return True;
2246 ** Built-in macro subroutine for reading the contents of a text file into
2247 ** a string. On success, returns 1 in $readStatus, and the contents of the
2248 ** file as a string in the subroutine return value. On failure, returns
2249 ** the empty string "" and an 0 $readStatus.
2251 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2252 DataValue *result, char **errMsg)
2254 char stringStorage[TYPE_INT_STR_SIZE(int)], *name;
2255 struct stat statbuf;
2256 FILE *fp;
2257 int readLen;
2259 /* Validate arguments and convert to int */
2260 if (nArgs != 1)
2261 return wrongNArgsErr(errMsg);
2262 if (!readStringArg(argList[0], &name, stringStorage, errMsg))
2263 return False;
2265 /* Read the whole file into an allocated string */
2266 if ((fp = fopen(name, "r")) == NULL)
2267 goto errorNoClose;
2268 if (fstat(fileno(fp), &statbuf) != 0)
2269 goto error;
2270 result->tag = STRING_TAG;
2271 AllocNString(&result->val.str, statbuf.st_size+1);
2272 readLen = fread(result->val.str.rep, sizeof(char), statbuf.st_size+1, fp);
2273 if (ferror(fp))
2274 goto error;
2275 if(!feof(fp)){
2276 /* Couldn't trust file size. Use slower but more general method */
2277 int chunkSize = 1024;
2278 char *buffer;
2280 buffer = XtMalloc(readLen * sizeof(char));
2281 memcpy(buffer, result->val.str.rep, readLen * sizeof(char));
2282 while (!feof(fp)){
2283 buffer = XtRealloc(buffer, (readLen+chunkSize)*sizeof(char));
2284 readLen += fread(&buffer[readLen], sizeof(char), chunkSize, fp);
2285 if (ferror(fp)){
2286 XtFree(buffer);
2287 goto error;
2290 AllocNString(&result->val.str, readLen + 1);
2291 memcpy(result->val.str.rep, buffer, readLen * sizeof(char));
2292 XtFree(buffer);
2294 fclose(fp);
2296 /* Return the results */
2297 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2298 ReturnGlobals[READ_STATUS]->value.val.n = True;
2299 return True;
2301 error:
2302 fclose(fp);
2304 errorNoClose:
2305 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG;
2306 ReturnGlobals[READ_STATUS]->value.val.n = False;
2307 result->tag = STRING_TAG;
2308 result->val.str.rep = PERM_ALLOC_STR("");
2309 result->val.str.len = 0;
2310 return True;
2314 ** Built-in macro subroutines for writing or appending a string (parameter $1)
2315 ** to a file named in parameter $2. Returns 1 on successful write, or 0 if
2316 ** unsuccessful.
2318 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2319 DataValue *result, char **errMsg)
2321 return writeOrAppendFile(False, window, argList, nArgs, result, errMsg);
2324 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs,
2325 DataValue *result, char **errMsg)
2327 return writeOrAppendFile(True, window, argList, nArgs, result, errMsg);
2330 static int writeOrAppendFile(int append, WindowInfo *window,
2331 DataValue *argList, int nArgs, DataValue *result, char **errMsg)
2333 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *name, *string;
2334 FILE *fp;
2336 /* Validate argument */
2337 if (nArgs != 2)
2338 return wrongNArgsErr(errMsg);
2339 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg))
2340 return False;
2341 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg))
2342 return False;
2344 /* open the file */
2345 if ((fp = fopen(name, append ? "a" : "w")) == NULL) {
2346 result->tag = INT_TAG;
2347 result->val.n = False;
2348 return True;
2351 /* write the string to the file */
2352 fwrite(string, sizeof(char), strlen(string), fp);
2353 if (ferror(fp)) {
2354 fclose(fp);
2355 result->tag = INT_TAG;
2356 result->val.n = False;
2357 return True;
2359 fclose(fp);
2361 /* return the status */
2362 result->tag = INT_TAG;
2363 result->val.n = True;
2364 return True;
2368 ** Built-in macro subroutine for searching silently in a window without
2369 ** dialogs, beeps, or changes to the selection. Arguments are: $1: string to
2370 ** search for, $2: starting position. Optional arguments may include the
2371 ** strings: "wrap" to make the search wrap around the beginning or end of the
2372 ** string, "backward" or "forward" to change the search direction ("forward" is
2373 ** the default), "literal", "case" or "regex" to change the search type
2374 ** (default is "literal").
2376 ** Returns the starting position of the match, or -1 if nothing matched.
2377 ** also returns the ending position of the match in $searchEndPos
2379 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs,
2380 DataValue *result, char **errMsg)
2382 DataValue newArgList[9];
2383 int retVal;
2385 /* Use the search string routine, by adding the buffer contents as
2386 the string argument */
2387 if (nArgs > 8)
2388 return wrongNArgsErr(errMsg);
2389 newArgList[0].tag = STRING_TAG;
2390 newArgList[0].val.str.rep = BufGetAll(window->buffer);
2391 newArgList[0].val.str.len = window->buffer->length;
2392 memcpy(&newArgList[1], argList, nArgs * sizeof(DataValue));
2393 retVal = searchStringMS(window, newArgList, nArgs+1, result, errMsg);
2394 XtFree(newArgList[0].val.str.rep);
2395 return retVal;
2399 ** Built-in macro subroutine for searching a string. Arguments are $1:
2400 ** string to search in, $2: string to search for, $3: starting position.
2401 ** Optional arguments may include the strings: "wrap" to make the search
2402 ** wrap around the beginning or end of the string, "backward" or "forward"
2403 ** to change the search direction ("forward" is the default), "literal",
2404 ** "case" or "regex" to change the search type (default is "literal").
2406 ** Returns the starting position of the match, or -1 if nothing matched.
2407 ** also returns the ending position of the match in $searchEndPos
2409 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2410 DataValue *result, char **errMsg)
2412 int beginPos, wrap, direction, found = False, foundStart, foundEnd, type;
2413 int skipSearch = False, len;
2414 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *searchStr;
2416 /* Validate arguments and convert to proper types */
2417 if (nArgs < 3)
2418 return tooFewArgsErr(errMsg);
2419 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2420 return False;
2421 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2422 return False;
2423 if (!readIntArg(argList[2], &beginPos, errMsg))
2424 return False;
2425 if (!readSearchArgs(&argList[3], nArgs-3, &direction, &type, &wrap, errMsg))
2426 return False;
2428 len = argList[0].val.str.len;
2429 if (beginPos > len) {
2430 if (direction == SEARCH_FORWARD) {
2431 if (wrap) {
2432 beginPos = 0; /* Wrap immediately */
2433 } else {
2434 found = False;
2435 skipSearch = True;
2437 } else {
2438 beginPos = len;
2440 } else if (beginPos < 0) {
2441 if (direction == SEARCH_BACKWARD) {
2442 if (wrap) {
2443 beginPos = len; /* Wrap immediately */
2444 } else {
2445 found = False;
2446 skipSearch = True;
2448 } else {
2449 beginPos = 0;
2453 if (!skipSearch)
2454 found = SearchString(string, searchStr, direction, type, wrap, beginPos,
2455 &foundStart, &foundEnd, NULL, NULL, GetWindowDelimiters(window));
2457 /* Return the results */
2458 ReturnGlobals[SEARCH_END]->value.tag = INT_TAG;
2459 ReturnGlobals[SEARCH_END]->value.val.n = found ? foundEnd : 0;
2460 result->tag = INT_TAG;
2461 result->val.n = found ? foundStart : -1;
2462 return True;
2466 ** Built-in macro subroutine for replacing all occurences of a search string in
2467 ** a string with a replacement string. Arguments are $1: string to search in,
2468 ** $2: string to search for, $3: replacement string. Also takes an optional
2469 ** search type: one of "literal", "case" or "regex" (default is "literal"), and
2470 ** an optional "copy" argument.
2472 ** Returns a new string with all of the replacements done. If no replacements
2473 ** were performed and "copy" was specified, returns a copy of the original
2474 ** string. Otherwise returns an empty string ("").
2476 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs,
2477 DataValue *result, char **errMsg)
2479 char stringStorage[3][TYPE_INT_STR_SIZE(int)], *string, *searchStr, *replaceStr;
2480 char *argStr, *replacedStr;
2481 int searchType = SEARCH_LITERAL, copyStart, copyEnd;
2482 int replacedLen, replaceEnd, force=False, i;
2484 /* Validate arguments and convert to proper types */
2485 if (nArgs < 3 || nArgs > 5)
2486 return wrongNArgsErr(errMsg);
2487 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg))
2488 return False;
2489 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg))
2490 return False;
2491 if (!readStringArg(argList[2], &replaceStr, stringStorage[2], errMsg))
2492 return False;
2493 for (i = 3; i < nArgs; i++) {
2494 /* Read the optional search type and force arguments */
2495 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
2496 return False;
2497 if (!StringToSearchType(argStr, &searchType)) {
2498 /* It's not a search type. is it "copy"? */
2499 if (!strcmp(argStr, "copy")) {
2500 force = True;
2501 } else {
2502 *errMsg = "unrecognized argument to %s";
2503 return False;
2508 /* Do the replace */
2509 replacedStr = ReplaceAllInString(string, searchStr, replaceStr, searchType,
2510 &copyStart, &copyEnd, &replacedLen, GetWindowDelimiters(window));
2512 /* Return the results */
2513 result->tag = STRING_TAG;
2514 if (replacedStr == NULL) {
2515 if (force) {
2516 /* Just copy the original DataValue */
2517 if (argList[0].tag == STRING_TAG) {
2518 result->val.str.rep = argList[0].val.str.rep;
2519 result->val.str.len = argList[0].val.str.len;
2521 else {
2522 AllocNStringCpy(&result->val.str, string);
2525 else {
2526 result->val.str.rep = PERM_ALLOC_STR("");
2527 result->val.str.len = 0;
2530 else {
2531 size_t remainder = strlen(&string[copyEnd]);
2532 replaceEnd = copyStart + replacedLen;
2533 AllocNString(&result->val.str, replaceEnd + remainder + 1);
2534 strncpy(result->val.str.rep, string, copyStart);
2535 strcpy(&result->val.str.rep[copyStart], replacedStr);
2536 strcpy(&result->val.str.rep[replaceEnd], &string[copyEnd]);
2537 XtFree(replacedStr);
2539 return True;
2542 static int readSearchArgs(DataValue *argList, int nArgs, int *searchDirection,
2543 int *searchType, int *wrap, char **errMsg)
2545 int i;
2546 char *argStr, stringStorage[TYPE_INT_STR_SIZE(int)];
2548 *wrap = False;
2549 *searchDirection = SEARCH_FORWARD;
2550 *searchType = SEARCH_LITERAL;
2551 for (i=0; i<nArgs; i++) {
2552 if (!readStringArg(argList[i], &argStr, stringStorage, errMsg))
2553 return False;
2554 else if (!strcmp(argStr, "wrap"))
2555 *wrap = True;
2556 else if (!strcmp(argStr, "nowrap"))
2557 *wrap = False;
2558 else if (!strcmp(argStr, "backward"))
2559 *searchDirection = SEARCH_BACKWARD;
2560 else if (!strcmp(argStr, "forward"))
2561 *searchDirection = SEARCH_FORWARD;
2562 else if (!StringToSearchType(argStr, searchType)) {
2563 *errMsg = "Unrecognized argument to %s";
2564 return False;
2567 return True;
2570 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs,
2571 DataValue *result, char **errMsg)
2573 int pos;
2575 /* Get argument and convert to int */
2576 if (nArgs != 1)
2577 return wrongNArgsErr(errMsg);
2578 if (!readIntArg(argList[0], &pos, errMsg))
2579 return False;
2581 /* Set the position */
2582 TextSetCursorPos(window->lastFocus, pos);
2583 result->tag = NO_TAG;
2584 return True;
2587 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs,
2588 DataValue *result, char **errMsg)
2590 int start, end, startTmp;
2592 /* Get arguments and convert to int */
2593 if (nArgs != 2)
2594 return wrongNArgsErr(errMsg);
2595 if (!readIntArg(argList[0], &start, errMsg))
2596 return False;
2597 if (!readIntArg(argList[1], &end, errMsg))
2598 return False;
2600 /* Verify integrity of arguments */
2601 if (start > end) {
2602 startTmp = start;
2603 start = end;
2604 end = startTmp;
2606 if (start < 0) start = 0;
2607 if (start > window->buffer->length) start = window->buffer->length;
2608 if (end < 0) end = 0;
2609 if (end > window->buffer->length) end = window->buffer->length;
2611 /* Make the selection */
2612 BufSelect(window->buffer, start, end);
2613 result->tag = NO_TAG;
2614 return True;
2617 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs,
2618 DataValue *result, char **errMsg)
2620 int start, end, left, right;
2622 /* Get arguments and convert to int */
2623 if (nArgs != 4)
2624 return wrongNArgsErr(errMsg);
2625 if (!readIntArg(argList[0], &start, errMsg))
2626 return False;
2627 if (!readIntArg(argList[1], &end, errMsg))
2628 return False;
2629 if (!readIntArg(argList[2], &left, errMsg))
2630 return False;
2631 if (!readIntArg(argList[3], &right, errMsg))
2632 return False;
2634 /* Make the selection */
2635 BufRectSelect(window->buffer, start, end, left, right);
2636 result->tag = NO_TAG;
2637 return True;
2641 ** Macro subroutine to ring the bell
2643 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs,
2644 DataValue *result, char **errMsg)
2646 if (nArgs != 0)
2647 return wrongNArgsErr(errMsg);
2648 XBell(XtDisplay(window->shell), 0);
2649 result->tag = NO_TAG;
2650 return True;
2653 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs,
2654 DataValue *result, char **errMsg)
2656 char stringStorage[TYPE_INT_STR_SIZE(int)], *string;
2657 int i;
2659 if (nArgs == 0)
2660 return tooFewArgsErr(errMsg);
2661 for (i=0; i<nArgs; i++) {
2662 if (!readStringArg(argList[i], &string, stringStorage, errMsg))
2663 return False;
2664 printf("%s%s", string, i==nArgs-1 ? "" : " ");
2666 fflush( stdout );
2667 result->tag = NO_TAG;
2668 return True;
2672 ** Built-in macro subroutine for getting the value of an environment variable
2674 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs,
2675 DataValue *result, char **errMsg)
2677 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
2678 char *name;
2679 char *value;
2681 /* Get name of variable to get */
2682 if (nArgs != 1)
2683 return wrongNArgsErr(errMsg);
2684 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
2685 *errMsg = "argument to %s must be a string";
2686 return False;
2688 value = getenv(name);
2689 if (value == NULL)
2690 value = "";
2692 /* Return the text as an allocated string */
2693 result->tag = STRING_TAG;
2694 AllocNStringCpy(&result->val.str, value);
2695 return True;
2698 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs,
2699 DataValue *result, char **errMsg)
2701 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *cmdString, *inputString;
2703 if (nArgs != 2)
2704 return wrongNArgsErr(errMsg);
2705 if (!readStringArg(argList[0], &cmdString, stringStorage[0], errMsg))
2706 return False;
2707 if (!readStringArg(argList[1], &inputString, stringStorage[1], errMsg))
2708 return False;
2710 /* Shell command execution requires that the macro be suspended, so
2711 this subroutine can't be run if macro execution can't be interrupted */
2712 if (MacroRunWindow()->macroCmdData == NULL) {
2713 *errMsg = "%s can't be called from non-suspendable context";
2714 return False;
2717 #ifdef VMS
2718 *errMsg = "Shell commands not supported under VMS";
2719 return False;
2720 #else
2721 ShellCmdToMacroString(window, cmdString, inputString);
2722 result->tag = INT_TAG;
2723 result->val.n = 0;
2724 return True;
2725 #endif /*VMS*/
2729 ** Method used by ShellCmdToMacroString (called by shellCmdMS), for returning
2730 ** macro string and exit status after the execution of a shell command is
2731 ** complete. (Sorry about the poor modularity here, it's just not worth
2732 ** teaching other modules about macro return globals, since other than this,
2733 ** they're not used outside of macro.c)
2735 void ReturnShellCommandOutput(WindowInfo *window, const char *outText, int status)
2737 DataValue retVal;
2738 macroCmdInfo *cmdData = window->macroCmdData;
2740 if (cmdData == NULL)
2741 return;
2742 retVal.tag = STRING_TAG;
2743 AllocNStringCpy(&retVal.val.str, outText);
2744 ModifyReturnedValue(cmdData->context, retVal);
2745 ReturnGlobals[SHELL_CMD_STATUS]->value.tag = INT_TAG;
2746 ReturnGlobals[SHELL_CMD_STATUS]->value.val.n = status;
2749 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2750 DataValue *result, char **errMsg)
2752 macroCmdInfo *cmdData;
2753 char stringStorage[9][TYPE_INT_STR_SIZE(int)];
2754 char *btnLabels[sizeof(stringStorage)/sizeof(*stringStorage)];
2755 char *message;
2756 Arg al[20];
2757 int ac;
2758 Widget dialog, btn;
2759 int i, nBtns;
2760 XmString s1, s2;
2762 /* Ignore the focused window passed as the function argument and put
2763 the dialog up over the window which is executing the macro */
2764 window = MacroRunWindow();
2765 cmdData = window->macroCmdData;
2767 /* Dialogs require macro to be suspended and interleaved with other macros.
2768 This subroutine can't be run if macro execution can't be interrupted */
2769 if (!cmdData) {
2770 *errMsg = "%s can't be called from non-suspendable context";
2771 return False;
2774 /* Read and check the arguments. The first being the dialog message,
2775 and the rest being the button labels */
2776 if (nArgs == 0) {
2777 *errMsg = "%s subroutine called with no arguments";
2778 return False;
2780 if (nArgs > sizeof(stringStorage)/sizeof(*stringStorage)) {
2781 *errMsg = "%s subroutine called with too many arguments";
2782 return False;
2784 if (!readStringArg(argList[0], &message, stringStorage[0], errMsg))
2785 return False;
2786 for (i=1; i<nArgs; i++)
2787 if (!readStringArg(argList[i], &btnLabels[i-1], stringStorage[i],
2788 errMsg))
2789 return False;
2790 if (nArgs == 1) {
2791 btnLabels[0] = "Dismiss";
2792 nBtns = 1;
2793 } else
2794 nBtns = nArgs - 1;
2796 /* Create the message box dialog widget and its dialog shell parent */
2797 ac = 0;
2798 XtSetArg(al[ac], XmNtitle, " "); ac++;
2799 XtSetArg(al[ac], XmNmessageString, s1=MKSTRING(message)); ac++;
2800 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0]));
2801 ac++;
2802 dialog = CreateMessageDialog(window->shell, "macroDialog", al, ac);
2803 XmStringFree(s1);
2804 XmStringFree(s2);
2805 AddMotifCloseCallback(XtParent(dialog), dialogCloseCB, window);
2806 XtAddCallback(dialog, XmNokCallback, dialogBtnCB, window);
2807 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2808 XmNuserData, (XtPointer)1, NULL);
2809 cmdData->dialog = dialog;
2811 /* Unmanage default buttons, except for "OK" */
2812 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2813 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2815 /* Make callback for the unmanaged cancel button (which can
2816 still get executed via the esc key) activate close box action */
2817 XtAddCallback(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2818 XmNactivateCallback, dialogCloseCB, window);
2820 /* Add user specified buttons (1st is already done) */
2821 for (i=1; i<nBtns; i++) {
2822 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2823 XmNlabelString, s1=XmStringCreateSimple(btnLabels[i]),
2824 XmNuserData, (XtPointer)(i+1), NULL);
2825 XtAddCallback(btn, XmNactivateCallback, dialogBtnCB, window);
2826 XmStringFree(s1);
2829 #ifdef LESSTIF_VERSION
2830 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2831 the escape key for closing the dialog (probably because the
2832 cancel button is not managed). */
2833 XtAddEventHandler(dialog, KeyPressMask, False, dialogEscCB,
2834 (XtPointer)window);
2835 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
2836 True, GrabModeAsync, GrabModeAsync);
2837 #endif /* LESSTIF_VERSION */
2839 /* Put up the dialog */
2840 ManageDialogCenteredOnPointer(dialog);
2842 /* Stop macro execution until the dialog is complete */
2843 PreemptMacro();
2845 /* Return placeholder result. Value will be changed by button callback */
2846 result->tag = INT_TAG;
2847 result->val.n = 0;
2848 return True;
2851 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData)
2853 WindowInfo *window = (WindowInfo *)clientData;
2854 macroCmdInfo *cmdData = window->macroCmdData;
2855 XtPointer userData;
2856 DataValue retVal;
2858 /* Return the index of the button which was pressed (stored in the userData
2859 field of the button widget). The 1st button, being a gadget, is not
2860 returned in w. */
2861 if (cmdData == NULL)
2862 return; /* shouldn't happen */
2863 if (XtClass(w) == xmPushButtonWidgetClass) {
2864 XtVaGetValues(w, XmNuserData, &userData, NULL);
2865 retVal.val.n = (int)userData;
2866 } else
2867 retVal.val.n = 1;
2868 retVal.tag = INT_TAG;
2869 ModifyReturnedValue(cmdData->context, retVal);
2871 /* Pop down the dialog */
2872 XtDestroyWidget(XtParent(cmdData->dialog));
2873 cmdData->dialog = NULL;
2875 /* Continue preempted macro execution */
2876 ResumeMacroExecution(window);
2879 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData)
2881 WindowInfo *window = (WindowInfo *)clientData;
2882 macroCmdInfo *cmdData = window->macroCmdData;
2883 DataValue retVal;
2885 /* Return 0 to show that the dialog was closed via the window close box */
2886 retVal.val.n = 0;
2887 retVal.tag = INT_TAG;
2888 ModifyReturnedValue(cmdData->context, retVal);
2890 /* Pop down the dialog */
2891 XtDestroyWidget(XtParent(cmdData->dialog));
2892 cmdData->dialog = NULL;
2894 /* Continue preempted macro execution */
2895 ResumeMacroExecution(window);
2898 #ifdef LESSTIF_VERSION
2899 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event,
2900 Boolean *cont)
2902 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
2903 return;
2904 if (clientData != NULL) {
2905 dialogCloseCB(w, (WindowInfo *)clientData, NULL);
2907 *cont = False;
2909 #endif /* LESSTIF_VERSION */
2911 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
2912 DataValue *result, char **errMsg)
2914 macroCmdInfo *cmdData;
2915 char stringStorage[9][TYPE_INT_STR_SIZE(int)];
2916 char *btnLabels[sizeof(stringStorage)/sizeof(*stringStorage)];
2917 char *message;
2918 Widget dialog, btn;
2919 int i, nBtns;
2920 XmString s1, s2;
2921 Arg al[20];
2922 int ac;
2924 /* Ignore the focused window passed as the function argument and put
2925 the dialog up over the window which is executing the macro */
2926 window = MacroRunWindow();
2927 cmdData = window->macroCmdData;
2929 /* Dialogs require macro to be suspended and interleaved with other macros.
2930 This subroutine can't be run if macro execution can't be interrupted */
2931 if (!cmdData) {
2932 *errMsg = "%s can't be called from non-suspendable context";
2933 return False;
2936 /* Read and check the arguments. The first being the dialog message,
2937 and the rest being the button labels */
2938 if (nArgs == 0) {
2939 *errMsg = "%s subroutine called with no arguments";
2940 return False;
2942 if (nArgs > sizeof(stringStorage)/sizeof(*stringStorage)) {
2943 *errMsg = "%s subroutine called with too many arguments";
2944 return False;
2946 if (!readStringArg(argList[0], &message, stringStorage[0], errMsg))
2947 return False;
2948 for (i=1; i<nArgs; i++)
2949 if (!readStringArg(argList[i], &btnLabels[i-1], stringStorage[i],
2950 errMsg))
2951 return False;
2952 if (nArgs == 1) {
2953 btnLabels[0] = "Dismiss";
2954 nBtns = 1;
2955 } else
2956 nBtns = nArgs - 1;
2958 /* Create the selection box dialog widget and its dialog shell parent */
2959 ac = 0;
2960 XtSetArg(al[ac], XmNtitle, " "); ac++;
2961 XtSetArg(al[ac], XmNselectionLabelString, s1=MKSTRING(message)); ac++;
2962 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0]));
2963 ac++;
2964 dialog = CreatePromptDialog(window->shell, "macroStringDialog", al, ac);
2965 XmStringFree(s1);
2966 XmStringFree(s2);
2967 AddMotifCloseCallback(XtParent(dialog), stringDialogCloseCB, window);
2968 XtAddCallback(dialog, XmNokCallback, stringDialogBtnCB, window);
2969 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
2970 XmNuserData, (XtPointer)1, NULL);
2971 cmdData->dialog = dialog;
2973 /* Unmanage unneded widgets */
2974 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
2975 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
2977 /* Make callback for the unmanaged cancel button (which can
2978 still get executed via the esc key) activate close box action */
2979 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
2980 XmNactivateCallback, stringDialogCloseCB, window);
2982 /* Add user specified buttons (1st is already done). Selection box
2983 requires a place-holder widget to be added before buttons can be
2984 added, that's what the separator below is for */
2985 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
2986 for (i=1; i<nBtns; i++) {
2987 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
2988 XmNlabelString, s1=XmStringCreateSimple(btnLabels[i]),
2989 XmNuserData, (XtPointer)(i+1), NULL);
2990 XtAddCallback(btn, XmNactivateCallback, stringDialogBtnCB, window);
2991 XmStringFree(s1);
2994 #ifdef LESSTIF_VERSION
2995 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
2996 the escape key for closing the dialog (probably because the
2997 cancel button is not managed). */
2998 XtAddEventHandler(dialog, KeyPressMask, False, stringDialogEscCB,
2999 (XtPointer)window);
3000 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3001 True, GrabModeAsync, GrabModeAsync);
3002 #endif /* LESSTIF_VERSION */
3004 /* Put up the dialog */
3005 ManageDialogCenteredOnPointer(dialog);
3007 /* Stop macro execution until the dialog is complete */
3008 PreemptMacro();
3010 /* Return placeholder result. Value will be changed by button callback */
3011 result->tag = INT_TAG;
3012 result->val.n = 0;
3013 return True;
3016 static void stringDialogBtnCB(Widget w, XtPointer clientData,
3017 XtPointer callData)
3019 WindowInfo *window = (WindowInfo *)clientData;
3020 macroCmdInfo *cmdData = window->macroCmdData;
3021 XtPointer userData;
3022 DataValue retVal;
3023 char *text;
3024 int btnNum;
3026 /* shouldn't happen, but would crash if it did */
3027 if (cmdData == NULL)
3028 return;
3030 /* Return the string entered in the selection text area */
3031 text = XmTextGetString(XmSelectionBoxGetChild(cmdData->dialog,
3032 XmDIALOG_TEXT));
3033 retVal.tag = STRING_TAG;
3034 AllocNStringCpy(&retVal.val.str, text);
3035 XtFree(text);
3036 ModifyReturnedValue(cmdData->context, retVal);
3038 /* Find the index of the button which was pressed (stored in the userData
3039 field of the button widget). The 1st button, being a gadget, is not
3040 returned in w. */
3041 if (XtClass(w) == xmPushButtonWidgetClass) {
3042 XtVaGetValues(w, XmNuserData, &userData, NULL);
3043 btnNum = (int)userData;
3044 } else
3045 btnNum = 1;
3047 /* Return the button number in the global variable $string_dialog_button */
3048 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3049 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = btnNum;
3051 /* Pop down the dialog */
3052 XtDestroyWidget(XtParent(cmdData->dialog));
3053 cmdData->dialog = NULL;
3055 /* Continue preempted macro execution */
3056 ResumeMacroExecution(window);
3059 static void stringDialogCloseCB(Widget w, XtPointer clientData,
3060 XtPointer callData)
3062 WindowInfo *window = (WindowInfo *)clientData;
3063 macroCmdInfo *cmdData = window->macroCmdData;
3064 DataValue retVal;
3066 /* shouldn't happen, but would crash if it did */
3067 if (cmdData == NULL)
3068 return;
3070 /* Return an empty string */
3071 retVal.tag = STRING_TAG;
3072 retVal.val.str.rep = PERM_ALLOC_STR("");
3073 retVal.val.str.len = 0;
3074 ModifyReturnedValue(cmdData->context, retVal);
3076 /* Return button number 0 in the global variable $string_dialog_button */
3077 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG;
3078 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = 0;
3080 /* Pop down the dialog */
3081 XtDestroyWidget(XtParent(cmdData->dialog));
3082 cmdData->dialog = NULL;
3084 /* Continue preempted macro execution */
3085 ResumeMacroExecution(window);
3088 #ifdef LESSTIF_VERSION
3089 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3090 Boolean *cont)
3092 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3093 return;
3094 if (clientData != NULL) {
3095 stringDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3097 *cont = False;
3099 #endif /* LESSTIF_VERSION */
3102 ** A subroutine to put up a calltip
3103 ** First arg is either text to be displayed or a key for tip/tag lookup.
3104 ** Optional second arg is the buffer position beneath which to display the
3105 ** upper-left corner of the tip. Default (or -1) puts it under the cursor.
3106 ** Additional optional arguments:
3107 ** "tipText": (default) Indicates first arg is text to be displayed in tip.
3108 ** "tipKey": Indicates first arg is key in calltips database. If key
3109 ** is not found in tip database then the tags database is also
3110 ** searched.
3111 ** "tagKey": Indicates first arg is key in tags database. (Skips
3112 ** search in calltips database.)
3113 ** "center": Horizontally center the calltip at the position
3114 ** "right": Put the right edge of the calltip at the position
3115 ** "center" and "right" cannot both be specified.
3116 ** "above": Place the calltip above the position
3117 ** "strict": Don't move the calltip to keep it on-screen and away
3118 ** from the cursor's line.
3120 ** Returns the new calltip's ID on success, 0 on failure.
3122 ** Does this need to go on IgnoredActions? I don't think so, since
3123 ** showing a calltip may be part of the action you want to learn.
3125 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3126 DataValue *result, char **errMsg)
3128 char stringStorage[TYPE_INT_STR_SIZE(int)], *tipText, *txtArg;
3129 Boolean anchored = False, lookup = True;
3130 int mode = -1, i;
3131 int anchorPos, hAlign = TIP_LEFT, vAlign = TIP_BELOW,
3132 alignMode = TIP_SLOPPY;
3134 /* Read and check the string */
3135 if (nArgs < 1) {
3136 *errMsg = "%s subroutine called with too few arguments";
3137 return False;
3139 if (nArgs > 6) {
3140 *errMsg = "%s subroutine called with too many arguments";
3141 return False;
3144 /* Read the tip text or key */
3145 if (!readStringArg(argList[0], &tipText, stringStorage, errMsg))
3146 return False;
3148 /* Read the anchor position (-1 for unanchored) */
3149 if (nArgs > 1) {
3150 if (!readIntArg(argList[1], &anchorPos, errMsg))
3151 return False;
3152 } else {
3153 anchorPos = -1;
3155 if (anchorPos >= 0) anchored = True;
3157 /* Any further args are directives for relative positioning */
3158 for (i = 2; i < nArgs; ++i) {
3159 if (!readStringArg(argList[i], &txtArg, stringStorage, errMsg)){
3160 return False;
3162 switch( txtArg[0] ) {
3163 case 'c':
3164 if (strcmp(txtArg, "center"))
3165 goto bad_arg;
3166 hAlign = TIP_CENTER;
3167 break;
3168 case 'r':
3169 if (strcmp(txtArg, "right"))
3170 goto bad_arg;
3171 hAlign = TIP_RIGHT;
3172 break;
3173 case 'a':
3174 if (strcmp(txtArg, "above"))
3175 goto bad_arg;
3176 vAlign = TIP_ABOVE;
3177 break;
3178 case 's':
3179 if (strcmp(txtArg, "strict"))
3180 goto bad_arg;
3181 alignMode = TIP_STRICT;
3182 break;
3183 case 't':
3184 if (!strcmp(txtArg, "tipText"))
3185 mode = -1;
3186 else if (!strcmp(txtArg, "tipKey"))
3187 mode = TIP;
3188 else if (!strcmp(txtArg, "tagKey"))
3189 mode = TIP_FROM_TAG;
3190 else
3191 goto bad_arg;
3192 break;
3193 default:
3194 goto bad_arg;
3198 result->tag = INT_TAG;
3199 if (mode < 0) lookup = False;
3200 /* Look up (maybe) a calltip and display it */
3201 result->val.n = ShowTipString( window, tipText, anchored, anchorPos, lookup,
3202 mode, hAlign, vAlign, alignMode );
3204 return True;
3206 bad_arg:
3207 /* This is how the (more informative) global var. version would work,
3208 assuming there was a global buffer called msg. */
3209 /* sprintf(msg, "unrecognized argument to %%s: \"%s\"", txtArg);
3210 *errMsg = msg; */
3211 *errMsg = "unrecognized argument to %s";
3212 return False;
3216 ** A subroutine to kill the current calltip
3218 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs,
3219 DataValue *result, char **errMsg)
3221 int calltipID = 0;
3223 if (nArgs > 1) {
3224 *errMsg = "%s subroutine called with too many arguments";
3225 return False;
3227 if (nArgs > 0) {
3228 if (!readIntArg(argList[0], &calltipID, errMsg))
3229 return False;
3232 KillCalltip( window, calltipID );
3234 result->tag = NO_TAG;
3235 return True;
3239 * A subroutine to get the ID of the current calltip, or 0 if there is none.
3241 static int calltipIDMV(WindowInfo *window, DataValue *argList,
3242 int nArgs, DataValue *result, char **errMsg)
3244 result->tag = INT_TAG;
3245 result->val.n = GetCalltipID(window, 0);
3246 return True;
3249 /* T Balinski */
3250 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs,
3251 DataValue *result, char **errMsg)
3253 macroCmdInfo *cmdData;
3254 char stringStorage[9][TYPE_INT_STR_SIZE(int)];
3255 char *btnLabels[sizeof(stringStorage)/sizeof(*stringStorage)];
3256 char *message, *text;
3257 Widget dialog, btn;
3258 int i, nBtns;
3259 XmString s1, s2;
3260 long nlines = 0;
3261 char *p, *old_p, **text_lines, *tmp;
3262 int tmp_len;
3263 int n, is_last;
3264 XmString *test_strings;
3265 int tabDist;
3266 Arg al[20];
3267 int ac;
3270 /* Ignore the focused window passed as the function argument and put
3271 the dialog up over the window which is executing the macro */
3272 window = MacroRunWindow();
3273 cmdData = window->macroCmdData;
3275 /* Dialogs require macro to be suspended and interleaved with other macros.
3276 This subroutine can't be run if macro execution can't be interrupted */
3277 if (!cmdData) {
3278 *errMsg = "%s can't be called from non-suspendable context";
3279 return False;
3282 /* Read and check the arguments. The first being the dialog message,
3283 and the rest being the button labels */
3284 if (nArgs < 2) {
3285 *errMsg = "%s subroutine called with no message, string or arguments";
3286 return False;
3288 if (nArgs > sizeof(stringStorage)/sizeof(*stringStorage)) {
3289 *errMsg = "%s subroutine called with too many arguments";
3290 return False;
3293 if (!readStringArg(argList[0], &message, stringStorage[0], errMsg))
3294 return False;
3296 if (!readStringArg(argList[1], &text, stringStorage[0], errMsg))
3297 return False;
3299 if (!text || text[0] == '\0') {
3300 *errMsg = "%s subroutine called with empty list data";
3301 return False;
3304 for (i=2; i<nArgs; i++)
3305 if (!readStringArg(argList[i], &btnLabels[i-2], stringStorage[i],
3306 errMsg))
3307 return False;
3308 if (nArgs == 2) {
3309 btnLabels[0] = "Dismiss";
3310 nBtns = 1;
3311 } else
3312 nBtns = nArgs - 2;
3314 /* count the lines in the text - add one for unterminated last line */
3315 nlines = 1;
3316 for (p = text; *p; p++)
3317 if (*p == '\n')
3318 nlines++;
3320 /* now set up arrays of pointers to lines */
3321 /* test_strings to hold the display strings (tab expanded) */
3322 /* text_lines to hold the original text lines (without the '\n's) */
3323 test_strings = (XmString *) XtMalloc(sizeof(XmString) * nlines);
3324 text_lines = (char **)XtMalloc(sizeof(char *) * (nlines + 1));
3325 for (n = 0; n < nlines; n++) {
3326 test_strings[n] = (XmString)0;
3327 text_lines[n] = (char *)0;
3329 text_lines[n] = (char *)0; /* make sure this is a null-terminated table */
3331 /* pick up the tabDist value */
3332 tabDist = window->buffer->tabDist;
3334 /* load the table */
3335 n = 0;
3336 is_last = 0;
3337 p = old_p = text;
3338 tmp_len = 0; /* current allocated size of temporary buffer tmp */
3339 tmp = malloc(1); /* temporary buffer into which to expand tabs */
3340 do {
3341 is_last = (*p == '\0');
3342 if (*p == '\n' || is_last) {
3343 *p = '\0';
3344 if (strlen(old_p) > 0) { /* only include non-empty lines */
3345 char *s, *t;
3346 int l;
3348 /* save the actual text line in text_lines[n] */
3349 text_lines[n] = (char *)XtMalloc(strlen(old_p) + 1);
3350 strcpy(text_lines[n], old_p);
3352 /* work out the tabs expanded length */
3353 for (s = old_p, l = 0; *s; s++)
3354 l += (*s == '\t') ? tabDist - (l % tabDist) : 1;
3356 /* verify tmp is big enough then tab-expand old_p into tmp */
3357 if (l > tmp_len)
3358 tmp = realloc(tmp, (tmp_len = l) + 1);
3359 for (s = old_p, t = tmp, l = 0; *s; s++) {
3360 if (*s == '\t') {
3361 for (i = tabDist - (l % tabDist); i--; l++)
3362 *t++ = ' ';
3364 else {
3365 *t++ = *s;
3366 l++;
3369 *t = '\0';
3370 /* that's it: tmp is the tab-expanded version of old_p */
3371 test_strings[n] = MKSTRING(tmp);
3372 n++;
3374 old_p = p + 1;
3375 if (!is_last)
3376 *p = '\n'; /* put back our newline */
3378 p++;
3379 } while (!is_last);
3381 free(tmp); /* don't need this anymore */
3382 nlines = n;
3383 if (nlines == 0) {
3384 test_strings[0] = MKSTRING("");
3385 nlines = 1;
3388 /* Create the selection box dialog widget and its dialog shell parent */
3389 ac = 0;
3390 XtSetArg(al[ac], XmNtitle, " "); ac++;
3391 XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++;
3392 XtSetArg(al[ac], XmNlistItems, test_strings); ac++;
3393 XtSetArg(al[ac], XmNlistItemCount, nlines); ac++;
3394 XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++;
3395 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabels[0])); ac++;
3396 dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac);
3397 AddMotifCloseCallback(XtParent(dialog), listDialogCloseCB, window);
3398 XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window);
3399 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON),
3400 XmNuserData, (XtPointer)1, NULL);
3401 XmStringFree(s1);
3402 XmStringFree(s2);
3403 cmdData->dialog = dialog;
3405 /* forget lines stored in list */
3406 while (n--)
3407 XmStringFree(test_strings[n]);
3408 XtFree((char *)test_strings);
3410 /* modify the list */
3411 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST),
3412 XmNselectionPolicy, XmSINGLE_SELECT,
3413 XmNuserData, (XtPointer)text_lines, NULL);
3415 /* Unmanage unneeded widgets */
3416 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
3417 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
3418 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
3419 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
3420 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));
3422 /* Make callback for the unmanaged cancel button (which can
3423 still get executed via the esc key) activate close box action */
3424 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON),
3425 XmNactivateCallback, listDialogCloseCB, window);
3427 /* Add user specified buttons (1st is already done). Selection box
3428 requires a place-holder widget to be added before buttons can be
3429 added, that's what the separator below is for */
3430 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL);
3431 for (i=1; i<nBtns; i++) {
3432 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog,
3433 XmNlabelString, s1=XmStringCreateSimple(btnLabels[i]),
3434 XmNuserData, (XtPointer)(i+1), NULL);
3435 XtAddCallback(btn, XmNactivateCallback, listDialogBtnCB, window);
3436 XmStringFree(s1);
3439 #ifdef LESSTIF_VERSION
3440 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle
3441 the escape key for closing the dialog. */
3442 XtAddEventHandler(dialog, KeyPressMask, False, listDialogEscCB,
3443 (XtPointer)window);
3444 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
3445 True, GrabModeAsync, GrabModeAsync);
3446 #endif /* LESSTIF_VERSION */
3448 /* Put up the dialog */
3449 ManageDialogCenteredOnPointer(dialog);
3451 /* Stop macro execution until the dialog is complete */
3452 PreemptMacro();
3454 /* Return placeholder result. Value will be changed by button callback */
3455 result->tag = INT_TAG;
3456 result->val.n = 0;
3457 return True;
3460 static void listDialogBtnCB(Widget w, XtPointer clientData,
3461 XtPointer callData)
3463 WindowInfo *window = (WindowInfo *)clientData;
3464 macroCmdInfo *cmdData = window->macroCmdData;
3465 XtPointer userData;
3466 DataValue retVal;
3467 char *text;
3468 char **text_lines;
3469 int btnNum;
3470 int n_sel, *seltable, sel_index = 0;
3471 Widget theList;
3472 size_t length;
3474 /* shouldn't happen, but would crash if it did */
3475 if (cmdData == NULL)
3476 return;
3478 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3479 /* Return the string selected in the selection list area */
3480 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3481 if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) {
3482 n_sel = 0;
3484 else {
3485 sel_index = seltable[0] - 1;
3486 XtFree((XtPointer)seltable);
3489 if (!n_sel) {
3490 text = PERM_ALLOC_STR("");
3491 length = 0;
3493 else {
3494 length = strlen((char *)text_lines[sel_index]);
3495 text = AllocString(length + 1);
3496 strcpy(text, text_lines[sel_index]);
3499 /* don't need text_lines anymore: free it */
3500 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3501 XtFree((XtPointer)text_lines[sel_index]);
3502 XtFree((XtPointer)text_lines);
3504 retVal.tag = STRING_TAG;
3505 retVal.val.str.rep = text;
3506 retVal.val.str.len = length;
3507 ModifyReturnedValue(cmdData->context, retVal);
3509 /* Find the index of the button which was pressed (stored in the userData
3510 field of the button widget). The 1st button, being a gadget, is not
3511 returned in w. */
3512 if (XtClass(w) == xmPushButtonWidgetClass) {
3513 XtVaGetValues(w, XmNuserData, &userData, NULL);
3514 btnNum = (int)userData;
3515 } else
3516 btnNum = 1;
3518 /* Return the button number in the global variable $list_dialog_button */
3519 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3520 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = btnNum;
3522 /* Pop down the dialog */
3523 XtDestroyWidget(XtParent(cmdData->dialog));
3524 cmdData->dialog = NULL;
3526 /* Continue preempted macro execution */
3527 ResumeMacroExecution(window);
3530 static void listDialogCloseCB(Widget w, XtPointer clientData,
3531 XtPointer callData)
3533 WindowInfo *window = (WindowInfo *)clientData;
3534 macroCmdInfo *cmdData = window->macroCmdData;
3535 DataValue retVal;
3536 char **text_lines;
3537 int sel_index;
3538 Widget theList;
3540 /* shouldn't happen, but would crash if it did */
3541 if (cmdData == NULL)
3542 return;
3544 /* don't need text_lines anymore: retrieve it then free it */
3545 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST);
3546 XtVaGetValues(theList, XmNuserData, &text_lines, NULL);
3547 for (sel_index = 0; text_lines[sel_index]; sel_index++)
3548 XtFree((XtPointer)text_lines[sel_index]);
3549 XtFree((XtPointer)text_lines);
3551 /* Return an empty string */
3552 retVal.tag = STRING_TAG;
3553 retVal.val.str.rep = PERM_ALLOC_STR("");
3554 retVal.val.str.len = 0;
3555 ModifyReturnedValue(cmdData->context, retVal);
3557 /* Return button number 0 in the global variable $list_dialog_button */
3558 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG;
3559 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = 0;
3561 /* Pop down the dialog */
3562 XtDestroyWidget(XtParent(cmdData->dialog));
3563 cmdData->dialog = NULL;
3565 /* Continue preempted macro execution */
3566 ResumeMacroExecution(window);
3568 /* T Balinski End */
3570 #ifdef LESSTIF_VERSION
3571 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event,
3572 Boolean *cont)
3574 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
3575 return;
3576 if (clientData != NULL) {
3577 listDialogCloseCB(w, (WindowInfo *)clientData, NULL);
3579 *cont = False;
3581 #endif /* LESSTIF_VERSION */
3584 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs,
3585 DataValue *result, char **errMsg)
3587 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3588 char *leftStr, *rightStr, *argStr;
3589 int considerCase = True;
3590 int i;
3591 int compareResult;
3593 if (nArgs < 2) {
3594 return(wrongNArgsErr(errMsg));
3596 if (!readStringArg(argList[0], &leftStr, stringStorage[0], errMsg))
3597 return False;
3598 if (!readStringArg(argList[1], &rightStr, stringStorage[1], errMsg))
3599 return False;
3600 for (i = 2; i < nArgs; ++i) {
3601 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg))
3602 return False;
3603 else if (!strcmp(argStr, "case"))
3604 considerCase = True;
3605 else if (!strcmp(argStr, "nocase"))
3606 considerCase = False;
3607 else {
3608 *errMsg = "Unrecognized argument to %s";
3609 return False;
3612 if (considerCase) {
3613 compareResult = strcmp(leftStr, rightStr);
3614 compareResult = (compareResult > 0) ? 1 : ((compareResult < 0) ? -1 : 0);
3616 else {
3617 compareResult = strCaseCmp(leftStr, rightStr);
3619 result->tag = INT_TAG;
3620 result->val.n = compareResult;
3621 return True;
3625 ** This function is intended to split strings into an array of substrings
3626 ** Importatnt note: It should always return at least one entry with key 0
3627 ** split("", ",") result[0] = ""
3628 ** split("1,2", ",") result[0] = "1" result[1] = "2"
3629 ** split("1,2,", ",") result[0] = "1" result[1] = "2" result[2] = ""
3631 ** This behavior is specifically important when used to break up
3632 ** array sub-scripts
3635 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs,
3636 DataValue *result, char **errMsg)
3638 char stringStorage[3][TYPE_INT_STR_SIZE(int)];
3639 char *sourceStr, *splitStr, *typeSplitStr;
3640 int searchType, beginPos, foundStart, foundEnd, strLength;
3641 int found, elementEnd, indexNum;
3642 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
3643 DataValue element;
3644 int elementLen;
3646 if (nArgs < 2) {
3647 return(wrongNArgsErr(errMsg));
3649 if (!readStringArg(argList[0], &sourceStr, stringStorage[0], errMsg)) {
3650 *errMsg = "first argument must be a string: %s";
3651 return(False);
3653 if (!readStringArg(argList[1], &splitStr, stringStorage[1], errMsg)) {
3654 splitStr = NULL;
3656 else {
3657 if (splitStr[0] == 0) {
3658 splitStr = NULL;
3661 if (splitStr == NULL) {
3662 *errMsg = "second argument must be a non-empty string: %s";
3663 return(False);
3665 if (nArgs > 2 && readStringArg(argList[2], &typeSplitStr, stringStorage[2], errMsg)) {
3666 if (!StringToSearchType(typeSplitStr, &searchType)) {
3667 *errMsg = "unrecognized argument to %s";
3668 return(False);
3671 else {
3672 searchType = SEARCH_LITERAL;
3675 result->tag = ARRAY_TAG;
3676 result->val.arrayPtr = ArrayNew();
3678 beginPos = 0;
3679 indexNum = 0;
3680 strLength = strlen(sourceStr);
3681 found = 1;
3682 while (found && beginPos < strLength) {
3683 sprintf(indexStr, "%d", indexNum);
3684 allocIndexStr = AllocString(strlen(indexStr) + 1);
3685 if (!allocIndexStr) {
3686 *errMsg = "array element failed to allocate key: %s";
3687 return(False);
3689 strcpy(allocIndexStr, indexStr);
3690 found = SearchString(sourceStr, splitStr, SEARCH_FORWARD, searchType,
3691 False, beginPos, &foundStart, &foundEnd,
3692 NULL, NULL, GetWindowDelimiters(window));
3693 elementEnd = found ? foundStart : strLength;
3694 elementLen = elementEnd - beginPos;
3695 element.tag = STRING_TAG;
3696 if (!AllocNStringNCpy(&element.val.str, &sourceStr[beginPos], elementLen)) {
3697 *errMsg = "failed to allocate element value: %s";
3698 return(False);
3701 if (!ArrayInsert(result, allocIndexStr, &element)) {
3702 M_ARRAY_INSERT_FAILURE();
3705 beginPos = found ? foundEnd : strLength;
3706 ++indexNum;
3708 if (found) {
3709 sprintf(indexStr, "%d", indexNum);
3710 allocIndexStr = AllocString(strlen(indexStr) + 1);
3711 if (!allocIndexStr) {
3712 *errMsg = "array element failed to allocate key: %s";
3713 return(False);
3715 strcpy(allocIndexStr, indexStr);
3716 element.tag = STRING_TAG;
3717 element.val.str.rep = PERM_ALLOC_STR("");
3718 element.val.str.len = 0;
3720 if (!ArrayInsert(result, allocIndexStr, &element)) {
3721 M_ARRAY_INSERT_FAILURE();
3724 return(True);
3728 ** Set the backlighting string resource for the current window. If no parameter
3729 ** is passed or the value "default" is passed, it attempts to set the preference
3730 ** value of the resource. If the empty string is passed, the backlighting string
3731 ** will be cleared, turning off backlighting.
3733 /* DISABLED for 5.4
3734 static int setBacklightStringMS(WindowInfo *window, DataValue *argList,
3735 int nArgs, DataValue *result, char **errMsg)
3737 char *backlightString;
3739 if (nArgs == 0) {
3740 backlightString = GetPrefBacklightCharTypes();
3742 else if (nArgs == 1) {
3743 if (argList[0].tag != STRING_TAG) {
3744 *errMsg = "%s not called with a string parameter";
3745 return False;
3747 backlightString = argList[0].val.str.rep;
3749 else
3750 return wrongNArgsErr(errMsg);
3752 if (strcmp(backlightString, "default") == 0)
3753 backlightString = GetPrefBacklightCharTypes();
3754 if (backlightString && *backlightString == '\0') / * empty string param * /
3755 backlightString = NULL; / * turns of backlighting * /
3757 SetBacklightChars(window, backlightString);
3758 return True;
3759 } */
3761 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs,
3762 DataValue *result, char **errMsg)
3764 result->tag = INT_TAG;
3765 result->val.n = TextGetCursorPos(window->lastFocus);
3766 return True;
3769 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs,
3770 DataValue *result, char **errMsg)
3772 int line, cursorPos, colNum;
3774 result->tag = INT_TAG;
3775 cursorPos = TextGetCursorPos(window->lastFocus);
3776 if (!TextPosToLineAndCol(window->lastFocus, cursorPos, &line, &colNum))
3777 line = BufCountLines(window->buffer, 0, cursorPos) + 1;
3778 result->val.n = line;
3779 return True;
3782 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs,
3783 DataValue *result, char **errMsg)
3785 textBuffer *buf = window->buffer;
3786 int cursorPos;
3788 result->tag = INT_TAG;
3789 cursorPos = TextGetCursorPos(window->lastFocus);
3790 result->val.n = BufCountDispChars(buf, BufStartOfLine(buf, cursorPos),
3791 cursorPos);
3792 return True;
3795 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs,
3796 DataValue *result, char **errMsg)
3798 result->tag = STRING_TAG;
3799 AllocNStringCpy(&result->val.str, window->filename);
3800 return True;
3803 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs,
3804 DataValue *result, char **errMsg)
3806 result->tag = STRING_TAG;
3807 AllocNStringCpy(&result->val.str, window->path);
3808 return True;
3811 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs,
3812 DataValue *result, char **errMsg)
3814 result->tag = INT_TAG;
3815 result->val.n = window->buffer->length;
3816 return True;
3819 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs,
3820 DataValue *result, char **errMsg)
3822 result->tag = INT_TAG;
3823 result->val.n = window->buffer->primary.selected ?
3824 window->buffer->primary.start : -1;
3825 return True;
3828 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs,
3829 DataValue *result, char **errMsg)
3831 result->tag = INT_TAG;
3832 result->val.n = window->buffer->primary.selected ?
3833 window->buffer->primary.end : -1;
3834 return True;
3837 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs,
3838 DataValue *result, char **errMsg)
3840 selection *sel = &window->buffer->primary;
3842 result->tag = INT_TAG;
3843 result->val.n = sel->selected && sel->rectangular ? sel->rectStart : -1;
3844 return True;
3847 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs,
3848 DataValue *result, char **errMsg)
3850 selection *sel = &window->buffer->primary;
3852 result->tag = INT_TAG;
3853 result->val.n = sel->selected && sel->rectangular ? sel->rectEnd : -1;
3854 return True;
3857 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs,
3858 DataValue *result, char **errMsg)
3860 int margin, nCols;
3862 XtVaGetValues(window->textArea, textNcolumns, &nCols,
3863 textNwrapMargin, &margin, NULL);
3864 result->tag = INT_TAG;
3865 result->val.n = margin == 0 ? nCols : margin;
3866 return True;
3869 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs,
3870 DataValue *result, char **errMsg)
3872 result->tag = INT_TAG;
3873 result->val.n = window->showStats ? 1 : 0;
3874 return True;
3877 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs,
3878 DataValue *result, char **errMsg)
3880 result->tag = INT_TAG;
3881 result->val.n = window->showISearchLine ? 1 : 0;
3882 return True;
3885 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs,
3886 DataValue *result, char **errMsg)
3888 result->tag = INT_TAG;
3889 result->val.n = window->showLineNumbers ? 1 : 0;
3890 return True;
3893 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs,
3894 DataValue *result, char **errMsg)
3896 char *res = NULL;
3898 switch (window->indentStyle) {
3899 case NO_AUTO_INDENT:
3900 res = PERM_ALLOC_STR("off");
3901 break;
3902 case AUTO_INDENT:
3903 res = PERM_ALLOC_STR("on");
3904 break;
3905 case SMART_INDENT:
3906 res = PERM_ALLOC_STR("smart");
3907 break;
3908 default:
3909 *errMsg = "Invalid indent style value encountered in %s";
3910 return False;
3911 break;
3913 result->tag = STRING_TAG;
3914 result->val.str.rep = res;
3915 result->val.str.len = strlen(res);
3916 return True;
3919 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs,
3920 DataValue *result, char **errMsg)
3922 char *res = NULL;
3924 switch (window->wrapMode) {
3925 case NO_WRAP:
3926 res = PERM_ALLOC_STR("none");
3927 break;
3928 case NEWLINE_WRAP:
3929 res = PERM_ALLOC_STR("auto");
3930 break;
3931 case CONTINUOUS_WRAP:
3932 res = PERM_ALLOC_STR("continuous");
3933 break;
3934 default:
3935 *errMsg = "Invalid wrap style value encountered in %s";
3936 return False;
3937 break;
3939 result->tag = STRING_TAG;
3940 result->val.str.rep = res;
3941 result->val.str.len = strlen(res);
3942 return True;
3945 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs,
3946 DataValue *result, char **errMsg)
3948 result->tag = INT_TAG;
3949 result->val.n = window->highlightSyntax ? 1 : 0;
3950 return True;
3953 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs,
3954 DataValue *result, char **errMsg)
3956 result->tag = INT_TAG;
3957 result->val.n = window->saveOldVersion ? 1 : 0;
3958 return True;
3961 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs,
3962 DataValue *result, char **errMsg)
3964 result->tag = INT_TAG;
3965 result->val.n = window->autoSave ? 1 : 0;
3966 return True;
3969 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs,
3970 DataValue *result, char **errMsg)
3972 char *res = NULL;
3974 switch (window->showMatchingStyle) {
3975 case NO_FLASH:
3976 res = PERM_ALLOC_STR(NO_FLASH_STRING);
3977 break;
3978 case FLASH_DELIMIT:
3979 res = PERM_ALLOC_STR(FLASH_DELIMIT_STRING);
3980 break;
3981 case FLASH_RANGE:
3982 res = PERM_ALLOC_STR(FLASH_RANGE_STRING);
3983 break;
3984 default:
3985 *errMsg = "Invalid match flashing style value encountered in %s";
3986 return False;
3987 break;
3989 result->tag = STRING_TAG;
3990 result->val.str.rep = res;
3991 result->val.str.len = strlen(res);
3992 return True;
3995 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs,
3996 DataValue *result, char **errMsg)
3998 result->tag = INT_TAG;
3999 result->val.n = window->overstrike ? 1 : 0;
4000 return True;
4003 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs,
4004 DataValue *result, char **errMsg)
4006 result->tag = INT_TAG;
4007 result->val.n = (IS_ANY_LOCKED(window->lockReasons)) ? 1 : 0;
4008 return True;
4011 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs,
4012 DataValue *result, char **errMsg)
4014 result->tag = INT_TAG;
4015 result->val.n = (IS_USER_LOCKED(window->lockReasons)) ? 1 : 0;
4016 return True;
4019 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs,
4020 DataValue *result, char **errMsg)
4022 char *res = NULL;
4024 switch (window->fileFormat) {
4025 case UNIX_FILE_FORMAT:
4026 res = PERM_ALLOC_STR("unix");
4027 break;
4028 case DOS_FILE_FORMAT:
4029 res = PERM_ALLOC_STR("dos");
4030 break;
4031 case MAC_FILE_FORMAT:
4032 res = PERM_ALLOC_STR("macintosh");
4033 break;
4034 default:
4035 *errMsg = "Invalid linefeed style value encountered in %s";
4036 return False;
4038 result->tag = STRING_TAG;
4039 result->val.str.rep = res;
4040 result->val.str.len = strlen(res);
4041 return True;
4044 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4045 DataValue *result, char **errMsg)
4047 result->tag = STRING_TAG;
4048 AllocNStringCpy(&result->val.str, window->fontName);
4049 return True;
4052 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4053 DataValue *result, char **errMsg)
4055 result->tag = STRING_TAG;
4056 AllocNStringCpy(&result->val.str, window->italicFontName);
4057 return True;
4060 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs,
4061 DataValue *result, char **errMsg)
4063 result->tag = STRING_TAG;
4064 AllocNStringCpy(&result->val.str, window->boldFontName);
4065 return True;
4068 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs,
4069 DataValue *result, char **errMsg)
4071 result->tag = STRING_TAG;
4072 AllocNStringCpy(&result->val.str, window->boldItalicFontName);
4073 return True;
4076 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs,
4077 DataValue *result, char **errMsg)
4079 result->tag = STRING_TAG;
4080 result->val.str.rep = PERM_ALLOC_STR(ARRAY_DIM_SEP);
4081 result->val.str.len = strlen(result->val.str.rep);
4082 return True;
4085 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4086 DataValue *result, char **errMsg)
4088 result->tag = INT_TAG;
4089 result->val.n = TextGetMinFontWidth(window->textArea, window->highlightSyntax);
4090 return True;
4093 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4094 DataValue *result, char **errMsg)
4096 result->tag = INT_TAG;
4097 result->val.n = TextGetMaxFontWidth(window->textArea, window->highlightSyntax);
4098 return True;
4101 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs,
4102 DataValue *result, char **errMsg)
4104 result->tag = INT_TAG;
4105 result->val.n = TextFirstVisibleLine(window->lastFocus);
4106 return True;
4109 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs,
4110 DataValue *result, char **errMsg)
4112 result->tag = INT_TAG;
4113 result->val.n = TextNumVisibleLines(window->lastFocus);
4114 return True;
4117 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs,
4118 DataValue *result, char **errMsg)
4120 result->tag = INT_TAG;
4121 result->val.n = TextVisibleWidth(window->lastFocus);
4122 return True;
4125 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs,
4126 DataValue *result, char **errMsg)
4128 result->tag = INT_TAG;
4129 result->val.n = WidgetToPaneIndex(window, window->lastFocus) + 1;
4130 return True;
4133 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs,
4134 DataValue *result, char **errMsg)
4136 result->tag = INT_TAG;
4137 result->val.n = window->nPanes + 1;
4138 return True;
4141 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs,
4142 DataValue *result, char **errMsg)
4144 result->tag = ARRAY_TAG;
4145 result->val.arrayPtr = NULL;
4146 return True;
4149 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs,
4150 DataValue *result, char **errMsg)
4152 result->tag = STRING_TAG;
4153 AllocNStringCpy(&result->val.str, GetPrefServerName());
4154 return True;
4157 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4158 DataValue *result, char **errMsg)
4160 result->tag = INT_TAG;
4161 result->val.n = window->buffer->tabDist;
4162 return True;
4165 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs,
4166 DataValue *result, char **errMsg)
4168 int dist;
4170 XtVaGetValues(window->textArea, textNemulateTabs, &dist, NULL);
4171 result->tag = INT_TAG;
4172 result->val.n = dist == 0 ? -1 : dist;
4173 return True;
4176 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs,
4177 DataValue *result, char **errMsg)
4179 result->tag = INT_TAG;
4180 result->val.n = window->buffer->useTabs;
4181 return True;
4184 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs,
4185 DataValue *result, char **errMsg)
4187 result->tag = INT_TAG;
4188 result->val.n = window->fileChanged;
4189 return True;
4192 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs,
4193 DataValue *result, char **errMsg)
4195 char *lmName = LanguageModeName(window->languageMode);
4197 if (lmName == NULL)
4198 lmName = "Plain";
4199 result->tag = STRING_TAG;
4200 AllocNStringCpy(&result->val.str, lmName);
4201 return True;
4204 /* DISABLED for 5.4
4205 static int backlightStringMV(WindowInfo *window, DataValue *argList,
4206 int nArgs, DataValue *result, char **errMsg)
4208 char *backlightString = window->backlightCharTypes;
4210 result->tag = STRING_TAG;
4211 if (!backlightString || !window->backlightChars)
4212 backlightString = "";
4213 AllocNStringCpy(&result->val.str, backlightString);
4214 return True;
4215 } */
4217 /* -------------------------------------------------------------------------- */
4220 ** Range set macro variables and functions
4223 static int rangesetListMV(WindowInfo *window, DataValue *argList, int nArgs,
4224 DataValue *result, char **errMsg)
4226 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4227 unsigned char *rangesetList;
4228 char *allocIndexStr;
4229 char indexStr[TYPE_INT_STR_SIZE(int)] ;
4230 int nRangesets, i;
4231 DataValue element;
4233 result->tag = ARRAY_TAG;
4234 result->val.arrayPtr = ArrayNew();
4236 if (rangesetTable == NULL) {
4237 return True;
4240 rangesetList = RangesetGetList(rangesetTable);
4241 nRangesets = strlen((char*)rangesetList);
4242 for(i = 0; i < nRangesets; i++) {
4243 element.tag = INT_TAG;
4244 element.val.n = rangesetList[i];
4246 sprintf(indexStr, "%d", nRangesets - i - 1);
4247 allocIndexStr = AllocString(strlen(indexStr) + 1);
4248 if (allocIndexStr == NULL)
4249 M_FAILURE("Failed to allocate array key in %s");
4250 strcpy(allocIndexStr, indexStr);
4252 if (!ArrayInsert(result, allocIndexStr, &element))
4253 M_FAILURE("Failed to insert array element in %s");
4256 return True;
4261 ** Built-in macro subroutine to create a new rangeset or rangesets.
4262 ** If called with one argument: $1 is the number of rangesets required and
4263 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4264 ** (or an empty array if the requested number of rangesets are not available).
4265 ** If called with no arguments, returns a single rangeset label (not an array),
4266 ** or an empty string if there are no rangesets available.
4268 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs,
4269 DataValue *result, char **errMsg)
4271 int label;
4272 int i, nRangesetsRequired;
4273 DataValue element;
4274 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr;
4276 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4278 if (nArgs > 1)
4279 return wrongNArgsErr(errMsg);
4281 if (rangesetTable == NULL) {
4282 window->buffer->rangesetTable = rangesetTable =
4283 RangesetTableAlloc(window->buffer);
4286 if (nArgs == 0) {
4287 label = RangesetCreate(rangesetTable);
4289 result->tag = INT_TAG;
4290 result->val.n = label;
4291 return True;
4293 else {
4294 if (!readIntArg(argList[0], &nRangesetsRequired, errMsg))
4295 return False;
4297 result->tag = ARRAY_TAG;
4298 result->val.arrayPtr = ArrayNew();
4300 if (nRangesetsRequired > nRangesetsAvailable(rangesetTable))
4301 return True;
4303 for (i = 0; i < nRangesetsRequired; i++) {
4304 element.tag = INT_TAG;
4305 element.val.n = RangesetCreate(rangesetTable);
4307 sprintf(indexStr, "%d", i);
4308 allocIndexStr = AllocString(strlen(indexStr) + 1);
4309 if (!allocIndexStr) {
4310 *errMsg = "Array element failed to allocate key: %s";
4311 return(False);
4313 strcpy(allocIndexStr, indexStr);
4314 ArrayInsert(result, allocIndexStr, &element);
4317 return True;
4323 ** Built-in macro subroutine for forgetting a range set.
4326 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs,
4327 DataValue *result, char **errMsg)
4329 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4330 DataValue *array;
4331 DataValue element;
4332 char keyString[TYPE_INT_STR_SIZE(int)];
4333 int deleteLabels[N_RANGESETS];
4334 int i, arraySize;
4335 int label = 0;
4337 if (nArgs != 1) {
4338 return wrongNArgsErr(errMsg);
4341 if (argList[0].tag == ARRAY_TAG) {
4342 array = &argList[0];
4343 arraySize = ArraySize(array);
4345 if (arraySize > N_RANGESETS) {
4346 M_FAILURE("Too many elements in array in %s");
4349 for (i = 0; i < arraySize; i++) {
4350 sprintf(keyString, "%d", i);
4352 if (!ArrayGet(array, keyString, &element)) {
4353 M_FAILURE("Invalid key in array in %s");
4356 if (!readIntArg(element, &label, errMsg)
4357 || !RangesetLabelOK(label)) {
4358 M_FAILURE("Invalid rangeset label in array in %s");
4361 deleteLabels[i] = label;
4364 for (i = 0; i < arraySize; i++) {
4365 RangesetForget(rangesetTable, deleteLabels[i]);
4369 else {
4370 if (!readIntArg(argList[0], &label, errMsg)
4371 || !RangesetLabelOK(label)) {
4372 M_FAILURE("Invalid rangeset label in %s");
4375 if(rangesetTable != NULL) {
4376 RangesetForget(rangesetTable, label);
4380 /* set up result */
4381 result->tag = NO_TAG;
4382 return True;
4387 ** Built-in macro subroutine for getting all range sets with a specfic name.
4388 ** Arguments are $1: range set name.
4389 ** return value is an array indexed 0 to n, with the rangeset labels as values;
4392 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
4393 DataValue *result, char **errMsg)
4395 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4396 Rangeset *rangeset;
4397 int label;
4398 char *name, *rangeset_name;
4399 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4400 unsigned char *rangesetList;
4401 char *allocIndexStr;
4402 char indexStr[TYPE_INT_STR_SIZE(int)] ;
4403 int nRangesets, i, insertIndex = 0;
4404 DataValue element;
4406 if (nArgs != 1) {
4407 return wrongNArgsErr(errMsg);
4410 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) {
4411 M_FAILURE("First parameter is not a name string in %s");
4414 result->tag = ARRAY_TAG;
4415 result->val.arrayPtr = ArrayNew();
4417 if (rangesetTable == NULL) {
4418 return True;
4421 rangesetList = RangesetGetList(rangesetTable);
4422 nRangesets = strlen((char *)rangesetList);
4423 for (i = 0; i < nRangesets; ++i) {
4424 label = rangesetList[i];
4425 rangeset = RangesetFetch(rangesetTable, label);
4426 if (rangeset) {
4427 rangeset_name = RangesetGetName(rangeset);
4428 if (strcmp(name, rangeset_name ? rangeset_name : "") == 0) {
4429 element.tag = INT_TAG;
4430 element.val.n = label;
4432 sprintf(indexStr, "%d", insertIndex);
4433 allocIndexStr = AllocString(strlen(indexStr) + 1);
4434 if (allocIndexStr == NULL)
4435 M_FAILURE("Failed to allocate array key in %s");
4437 strcpy(allocIndexStr, indexStr);
4439 if (!ArrayInsert(result, allocIndexStr, &element))
4440 M_FAILURE("Failed to insert array element in %s");
4442 ++insertIndex;
4447 return True;
4452 ** Built-in macro subroutine for adding to a range set. Arguments are $1: range
4453 ** set label (one integer), then either (a) $2: source range set label,
4454 ** (b) $2: int start-range, $3: int end-range, (c) nothing (use selection
4455 ** if any to specify range to add - must not be rectangular). Returns the
4456 ** index of the newly added range (cases b and c), or 0 (case a).
4459 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs,
4460 DataValue *result, char **errMsg)
4462 textBuffer *buffer = window->buffer;
4463 RangesetTable *rangesetTable = buffer->rangesetTable;
4464 Rangeset *targetRangeset, *sourceRangeset;
4465 int start, end, isRect, rectStart, rectEnd, maxpos, index;
4466 int label = 0;
4468 if (nArgs < 1 || nArgs > 3)
4469 return wrongNArgsErr(errMsg);
4471 if (!readIntArg(argList[0], &label, errMsg)
4472 || !RangesetLabelOK(label)) {
4473 M_FAILURE("First parameter is an invalid rangeset label in %s");
4476 if (rangesetTable == NULL) {
4477 M_FAILURE("Rangeset does not exist in %s");
4480 targetRangeset = RangesetFetch(rangesetTable, label);
4482 if (targetRangeset == NULL) {
4483 M_FAILURE("Rangeset does not exist in %s");
4486 start = end = -1;
4488 if (nArgs == 1) {
4489 /* pick up current selection in this window */
4490 if (!BufGetSelectionPos(buffer, &start, &end,
4491 &isRect, &rectStart, &rectEnd) || isRect) {
4492 M_FAILURE("Selection missing or rectangular in call to %s");
4494 if (!RangesetAddBetween(targetRangeset, start, end)) {
4495 M_FAILURE("Failure to add selection in %s");
4499 if (nArgs == 2) {
4500 /* add ranges taken from a second set */
4501 if (!readIntArg(argList[1], &label, errMsg)
4502 || !RangesetLabelOK(label)) {
4503 M_FAILURE("Second parameter is an invalid rangeset label in %s");
4506 sourceRangeset = RangesetFetch(rangesetTable, label);
4507 if (sourceRangeset == NULL) {
4508 M_FAILURE("Second rangeset does not exist in %s");
4511 RangesetAdd(targetRangeset, sourceRangeset);
4514 if (nArgs == 3) {
4515 /* add a range bounded by the start and end positions in $2, $3 */
4516 if (!readIntArg(argList[1], &start, errMsg)) {
4517 return False;
4519 if (!readIntArg(argList[2], &end, errMsg)) {
4520 return False;
4523 /* make sure range is in order and fits buffer size */
4524 maxpos = buffer->gapEnd - buffer->gapStart + buffer->length;
4525 if (start < 0) start = 0;
4526 if (start > maxpos) start = maxpos;
4527 if (end < 0) end = 0;
4528 if (end > maxpos) end = maxpos;
4529 if (start > end) {int temp = start; start = end; end = temp;}
4531 if ((start != end) && !RangesetAddBetween(targetRangeset, start, end)) {
4532 M_FAILURE("Failed to add range in %s");
4536 /* (to) which range did we just add? */
4537 if (nArgs != 2 && start >= 0) {
4538 start = (start + end) / 2; /* "middle" of added range */
4539 index = 1 + RangesetFindRangeOfPos(targetRangeset, start, False);
4541 else {
4542 index = 0;
4545 /* set up result */
4546 result->tag = INT_TAG;
4547 result->val.n = index;
4548 return True;
4553 ** Built-in macro subroutine for removing from a range set. Almost identical to
4554 ** rangesetAddMS() - only changes are from RangesetAdd()/RangesetAddBetween()
4555 ** to RangesetSubtract()/RangesetSubtractBetween(), the handling of an
4556 ** undefined destination range, and that it returns no value.
4559 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs,
4560 DataValue *result, char **errMsg)
4562 textBuffer *buffer = window->buffer;
4563 RangesetTable *rangesetTable = buffer->rangesetTable;
4564 Rangeset *targetRangeset, *sourceRangeset;
4565 int start, end, isRect, rectStart, rectEnd, maxpos;
4566 int label = 0;
4568 if (nArgs < 1 || nArgs > 3) {
4569 return wrongNArgsErr(errMsg);
4572 if (!readIntArg(argList[0], &label, errMsg)
4573 || !RangesetLabelOK(label)) {
4574 M_FAILURE("First parameter is an invalid rangeset label in %s");
4577 if (rangesetTable == NULL) {
4578 M_FAILURE("Rangeset does not exist in %s");
4581 targetRangeset = RangesetFetch(rangesetTable, label);
4582 if (targetRangeset == NULL) {
4583 M_FAILURE("Rangeset does not exist in %s");
4586 if (nArgs == 1) {
4587 /* remove current selection in this window */
4588 if (!BufGetSelectionPos(buffer, &start, &end, &isRect, &rectStart, &rectEnd)
4589 || isRect) {
4590 M_FAILURE("Selection missing or rectangular in call to %s");
4592 RangesetRemoveBetween(targetRangeset, start, end);
4595 if (nArgs == 2) {
4596 /* remove ranges taken from a second set */
4597 if (!readIntArg(argList[1], &label, errMsg)
4598 || !RangesetLabelOK(label)) {
4599 M_FAILURE("Second parameter is an invalid rangeset label in %s");
4602 sourceRangeset = RangesetFetch(rangesetTable, label);
4603 if (sourceRangeset == NULL) {
4604 M_FAILURE("Second rangeset does not exist in %s");
4606 RangesetRemove(targetRangeset, sourceRangeset);
4609 if (nArgs == 3) {
4610 /* remove a range bounded by the start and end positions in $2, $3 */
4611 if (!readIntArg(argList[1], &start, errMsg))
4612 return False;
4613 if (!readIntArg(argList[2], &end, errMsg))
4614 return False;
4616 /* make sure range is in order and fits buffer size */
4617 maxpos = buffer->gapEnd - buffer->gapStart + buffer->length;
4618 if (start < 0) start = 0;
4619 if (start > maxpos) start = maxpos;
4620 if (end < 0) end = 0;
4621 if (end > maxpos) end = maxpos;
4622 if (start > end) {int temp = start; start = end; end = temp;}
4624 RangesetRemoveBetween(targetRangeset, start, end);
4627 /* set up result */
4628 result->tag = NO_TAG;
4629 return True;
4634 ** Built-in macro subroutine to invert a range set. Argument is $1: range set
4635 ** label (one alphabetic character). Returns nothing. Fails if range set
4636 ** undefined.
4639 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs,
4640 DataValue *result, char **errMsg)
4643 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4644 Rangeset *rangeset;
4645 int label = 0;
4647 if (nArgs != 1)
4648 return wrongNArgsErr(errMsg);
4650 if (!readIntArg(argList[0], &label, errMsg)
4651 || !RangesetLabelOK(label)) {
4652 M_FAILURE("First parameter is an invalid rangeset label in %s");
4655 if (rangesetTable == NULL) {
4656 M_FAILURE("Rangeset does not exist in %s");
4659 rangeset = RangesetFetch(rangesetTable, label);
4660 if (rangeset == NULL) {
4661 M_FAILURE("Rangeset does not exist in %s");
4664 if (RangesetInverse(rangeset) < 0) {
4665 M_FAILURE("Problem inverting rangeset in %s");
4668 /* set up result */
4669 result->tag = NO_TAG;
4670 return True;
4675 ** Built-in macro subroutine for finding out info about a rangeset. Takes one
4676 ** argument of a rangeset label. Returns an array with the following keys:
4677 ** defined, count, color, mode.
4680 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs,
4681 DataValue *result, char **errMsg)
4683 RangesetTable *rangesetTable = window->buffer->rangesetTable;
4684 Rangeset *rangeset = NULL;
4685 int count, defined;
4686 char *color, *name, *mode;
4687 DataValue element;
4688 int label = 0;
4690 if (nArgs != 1)
4691 return wrongNArgsErr(errMsg);
4693 if (!readIntArg(argList[0], &label, errMsg)
4694 || !RangesetLabelOK(label)) {
4695 M_FAILURE("First parameter is an invalid rangeset label in %s");
4698 if (rangesetTable != NULL) {
4699 rangeset = RangesetFetch(rangesetTable, label);
4702 RangesetGetInfo(rangeset, &defined, &label, &count, &color, &name, &mode);
4704 /* set up result */
4705 result->tag = ARRAY_TAG;
4706 result->val.arrayPtr = ArrayNew();
4708 element.tag = INT_TAG;
4709 element.val.n = defined;
4710 if (!ArrayInsert(result, PERM_ALLOC_STR("defined"), &element))
4711 M_FAILURE("Failed to insert array element \"defined\" in %s");
4713 element.tag = INT_TAG;
4714 element.val.n = count;
4715 if (!ArrayInsert(result, PERM_ALLOC_STR("count"), &element))
4716 M_FAILURE("Failed to insert array element \"count\" in %s");
4718 element.tag = STRING_TAG;
4719 if (!AllocNStringCpy(&element.val.str, color))
4720 M_FAILURE("Failed to allocate array value \"color\" in %s");
4721 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &element))
4722 M_FAILURE("Failed to insert array element \"color\" in %s");
4724 element.tag = STRING_TAG;
4725 if (!AllocNStringCpy(&element.val.str, name))
4726 M_FAILURE("Failed to allocate array value \"name\" in %s");
4727 if (!ArrayInsert(result, PERM_ALLOC_STR("name"), &element)) {
4728 M_FAILURE("Failed to insert array element \"name\" in %s");
4731 element.tag = STRING_TAG;
4732 if (!AllocNStringCpy(&element.val.str, mode))
4733 M_FAILURE("Failed to allocate array value \"mode\" in %s");
4734 if (!ArrayInsert(result, PERM_ALLOC_STR("mode"), &element))
4735 M_FAILURE("Failed to insert array element \"mode\" in %s");
4737 return True;
4741 ** Built-in macro subroutine for finding the extent of a range in a set.
4742 ** If only one parameter is supplied, use the spanning range of all
4743 ** ranges, otherwise select the individual range specified. Returns
4744 ** an array with the keys "start" and "end" and values
4747 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs,
4748 DataValue *result, char **errMsg)
4750 textBuffer *buffer = window->buffer;
4751 RangesetTable *rangesetTable = buffer->rangesetTable;
4752 Rangeset *rangeset;
4753 int start, end, dummy, rangeIndex, ok;
4754 DataValue element;
4755 int label = 0;
4757 if (nArgs < 1 || nArgs > 2) {
4758 return wrongNArgsErr(errMsg);
4761 if (!readIntArg(argList[0], &label, errMsg)
4762 || !RangesetLabelOK(label)) {
4763 M_FAILURE("First parameter is an invalid rangeset label in %s");
4766 if (rangesetTable == NULL) {
4767 M_FAILURE("Rangeset does not exist in %s");
4770 ok = False;
4771 rangeset = RangesetFetch(rangesetTable, label);
4772 if (rangeset != NULL) {
4773 if (nArgs == 1) {
4774 rangeIndex = RangesetGetNRanges(rangeset) - 1;
4775 ok = RangesetFindRangeNo(rangeset, 0, &start, &dummy);
4776 ok &= RangesetFindRangeNo(rangeset, rangeIndex, &dummy, &end);
4777 rangeIndex = -1;
4779 else if (nArgs == 2) {
4780 if (!readIntArg(argList[1], &rangeIndex, errMsg)) {
4781 return False;
4783 ok = RangesetFindRangeNo(rangeset, rangeIndex-1, &start, &end);
4787 /* set up result */
4788 result->tag = ARRAY_TAG;
4789 result->val.arrayPtr = ArrayNew();
4791 if (!ok)
4792 return True;
4794 element.tag = INT_TAG;
4795 element.val.n = start;
4796 if (!ArrayInsert(result, PERM_ALLOC_STR("start"), &element))
4797 M_FAILURE("Failed to insert array element \"start\" in %s");
4799 element.tag = INT_TAG;
4800 element.val.n = end;
4801 if (!ArrayInsert(result, PERM_ALLOC_STR("end"), &element))
4802 M_FAILURE("Failed to insert array element \"end\" in %s");
4804 return True;
4808 ** Built-in macro subroutine for checking a position against a range. If only
4809 ** one parameter is supplied, the current cursor position is used. Returns
4810 ** false (zero) if not in a range, range index (1-based) if in a range;
4811 ** fails if parameters were bad.
4814 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList,
4815 int nArgs, DataValue *result, char **errMsg)
4817 textBuffer *buffer = window->buffer;
4818 RangesetTable *rangesetTable = buffer->rangesetTable;
4819 Rangeset *rangeset;
4820 int pos, rangeIndex, maxpos;
4821 int label = 0;
4823 if (nArgs < 1 || nArgs > 2) {
4824 return wrongNArgsErr(errMsg);
4827 if (!readIntArg(argList[0], &label, errMsg)
4828 || !RangesetLabelOK(label)) {
4829 M_FAILURE("First parameter is an invalid rangeset label in %s");
4832 if (rangesetTable == NULL) {
4833 M_FAILURE("Rangeset does not exist in %s");
4836 rangeset = RangesetFetch(rangesetTable, label);
4837 if (rangeset == NULL) {
4838 M_FAILURE("Rangeset does not exist in %s");
4841 if (nArgs == 1) {
4842 pos = TextGetCursorPos(window->lastFocus);
4844 else if (nArgs == 2) {
4845 if (!readIntArg(argList[1], &pos, errMsg))
4846 return False;
4849 maxpos = buffer->gapEnd - buffer->gapStart + buffer->length;
4850 if (pos < 0 || pos > maxpos) {
4851 rangeIndex = 0;
4853 else {
4854 rangeIndex = RangesetFindRangeOfPos(rangeset, pos, False) + 1;
4857 /* set up result */
4858 result->tag = INT_TAG;
4859 result->val.n = rangeIndex;
4860 return True;
4864 ** Set the color of a range set's ranges. it is ignored if the color cannot be
4865 ** found/applied. If no color is applied, any current color is removed. Returns
4866 ** true if the rangeset is valid.
4869 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList,
4870 int nArgs, DataValue *result, char **errMsg)
4872 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4873 textBuffer *buffer = window->buffer;
4874 RangesetTable *rangesetTable = buffer->rangesetTable;
4875 Rangeset *rangeset;
4876 char *color_name;
4877 int label = 0;
4879 if (nArgs != 2) {
4880 return wrongNArgsErr(errMsg);
4883 if (!readIntArg(argList[0], &label, errMsg)
4884 || !RangesetLabelOK(label)) {
4885 M_FAILURE("First parameter is an invalid rangeset label in %s");
4888 if (rangesetTable == NULL) {
4889 M_FAILURE("Rangeset does not exist in %s");
4892 rangeset = RangesetFetch(rangesetTable, label);
4893 if (rangeset == NULL) {
4894 M_FAILURE("Rangeset does not exist in %s");
4897 color_name = "";
4898 if (rangeset != NULL) {
4899 if (!readStringArg(argList[1], &color_name, stringStorage[0], errMsg)) {
4900 M_FAILURE("Second parameter is not a color name string in %s");
4904 RangesetAssignColorName(rangeset, color_name);
4906 /* set up result */
4907 result->tag = NO_TAG;
4908 return True;
4912 ** Set the name of a range set's ranges. Returns
4913 ** true if the rangeset is valid.
4916 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList,
4917 int nArgs, DataValue *result, char **errMsg)
4919 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4920 textBuffer *buffer = window->buffer;
4921 RangesetTable *rangesetTable = buffer->rangesetTable;
4922 Rangeset *rangeset;
4923 char *name;
4924 int label = 0;
4926 if (nArgs != 2) {
4927 return wrongNArgsErr(errMsg);
4930 if (!readIntArg(argList[0], &label, errMsg)
4931 || !RangesetLabelOK(label)) {
4932 M_FAILURE("First parameter is an invalid rangeset label in %s");
4935 if (rangesetTable == NULL) {
4936 M_FAILURE("Rangeset does not exist in %s");
4939 rangeset = RangesetFetch(rangesetTable, label);
4940 if (rangeset == NULL) {
4941 M_FAILURE("Rangeset does not exist in %s");
4944 name = "";
4945 if (rangeset != NULL) {
4946 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg)) {
4947 M_FAILURE("Second parameter is not a valid name string in %s");
4951 RangesetAssignName(rangeset, name);
4953 /* set up result */
4954 result->tag = NO_TAG;
4955 return True;
4959 ** Change a range's modification response. Returns true if the rangeset is
4960 ** valid and the response type name is valid.
4963 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList,
4964 int nArgs, DataValue *result, char **errMsg)
4966 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
4967 textBuffer *buffer = window->buffer;
4968 RangesetTable *rangesetTable = buffer->rangesetTable;
4969 Rangeset *rangeset;
4970 char *update_fn_name;
4971 int ok;
4972 int label = 0;
4974 if (nArgs < 1 || nArgs > 2) {
4975 return wrongNArgsErr(errMsg);
4978 if (!readIntArg(argList[0], &label, errMsg)
4979 || !RangesetLabelOK(label)) {
4980 M_FAILURE("First parameter is an invalid rangeset label in %s");
4983 if (rangesetTable == NULL) {
4984 M_FAILURE("Rangeset does not exist in %s");
4987 rangeset = RangesetFetch(rangesetTable, label);
4988 if (rangeset == NULL) {
4989 M_FAILURE("Rangeset does not exist in %s");
4992 update_fn_name = "";
4993 if (rangeset != NULL) {
4994 if (nArgs == 2) {
4995 if (!readStringArg(argList[1], &update_fn_name, stringStorage[0], errMsg)) {
4996 M_FAILURE("Second parameter is not a string in %s");
5001 ok = RangesetChangeModifyResponse(rangeset, update_fn_name);
5003 if (!ok) {
5004 M_FAILURE("Second parameter is not a valid mode in %s");
5007 /* set up result */
5008 result->tag = NO_TAG;
5009 return True;
5012 /* -------------------------------------------------------------------------- */
5016 ** Routines to get details directly from the window.
5020 ** Sets up an array containing information about a style given its name or
5021 ** a buffer position (bufferPos >= 0) and its highlighting pattern code
5022 ** (patCode >= 0).
5023 ** From the name we obtain:
5024 ** ["color"] Foreground color name of style
5025 ** ["background"] Background color name of style if specified
5026 ** ["bold"] '1' if style is bold, '0' otherwise
5027 ** ["italic"] '1' if style is italic, '0' otherwise
5028 ** Given position and pattern code we obtain:
5029 ** ["rgb"] RGB representation of foreground color of style
5030 ** ["back_rgb"] RGB representation of background color of style
5031 ** ["extent"] Forward distance from position over which style applies
5032 ** We only supply the style name if the includeName parameter is set:
5033 ** ["style"] Name of style
5036 static int fillStyleResult(DataValue *result, char **errMsg,
5037 WindowInfo *window, char *styleName, Boolean preallocatedStyleName,
5038 Boolean includeName, int patCode, int bufferPos)
5040 DataValue DV;
5041 char colorValue[20];
5042 int r, g, b;
5044 /* initialize array */
5045 result->tag = ARRAY_TAG;
5046 result->val.arrayPtr = ArrayNew();
5048 /* the following array entries will be strings */
5049 DV.tag = STRING_TAG;
5051 if (includeName) {
5052 /* insert style name */
5053 if (preallocatedStyleName) {
5054 DV.val.str.rep = styleName;
5055 DV.val.str.len = strlen(styleName);
5057 else {
5058 AllocNStringCpy(&DV.val.str, styleName);
5060 M_STR_ALLOC_ASSERT(DV);
5061 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5062 M_ARRAY_INSERT_FAILURE();
5066 /* insert color name */
5067 AllocNStringCpy(&DV.val.str, ColorOfNamedStyle(styleName));
5068 M_STR_ALLOC_ASSERT(DV);
5069 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &DV)) {
5070 M_ARRAY_INSERT_FAILURE();
5073 /* Prepare array element for color value
5074 (only possible if we pass through the dynamic highlight pattern tables
5075 in other words, only if we have a pattern code) */
5076 if (patCode) {
5077 HighlightColorValueOfCode(window, patCode, &r, &g, &b);
5078 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
5079 AllocNStringCpy(&DV.val.str, colorValue);
5080 M_STR_ALLOC_ASSERT(DV);
5081 if (!ArrayInsert(result, PERM_ALLOC_STR("rgb"), &DV)) {
5082 M_ARRAY_INSERT_FAILURE();
5086 /* Prepare array element for background color name */
5087 AllocNStringCpy(&DV.val.str, BgColorOfNamedStyle(styleName));
5088 M_STR_ALLOC_ASSERT(DV);
5089 if (!ArrayInsert(result, PERM_ALLOC_STR("background"), &DV)) {
5090 M_ARRAY_INSERT_FAILURE();
5093 /* Prepare array element for background color value
5094 (only possible if we pass through the dynamic highlight pattern tables
5095 in other words, only if we have a pattern code) */
5096 if (patCode) {
5097 GetHighlightBGColorOfCode(window, patCode, &r, &g, &b);
5098 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256);
5099 AllocNStringCpy(&DV.val.str, colorValue);
5100 M_STR_ALLOC_ASSERT(DV);
5101 if (!ArrayInsert(result, PERM_ALLOC_STR("back_rgb"), &DV)) {
5102 M_ARRAY_INSERT_FAILURE();
5106 /* the following array entries will be integers */
5107 DV.tag = INT_TAG;
5109 /* Put boldness value in array */
5110 DV.val.n = FontOfNamedStyleIsBold(styleName);
5111 if (!ArrayInsert(result, PERM_ALLOC_STR("bold"), &DV)) {
5112 M_ARRAY_INSERT_FAILURE();
5115 /* Put italicity value in array */
5116 DV.val.n = FontOfNamedStyleIsItalic(styleName);
5117 if (!ArrayInsert(result, PERM_ALLOC_STR("italic"), &DV)) {
5118 M_ARRAY_INSERT_FAILURE();
5121 if (bufferPos >= 0) {
5122 /* insert extent */
5123 const char *styleNameNotUsed = NULL;
5124 DV.val.n = StyleLengthOfCodeFromPos(window, bufferPos, &styleNameNotUsed);
5125 if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) {
5126 M_ARRAY_INSERT_FAILURE();
5129 return True;
5133 ** Returns an array containing information about the style of name $1
5134 ** ["color"] Foreground color name of style
5135 ** ["background"] Background color name of style if specified
5136 ** ["bold"] '1' if style is bold, '0' otherwise
5137 ** ["italic"] '1' if style is italic, '0' otherwise
5140 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5141 DataValue *result, char **errMsg)
5143 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5144 char *styleName;
5146 /* Validate number of arguments */
5147 if (nArgs != 1) {
5148 return wrongNArgsErr(errMsg);
5151 /* Prepare result */
5152 result->tag = ARRAY_TAG;
5153 result->val.arrayPtr = NULL;
5155 if (!readStringArg(argList[0], &styleName, stringStorage[0], errMsg)) {
5156 M_FAILURE("First parameter is not a string in %s");
5159 if (!NamedStyleExists(styleName)) {
5160 /* if the given name is invalid we just return an empty array. */
5161 return True;
5164 return fillStyleResult(result, errMsg, window,
5165 styleName, (argList[0].tag == STRING_TAG), False, 0, -1);
5169 ** Returns an array containing information about the style of position $1
5170 ** ["style"] Name of style
5171 ** ["color"] Foreground color name of style
5172 ** ["background"] Background color name of style if specified
5173 ** ["bold"] '1' if style is bold, '0' otherwise
5174 ** ["italic"] '1' if style is italic, '0' otherwise
5175 ** ["rgb"] RGB representation of foreground color of style
5176 ** ["back_rgb"] RGB representation of background color of style
5177 ** ["extent"] Forward distance from position over which style applies
5180 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5181 DataValue *result, char **errMsg)
5183 int patCode;
5184 int bufferPos;
5185 textBuffer *buf = window->buffer;
5187 /* Validate number of arguments */
5188 if (nArgs != 1) {
5189 return wrongNArgsErr(errMsg);
5192 /* Prepare result */
5193 result->tag = ARRAY_TAG;
5194 result->val.arrayPtr = NULL;
5196 if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5197 return False;
5200 /* Verify sane buffer position */
5201 if ((bufferPos < 0) || (bufferPos >= buf->length)) {
5202 /* If the position is not legal, we cannot guess anything about
5203 the style, so we return an empty array. */
5204 return True;
5207 /* Determine pattern code */
5208 patCode = HighlightCodeOfPos(window, bufferPos);
5209 if (patCode == 0) {
5210 /* if there is no pattern we just return an empty array. */
5211 return True;
5214 return fillStyleResult(result, errMsg, window,
5215 HighlightStyleOfCode(window, patCode), False, True, patCode, bufferPos);
5219 ** Sets up an array containing information about a pattern given its name or
5220 ** a buffer position (bufferPos >= 0).
5221 ** From the name we obtain:
5222 ** ["style"] Name of style
5223 ** ["extent"] Forward distance from position over which style applies
5224 ** We only supply the pattern name if the includeName parameter is set:
5225 ** ["pattern"] Name of pattern
5228 static int fillPatternResult(DataValue *result, char **errMsg,
5229 WindowInfo *window, char *patternName, Boolean preallocatedPatternName,
5230 Boolean includeName, char* styleName, int bufferPos)
5232 DataValue DV;
5234 /* initialize array */
5235 result->tag = ARRAY_TAG;
5236 result->val.arrayPtr = ArrayNew();
5238 /* the following array entries will be strings */
5239 DV.tag = STRING_TAG;
5241 if (includeName) {
5242 /* insert pattern name */
5243 if (preallocatedPatternName) {
5244 DV.val.str.rep = patternName;
5245 DV.val.str.len = strlen(patternName);
5247 else {
5248 AllocNStringCpy(&DV.val.str, patternName);
5250 M_STR_ALLOC_ASSERT(DV);
5251 if (!ArrayInsert(result, PERM_ALLOC_STR("pattern"), &DV)) {
5252 M_ARRAY_INSERT_FAILURE();
5256 /* insert style name */
5257 AllocNStringCpy(&DV.val.str, styleName);
5258 M_STR_ALLOC_ASSERT(DV);
5259 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) {
5260 M_ARRAY_INSERT_FAILURE();
5263 /* the following array entries will be integers */
5264 DV.tag = INT_TAG;
5266 if (bufferPos >= 0) {
5267 /* insert extent */
5268 int checkCode = 0;
5269 DV.val.n = HighlightLengthOfCodeFromPos(window, bufferPos, &checkCode);
5270 if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) {
5271 M_ARRAY_INSERT_FAILURE();
5275 return True;
5279 ** Returns an array containing information about a highlighting pattern. The
5280 ** single parameter contains the pattern name for which this information is
5281 ** requested.
5282 ** The returned array looks like this:
5283 ** ["style"] Name of style
5285 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs,
5286 DataValue *result, char **errMsg)
5288 char stringStorage[1][TYPE_INT_STR_SIZE(int)];
5289 char *patternName = NULL;
5290 highlightPattern *pattern;
5292 /* Begin of building the result. */
5293 result->tag = ARRAY_TAG;
5294 result->val.arrayPtr = NULL;
5296 /* Validate number of arguments */
5297 if (nArgs != 1) {
5298 return wrongNArgsErr(errMsg);
5301 if (!readStringArg(argList[0], &patternName, stringStorage[0], errMsg)) {
5302 M_FAILURE("First parameter is not a string in %s");
5305 pattern = FindPatternOfWindow(window, patternName);
5306 if (pattern == NULL) {
5307 /* The pattern's name is unknown. */
5308 return True;
5311 return fillPatternResult(result, errMsg, window, patternName,
5312 (argList[0].tag == STRING_TAG), False, pattern->style, -1);
5316 ** Returns an array containing information about the highlighting pattern
5317 ** applied at a given position, passed as the only parameter.
5318 ** The returned array looks like this:
5319 ** ["pattern"] Name of pattern
5320 ** ["style"] Name of style
5321 ** ["extent"] Distance from position over which this pattern applies
5323 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs,
5324 DataValue *result, char **errMsg)
5326 int bufferPos = -1;
5327 textBuffer *buffer = window->buffer;
5328 int patCode = 0;
5330 /* Begin of building the result. */
5331 result->tag = ARRAY_TAG;
5332 result->val.arrayPtr = NULL;
5334 /* Validate number of arguments */
5335 if (nArgs != 1) {
5336 return wrongNArgsErr(errMsg);
5339 /* The most straightforward case: Get a pattern, style and extent
5340 for a buffer position. */
5341 if (!readIntArg(argList[0], &bufferPos, errMsg)) {
5342 return False;
5345 /* Verify sane buffer position
5346 * You would expect that buffer->length would be among the sane
5347 * positions, but we have n characters and n+1 buffer positions. */
5348 if ((bufferPos < 0) || (bufferPos >= buffer->length)) {
5349 /* If the position is not legal, we cannot guess anything about
5350 the highlighting pattern, so we return an empty array. */
5351 return True;
5354 /* Determine the highlighting pattern used */
5355 patCode = HighlightCodeOfPos(window, bufferPos);
5356 if (patCode == 0) {
5357 /* if there is no highlighting pattern we just return an empty array. */
5358 return True;
5361 return fillPatternResult(result, errMsg, window,
5362 HighlightNameOfCode(window, patCode), False, True,
5363 HighlightStyleOfCode(window, patCode), bufferPos);
5366 static int wrongNArgsErr(char **errMsg)
5368 *errMsg = "Wrong number of arguments to function %s";
5369 return False;
5372 static int tooFewArgsErr(char **errMsg)
5374 *errMsg = "Too few arguments to function %s";
5375 return False;
5379 ** strCaseCmp compares its arguments and returns 0 if the two strings
5380 ** are equal IGNORING case differences. Otherwise returns 1 or -1
5381 ** depending on relative comparison.
5383 static int strCaseCmp(char *str1, char *str2)
5385 char *c1, *c2;
5387 for (c1 = str1, c2 = str2;
5388 (*c1 != '\0' && *c2 != '\0')
5389 && toupper((unsigned char)*c1) == toupper((unsigned char)*c2);
5390 ++c1, ++c2)
5394 if (((unsigned char)toupper((unsigned char)*c1))
5395 > ((unsigned char)toupper((unsigned char)*c2)))
5397 return(1);
5398 } else if (((unsigned char)toupper((unsigned char)*c1))
5399 < ((unsigned char)toupper((unsigned char)*c2)))
5401 return(-1);
5402 } else
5404 return(0);
5409 ** Get an integer value from a tagged DataValue structure. Return True
5410 ** if conversion succeeded, and store result in *result, otherwise
5411 ** return False with an error message in *errMsg.
5413 static int readIntArg(DataValue dv, int *result, char **errMsg)
5415 char *c;
5417 if (dv.tag == INT_TAG) {
5418 *result = dv.val.n;
5419 return True;
5420 } else if (dv.tag == STRING_TAG) {
5421 for (c=dv.val.str.rep; *c != '\0'; c++) {
5422 if (!(isdigit((unsigned char)*c) || *c == ' ' || *c == '\t')) {
5423 goto typeError;
5426 sscanf(dv.val.str.rep, "%d", result);
5427 return True;
5430 typeError:
5431 *errMsg = "%s called with non-integer argument";
5432 return False;
5436 ** Get an string value from a tagged DataValue structure. Return True
5437 ** if conversion succeeded, and store result in *result, otherwise
5438 ** return False with an error message in *errMsg. If an integer value
5439 ** is converted, write the string in the space provided by "stringStorage",
5440 ** which must be large enough to handle ints of the maximum size.
5442 static int readStringArg(DataValue dv, char **result, char *stringStorage,
5443 char **errMsg)
5445 if (dv.tag == STRING_TAG) {
5446 *result = dv.val.str.rep;
5447 return True;
5448 } else if (dv.tag == INT_TAG) {
5449 sprintf(stringStorage, "%d", dv.val.n);
5450 *result = stringStorage;
5451 return True;
5453 *errMsg = "%s called with unknown object";
5454 return False;