UNIXworkcode

1 /******************************************************************************* 2 * * 3 * macro.c -- Macro file processing, learn/replay, and built-in macro * 4 * subroutines * 5 * * 6 * Copyright (C) 1999 Mark Edel * 7 * * 8 * This is free software; you can redistribute it and/or modify it under the * 9 * terms of the GNU General Public License as published by the Free Software * 10 * Foundation; either version 2 of the License, or (at your option) any later * 11 * version. In addition, you may distribute versions of this program linked to * 12 * Motif or Open Motif. See README for details. * 13 * * 14 * This software is distributed in the hope that it will be useful, but WITHOUT * 15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 17 * for more details. * 18 * * 19 * You should have received a copy of the GNU General Public License along with * 20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 21 * Place, Suite 330, Boston, MA 02111-1307 USA * 22 * * 23 * Nirvana Text Editor * 24 * April, 1997 * 25 * * 26 * Written by Mark Edel * 27 * * 28 *******************************************************************************/ 29 30 #ifdef HAVE_CONFIG_H 31 #include "../config.h" 32 #endif 33 34 #include "macro.h" 35 #include "textBuf.h" 36 #include "text.h" 37 #include "nedit.h" 38 #include "window.h" 39 #include "preferences.h" 40 #include "interpret.h" 41 #include "parse.h" 42 #include "search.h" 43 #include "server.h" 44 #include "shell.h" 45 #include "smartIndent.h" 46 #include "userCmds.h" 47 #include "selection.h" 48 #include "../util/rbTree.h" 49 #include "tags.h" 50 #include "calltips.h" 51 #include "../util/DialogF.h" 52 #include "../util/misc.h" 53 #include "../util/fileUtils.h" 54 #include "../util/utils.h" 55 #include "../util/getfiles.h" 56 #include "../util/filedialog.h" 57 #include "highlight.h" 58 #include "highlightData.h" 59 #include "rangeset.h" 60 #include "../util/nedit_malloc.h" 61 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <ctype.h> 66 #include <errno.h> 67 68 #include <sys/types.h> 69 #include <sys/stat.h> 70 #ifndef __MVS__ 71 #include <sys/param.h> 72 #endif 73 #include <fcntl.h> 74 75 #include <inttypes.h> 76 77 #include <X11/Intrinsic.h> 78 #include <X11/keysym.h> 79 #include <Xm/Xm.h> 80 #include <Xm/CutPaste.h> 81 #include <Xm/Form.h> 82 #include <Xm/RowColumn.h> 83 #include <Xm/LabelG.h> 84 #include <Xm/List.h> 85 #include <Xm/ToggleB.h> 86 #include <Xm/DialogS.h> 87 #include <Xm/MessageB.h> 88 #include <Xm/SelectioB.h> 89 #include <Xm/PushB.h> 90 #include <Xm/Text.h> 91 #include <Xm/Separator.h> 92 93 #ifdef HAVE_DEBUG_H 94 #include "../debug.h" 95 #endif 96 97 /* Maximum number of actions in a macro and args in 98 an action (to simplify the reader) */ 99 #define MAX_MACRO_ACTIONS 1024 100 #define MAX_ACTION_ARGS 40 101 102 /* How long to wait (msec) before putting up Macro Command banner */ 103 #define BANNER_WAIT_TIME 6000 104 105 /* The following definitions cause an exit from the macro with a message */ 106 /* added if (1) to remove compiler warnings on solaris */ 107 #define M_FAILURE(s) do { *errMsg = s; if (1) return False; } while (0) 108 #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) 109 #define M_ARRAY_INSERT_FAILURE() M_FAILURE("array element failed to insert: %s") 110 111 /* Data attached to window during shell command execution with 112 information for controling and communicating with the process */ 113 typedef struct { 114 XtIntervalId bannerTimeoutID; 115 XtWorkProcId continueWorkProcID; 116 char bannerIsUp; 117 char closeOnCompletion; 118 Program *program; 119 RestartData *context; 120 Widget dialog; 121 } macroCmdInfo; 122 123 /* Widgets and global data for Repeat dialog */ 124 typedef struct { 125 WindowInfo *forWindow; 126 char *lastCommand; 127 Widget shell, repeatText, lastCmdToggle; 128 Widget inSelToggle, toEndToggle; 129 } repeatDialog; 130 131 static void cancelLearn(void); 132 static void runMacro(WindowInfo *window, Program *prog); 133 static void finishMacroCmdExecution(WindowInfo *window); 134 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData); 135 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData); 136 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event); 137 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData); 138 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData); 139 static void learnActionHook(Widget w, XtPointer clientData, String actionName, 140 XEvent *event, String *params, Cardinal *numParams); 141 static void lastActionHook(Widget w, XtPointer clientData, String actionName, 142 XEvent *event, String *params, Cardinal *numParams); 143 static char *actionToString(Widget w, char *actionName, XEvent *event, 144 String *params, Cardinal numParams); 145 static int isMouseAction(const char *action); 146 static int isRedundantAction(const char *action); 147 static int isIgnoredAction(const char *action); 148 static int readCheckMacroString(Widget dialogParent, char *string, 149 WindowInfo *runWindow, const char *errIn, char **errPos); 150 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id); 151 static Boolean continueWorkProc(XtPointer clientData); 152 static int escapeStringChars(char *fromString, char *toString); 153 static int escapedStringLength(char *string); 154 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs, 155 DataValue *result, char **errMsg); 156 static int minMS(WindowInfo *window, DataValue *argList, int nArgs, 157 DataValue *result, char **errMsg); 158 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs, 159 DataValue *result, char **errMsg); 160 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs, 161 DataValue *result, char **errMsg); 162 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs, 163 DataValue *result, char **errMsg); 164 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs, 165 DataValue *result, char **errMsg); 166 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs, 167 DataValue *result, char **errMsg); 168 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs, 169 DataValue *result, char **errMsg); 170 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs, 171 DataValue *result, char **errMsg); 172 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs, 173 DataValue *result, char **errMsg); 174 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs, 175 DataValue *result, char **errMsg); 176 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs, 177 DataValue *result, char **errMsg); 178 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs, 179 DataValue *result, char **errMsg); 180 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs, 181 DataValue *result, char **errMsg); 182 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs, 183 DataValue *result, char **errMsg); 184 static int writeOrAppendFile(int append, WindowInfo *window, 185 DataValue *argList, int nArgs, DataValue *result, char **errMsg); 186 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs, 187 DataValue *result, char **errMsg); 188 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs, 189 DataValue *result, char **errMsg); 190 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs, 191 DataValue *result, char **errMsg); 192 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs, 193 DataValue *result, char **errMsg); 194 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs, 195 DataValue *result, char **errMsg); 196 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs, 197 DataValue *result, char **errMsg); 198 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs, 199 DataValue *result, char **errMsg); 200 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs, 201 DataValue *result, char **errMsg); 202 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs, 203 DataValue *result, char **errMsg); 204 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs, 205 DataValue *result, char **errMsg); 206 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs, 207 DataValue *result, char **errMsg); 208 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs, 209 DataValue *result, char **errMsg); 210 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs, 211 DataValue *result, char **errMsg); 212 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs, 213 DataValue *result, char **errMsg); 214 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs, 215 DataValue *result, char **errMsg); 216 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData); 217 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData); 218 #ifdef LESSTIF_VERSION 219 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event, 220 Boolean *cont); 221 #endif /* LESSTIF_VERSION */ 222 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs, 223 DataValue *result, char **errMsg); 224 static void stringDialogBtnCB(Widget w, XtPointer clientData, 225 XtPointer callData); 226 static void stringDialogCloseCB(Widget w, XtPointer clientData, 227 XtPointer callData); 228 #ifdef LESSTIF_VERSION 229 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event, 230 Boolean *cont); 231 #endif /* LESSTIF_VERSION */ 232 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs, 233 DataValue *result, char **errMsg); 234 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs, 235 DataValue *result, char **errMsg); 236 /* T Balinski */ 237 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs, 238 DataValue *result, char **errMsg); 239 static void listDialogBtnCB(Widget w, XtPointer clientData, 240 XtPointer callData); 241 static void listDialogCloseCB(Widget w, XtPointer clientData, 242 XtPointer callData); 243 /* T Balinski End */ 244 #ifdef LESSTIF_VERSION 245 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event, 246 Boolean *cont); 247 #endif /* LESSTIF_VERSION */ 248 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs, 249 DataValue *result, char **errMsg); 250 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs, 251 DataValue *result, char **errMsg); 252 /* DISASBLED for 5.4 253 static int setBacklightStringMS(WindowInfo *window, DataValue *argList, 254 int nArgs, DataValue *result, char **errMsg); 255 */ 256 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs, 257 DataValue *result, char **errMsg); 258 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs, 259 DataValue *result, char **errMsg); 260 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs, 261 DataValue *result, char **errMsg); 262 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs, 263 DataValue *result, char **errMsg); 264 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs, 265 DataValue *result, char **errMsg); 266 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs, 267 DataValue *result, char **errMsg); 268 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs, 269 DataValue *result, char **errMsg); 270 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs, 271 DataValue *result, char **errMsg); 272 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs, 273 DataValue *result, char **errMsg); 274 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs, 275 DataValue *result, char **errMsg); 276 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs, 277 DataValue *result, char **errMsg); 278 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs, 279 DataValue *result, char **errMsg); 280 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs, 281 DataValue *result, char **errMsg); 282 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs, 283 DataValue *result, char **errMsg); 284 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs, 285 DataValue *result, char **errMsg); 286 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs, 287 DataValue *result, char **errMsg); 288 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs, 289 DataValue *result, char **errMsg); 290 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs, 291 DataValue *result, char **errMsg); 292 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs, 293 DataValue *result, char **errMsg); 294 static int matchSyntaxBasedMV(WindowInfo *window, DataValue *argList, int nArgs, 295 DataValue *result, char **errMsg); 296 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs, 297 DataValue *result, char **errMsg); 298 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs, 299 DataValue *result, char **errMsg); 300 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs, 301 DataValue *result, char **errMsg); 302 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs, 303 DataValue *result, char **errMsg); 304 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs, 305 DataValue *result, char **errMsg); 306 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs, 307 DataValue *result, char **errMsg); 308 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs, 309 DataValue *result, char **errMsg); 310 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs, 311 DataValue *result, char **errMsg); 312 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs, 313 DataValue *result, char **errMsg); 314 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs, 315 DataValue *result, char **errMsg); 316 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs, 317 DataValue *result, char **errMsg); 318 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs, 319 DataValue *result, char **errMsg); 320 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs, 321 DataValue *result, char **errMsg); 322 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs, 323 DataValue *result, char **errMsg); 324 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs, 325 DataValue *result, char **errMsg); 326 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs, 327 DataValue *result, char **errMsg); 328 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs, 329 DataValue *result, char **errMsg); 330 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs, 331 DataValue *result, char **errMsg); 332 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs, 333 DataValue *result, char **errMsg); 334 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs, 335 DataValue *result, char **errMsg); 336 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs, 337 DataValue *result, char **errMsg); 338 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs, 339 DataValue *result, char **errMsg); 340 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs, 341 DataValue *result, char **errMsg); 342 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs, 343 DataValue *result, char **errMsg); 344 static int calltipIDMV(WindowInfo *window, DataValue *argList, int nArgs, 345 DataValue *result, char **errMsg); 346 static int readSearchArgs(DataValue *argList, int nArgs, int*searchDirection, 347 int *searchType, int *wrap, char **errMsg); 348 static int wrongNArgsErr(char **errMsg); 349 static int tooFewArgsErr(char **errMsg); 350 static int 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); 356 */ 357 static int rangesetListMV(WindowInfo *window, DataValue *argList, 358 int nArgs, DataValue *result, char **errMsg); 359 static int versionMV(WindowInfo* window, DataValue* argList, int nArgs, 360 DataValue* result, char** errMsg); 361 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs, 362 DataValue *result, char **errMsg); 363 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs, 364 DataValue *result, char **errMsg); 365 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs, 366 DataValue *result, char **errMsg); 367 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs, 368 DataValue *result, char **errMsg); 369 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs, 370 DataValue *result, char **errMsg); 371 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs, 372 DataValue *result, char **errMsg); 373 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs, 374 DataValue *result, char **errMsg); 375 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs, 376 DataValue *result, char **errMsg); 377 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList, 378 int nArgs, DataValue *result, char **errMsg); 379 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList, 380 int nArgs, DataValue *result, char **errMsg); 381 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList, 382 int nArgs, DataValue *result, char **errMsg); 383 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList, 384 int nArgs, DataValue *result, char **errMsg); 385 386 static int fillPatternResult(DataValue *result, char **errMsg, WindowInfo *window, 387 char *patternName, Boolean preallocatedPatternName, Boolean includeName, 388 char *styleName, int bufferPos); 389 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs, 390 DataValue *result, char **errMsg); 391 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs, 392 DataValue *result, char **errMsg); 393 394 static int fillStyleResult(DataValue *result, char **errMsg, 395 WindowInfo *window, char *styleName, Boolean preallocatedStyleName, 396 Boolean includeName, int patCode, int bufferPos); 397 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs, 398 DataValue *result, char **errMsg); 399 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs, 400 DataValue *result, char **errMsg); 401 static int filenameDialogMS(WindowInfo* window, DataValue* argList, int nArgs, 402 DataValue* result, char** errMsg); 403 404 /* Built-in subroutines and variables for the macro language */ 405 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS, 406 dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS, 407 setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS, 408 searchStringMS, substringMS, replaceSubstringMS, readFileMS, 409 writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS, 410 replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS, 411 shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS, 412 tolowerMS, listDialogMS, getenvMS, 413 stringCompareMS, splitMS, calltipMS, killCalltipMS, 414 /* DISABLED for 5.4 setBacklightStringMS,*/ 415 rangesetCreateMS, rangesetDestroyMS, 416 rangesetAddMS, rangesetSubtractMS, rangesetInvertMS, 417 rangesetInfoMS, rangesetRangeMS, rangesetIncludesPosMS, 418 rangesetSetColorMS, rangesetSetNameMS, rangesetSetModeMS, 419 rangesetGetByNameMS, 420 getPatternByNameMS, getPatternAtPosMS, 421 getStyleByNameMS, getStyleAtPosMS, filenameDialogMS 422 }; 423 #define N_MACRO_SUBRS (sizeof MacroSubrs/sizeof *MacroSubrs) 424 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print", 425 "dialog", "string_dialog", "replace_range", "replace_selection", 426 "set_cursor_pos", "get_character", "min", "max", "search", 427 "search_string", "substring", "replace_substring", "read_file", 428 "write_file", "append_file", "beep", "get_selection", "valid_number", 429 "replace_in_string", "select", "select_rectangle", "focus_window", 430 "shell_command", "string_to_clipboard", "clipboard_to_string", 431 "toupper", "tolower", "list_dialog", "getenv", 432 "string_compare", "split", "calltip", "kill_calltip", 433 /* DISABLED for 5.4 "set_backlight_string", */ 434 "rangeset_create", "rangeset_destroy", 435 "rangeset_add", "rangeset_subtract", "rangeset_invert", 436 "rangeset_info", "rangeset_range", "rangeset_includes", 437 "rangeset_set_color", "rangeset_set_name", "rangeset_set_mode", 438 "rangeset_get_by_name", 439 "get_pattern_by_name", "get_pattern_at_pos", 440 "get_style_by_name", "get_style_at_pos", "filename_dialog" 441 }; 442 static BuiltInSubr SpecialVars[] = {cursorMV, lineMV, columnMV, 443 fileNameMV, filePathMV, lengthMV, selectionStartMV, selectionEndMV, 444 selectionLeftMV, selectionRightMV, wrapMarginMV, tabDistMV, 445 emTabDistMV, useTabsMV, languageModeMV, modifiedMV, 446 statisticsLineMV, incSearchLineMV, showLineNumbersMV, 447 autoIndentMV, wrapTextMV, highlightSyntaxMV, 448 makeBackupCopyMV, incBackupMV, showMatchingMV, matchSyntaxBasedMV, 449 overTypeModeMV, readOnlyMV, lockedMV, fileFormatMV, 450 fontNameMV, fontNameItalicMV, 451 fontNameBoldMV, fontNameBoldItalicMV, subscriptSepMV, 452 minFontWidthMV, maxFontWidthMV, topLineMV, numDisplayLinesMV, 453 displayWidthMV, activePaneMV, nPanesMV, emptyArrayMV, 454 serverNameMV, calltipIDMV, 455 /* DISABLED for 5.4 backlightStringMV, */ 456 rangesetListMV, versionMV 457 }; 458 #define N_SPECIAL_VARS (sizeof SpecialVars/sizeof *SpecialVars) 459 static const char *SpecialVarNames[N_SPECIAL_VARS] = {"$cursor", "$line", "$column", 460 "$file_name", "$file_path", "$text_length", "$selection_start", 461 "$selection_end", "$selection_left", "$selection_right", 462 "$wrap_margin", "$tab_dist", "$em_tab_dist", "$use_tabs", 463 "$language_mode", "$modified", 464 "$statistics_line", "$incremental_search_line", "$show_line_numbers", 465 "$auto_indent", "$wrap_text", "$highlight_syntax", 466 "$make_backup_copy", "$incremental_backup", "$show_matching", "$match_syntax_based", 467 "$overtype_mode", "$read_only", "$locked", "$file_format", 468 "$font_name", "$font_name_italic", 469 "$font_name_bold", "$font_name_bold_italic", "$sub_sep", 470 "$min_font_width", "$max_font_width", "$top_line", "$n_display_lines", 471 "$display_width", "$active_pane", "$n_panes", "$empty_array", 472 "$server_name", "$calltip_ID", 473 /* DISABLED for 5.4 "$backlight_string", */ 474 "$rangeset_list", "$VERSION" 475 }; 476 477 /* Global symbols for returning values from built-in functions */ 478 #define N_RETURN_GLOBALS 5 479 enum retGlobalSyms {STRING_DIALOG_BUTTON, SEARCH_END, READ_STATUS, 480 SHELL_CMD_STATUS, LIST_DIALOG_BUTTON}; 481 static const char *ReturnGlobalNames[N_RETURN_GLOBALS] = {"$string_dialog_button", 482 "$search_end", "$read_status", "$shell_cmd_status", 483 "$list_dialog_button"}; 484 static Symbol *ReturnGlobals[N_RETURN_GLOBALS]; 485 486 /* List of actions not useful when learning a macro sequence (also see below) */ 487 static char* IgnoredActions[] = {"focusIn", "focusOut"}; 488 489 /* List of actions intended to be attached to mouse buttons, which the user 490 must be warned can't be recorded in a learn/replay sequence */ 491 static const char* MouseActions[] = {"grab_focus", "extend_adjust", "extend_start", 492 "extend_end", "secondary_or_drag_adjust", "secondary_adjust", 493 "secondary_or_drag_start", "secondary_start", "move_destination", 494 "move_to", "move_to_or_end_drag", "copy_to", "copy_to_or_end_drag", 495 "exchange", "process_bdrag", "mouse_pan"}; 496 497 /* List of actions to not record because they 498 generate further actions, more suitable for recording */ 499 static const char* RedundantActions[] = {"open_dialog", "save_as_dialog", 500 "revert_to_saved_dialog", "include_file_dialog", "load_macro_file_dialog", 501 "load_tags_file_dialog", "find_dialog", "replace_dialog", 502 "goto_line_number_dialog", "mark_dialog", "goto_mark_dialog", 503 "control_code_dialog", "filter_selection_dialog", "execute_command_dialog", 504 "repeat_dialog", "start_incremental_find"}; 505 506 /* The last command executed (used by the Repeat command) */ 507 static char *LastCommand = NULL; 508 509 /* The current macro to execute on Replay command */ 510 static char *ReplayMacro = NULL; 511 512 /* Buffer where macro commands are recorded in Learn mode */ 513 static textBuffer *MacroRecordBuf = NULL; 514 515 /* Action Hook id for recording actions for Learn mode */ 516 static XtActionHookId MacroRecordActionHook = 0; 517 518 /* Window where macro recording is taking place */ 519 static WindowInfo *MacroRecordWindow = NULL; 520 521 /* Arrays for translating escape characters in escapeStringChars */ 522 static char ReplaceChars[] = "\\\"ntbrfav"; 523 static char EscapeChars[] = "\\\"\n\t\b\r\f\a\v"; 524 525 /* 526 ** Install built-in macro subroutines and special variables for accessing 527 ** editor information 528 */ 529 void RegisterMacroSubroutines(void) 530 { 531 static DataValue subrPtr = {NO_TAG, {0}}, noValue = {NO_TAG, {0}}; 532 unsigned i; 533 534 /* Install symbols for built-in routines and variables, with pointers 535 to the appropriate c routines to do the work */ 536 for (i=0; i<N_MACRO_SUBRS; i++) { 537 subrPtr.val.subr = MacroSubrs[i]; 538 InstallSymbol(MacroSubrNames[i], C_FUNCTION_SYM, subrPtr); 539 } 540 for (i=0; i<N_SPECIAL_VARS; i++) { 541 subrPtr.val.subr = SpecialVars[i]; 542 InstallSymbol(SpecialVarNames[i], PROC_VALUE_SYM, subrPtr); 543 } 544 545 /* Define global variables used for return values, remember their 546 locations so they can be set without a LookupSymbol call */ 547 for (i=0; i<N_RETURN_GLOBALS; i++) 548 ReturnGlobals[i] = InstallSymbol(ReturnGlobalNames[i], GLOBAL_SYM, 549 noValue); 550 } 551 552 #define MAX_LEARN_MSG_LEN ((2 * MAX_ACCEL_LEN) + 60) 553 void BeginLearn(WindowInfo *window) 554 { 555 WindowInfo *win; 556 XmString s; 557 XmString xmFinish; 558 XmString xmCancel; 559 char *cFinish; 560 char *cCancel; 561 char message[MAX_LEARN_MSG_LEN]; 562 563 /* If we're already in learn mode, return */ 564 if (MacroRecordActionHook != 0) 565 return; 566 567 /* dim the inappropriate menus and items, and undim finish and cancel */ 568 for (win=WindowList; win!=NULL; win=win->next) { 569 if (!IsTopDocument(win)) 570 continue; 571 XtSetSensitive(win->learnItem, False); 572 } 573 SetSensitive(window, window->finishLearnItem, True); 574 XtVaSetValues(window->cancelMacroItem, XmNlabelString, 575 s=XmStringCreateSimple("Cancel Learn"), NULL); 576 XmStringFree(s); 577 SetSensitive(window, window->cancelMacroItem, True); 578 579 /* Mark the window where learn mode is happening */ 580 MacroRecordWindow = window; 581 582 /* Allocate a text buffer for accumulating the macro strings */ 583 MacroRecordBuf = BufCreate(); 584 585 /* Add the action hook for recording the actions */ 586 MacroRecordActionHook = 587 XtAppAddActionHook(XtWidgetToApplicationContext(window->shell), 588 learnActionHook, window); 589 590 /* Extract accelerator texts from menu PushButtons */ 591 XtVaGetValues(window->finishLearnItem, XmNacceleratorText, &xmFinish, NULL); 592 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL); 593 594 /* Translate Motif strings to char* */ 595 cFinish = GetXmStringText(xmFinish); 596 cCancel = GetXmStringText(xmCancel); 597 598 /* Free Motif Strings */ 599 XmStringFree(xmFinish); 600 XmStringFree(xmCancel); 601 602 /* Create message */ 603 if (cFinish[0] == '\0') { 604 if (cCancel[0] == '\0') { 605 strncpy(message, "Learn Mode -- Use menu to finish or cancel", 606 MAX_LEARN_MSG_LEN); 607 message[MAX_LEARN_MSG_LEN - 1] = '\0'; 608 } 609 else { 610 sprintf(message, 611 "Learn Mode -- Use menu to finish, press %s to cancel", 612 cCancel); 613 } 614 } 615 else { 616 if (cCancel[0] == '\0') { 617 sprintf(message, 618 "Learn Mode -- Press %s to finish, use menu to cancel", 619 cFinish); 620 621 } 622 else { 623 sprintf(message, 624 "Learn Mode -- Press %s to finish, %s to cancel", 625 cFinish, 626 cCancel); 627 } 628 } 629 630 /* Free C-strings */ 631 NEditFree(cFinish); 632 NEditFree(cCancel); 633 634 /* Put up the learn-mode banner */ 635 SetModeMessage(window, message); 636 } 637 638 void AddLastCommandActionHook(XtAppContext context) 639 { 640 XtAppAddActionHook(context, lastActionHook, NULL); 641 } 642 643 void FinishLearn(void) 644 { 645 WindowInfo *win; 646 647 /* If we're not in learn mode, return */ 648 if (MacroRecordActionHook == 0) 649 return; 650 651 /* Remove the action hook */ 652 XtRemoveActionHook(MacroRecordActionHook); 653 MacroRecordActionHook = 0; 654 655 /* Free the old learn/replay sequence */ 656 NEditFree(ReplayMacro); 657 658 /* Store the finished action for the replay menu item */ 659 ReplayMacro = BufGetAll(MacroRecordBuf); 660 661 /* Free the buffer used to accumulate the macro sequence */ 662 BufFree(MacroRecordBuf); 663 664 /* Undim the menu items dimmed during learn */ 665 for (win=WindowList; win!=NULL; win=win->next) { 666 if (!IsTopDocument(win)) 667 continue; 668 XtSetSensitive(win->learnItem, True); 669 } 670 if (IsTopDocument(MacroRecordWindow)) { 671 XtSetSensitive(MacroRecordWindow->finishLearnItem, False); 672 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False); 673 } 674 675 /* Undim the replay and paste-macro buttons */ 676 for (win=WindowList; win!=NULL; win=win->next) { 677 if (!IsTopDocument(win)) 678 continue; 679 XtSetSensitive(win->replayItem, True); 680 } 681 DimPasteReplayBtns(True); 682 683 /* Clear learn-mode banner */ 684 ClearModeMessage(MacroRecordWindow); 685 } 686 687 /* 688 ** Cancel Learn mode, or macro execution (they're bound to the same menu item) 689 */ 690 void CancelMacroOrLearn(WindowInfo *window) 691 { 692 if (MacroRecordActionHook != 0) 693 cancelLearn(); 694 else if (window->macroCmdData != NULL) 695 AbortMacroCommand(window); 696 } 697 698 static void cancelLearn(void) 699 { 700 WindowInfo *win; 701 702 /* If we're not in learn mode, return */ 703 if (MacroRecordActionHook == 0) 704 return; 705 706 /* Remove the action hook */ 707 XtRemoveActionHook(MacroRecordActionHook); 708 MacroRecordActionHook = 0; 709 710 /* Free the macro under construction */ 711 BufFree(MacroRecordBuf); 712 713 /* Undim the menu items dimmed during learn */ 714 for (win=WindowList; win!=NULL; win=win->next) { 715 if (!IsTopDocument(win)) 716 continue; 717 XtSetSensitive(win->learnItem, True); 718 } 719 if (IsTopDocument(MacroRecordWindow)) { 720 XtSetSensitive(MacroRecordWindow->finishLearnItem, False); 721 XtSetSensitive(MacroRecordWindow->cancelMacroItem, False); 722 } 723 724 /* Clear learn-mode banner */ 725 ClearModeMessage(MacroRecordWindow); 726 } 727 728 /* 729 ** Execute the learn/replay sequence stored in "window" 730 */ 731 void Replay(WindowInfo *window) 732 { 733 Program *prog; 734 char *errMsg, *stoppedAt; 735 736 /* Verify that a replay macro exists and it's not empty and that */ 737 /* we're not already running a macro */ 738 if (ReplayMacro != NULL && 739 ReplayMacro[0] != 0 && 740 window->macroCmdData == NULL) { 741 /* Parse the replay macro (it's stored in text form) and compile it into 742 an executable program "prog" */ 743 prog = ParseMacro(ReplayMacro, &errMsg, &stoppedAt); 744 if (prog == NULL) { 745 fprintf(stderr, 746 "XNEdit internal error, learn/replay macro syntax error: %s\n", 747 errMsg); 748 return; 749 } 750 751 /* run the executable program */ 752 runMacro(window, prog); 753 } 754 } 755 756 /* 757 ** Read the initial NEdit macro file if one exists. 758 */ 759 void ReadMacroInitFile(WindowInfo *window) 760 { 761 const char* autoloadName = GetRCFileName(AUTOLOAD_NM); 762 static int initFileLoaded = False; 763 764 /* GetRCFileName() might return NULL if an error occurs during 765 creation of the preference file directory. */ 766 if (autoloadName != NULL && !initFileLoaded) 767 { 768 ReadMacroFile(window, autoloadName, False); 769 initFileLoaded = True; 770 } 771 } 772 773 /* 774 ** Read an NEdit macro file. Extends the syntax of the macro parser with 775 ** define keyword, and allows intermixing of defines with immediate actions. 776 */ 777 int ReadMacroFile(WindowInfo *window, const char *fileName, int warnNotExist) 778 { 779 int result; 780 char *fileString; 781 782 /* read-in macro file and force a terminating \n, to prevent syntax 783 ** errors with statements on the last line 784 */ 785 fileString = ReadAnyTextFile(fileName, True); 786 if (fileString == NULL){ 787 if (errno != ENOENT || warnNotExist) 788 { 789 DialogF(DF_ERR, window->shell, 1, "Read Macro", 790 "Error reading macro file %s: %s", "OK", fileName, 791 strerror(errno)); 792 } 793 return False; 794 } 795 796 /* Parse fileString */ 797 result = readCheckMacroString(window->shell, fileString, window, fileName, 798 NULL); 799 NEditFree(fileString); 800 return result; 801 } 802 803 /* 804 ** Parse and execute a macro string including macro definitions. Report 805 ** parsing errors in a dialog posted over window->shell. 806 */ 807 int ReadMacroString(WindowInfo *window, char *string, const char *errIn) 808 { 809 return readCheckMacroString(window->shell, string, window, errIn, NULL); 810 } 811 812 /* 813 ** Check a macro string containing definitions for errors. Returns True 814 ** if macro compiled successfully. Returns False and puts up 815 ** a dialog explaining if macro did not compile successfully. 816 */ 817 int CheckMacroString(Widget dialogParent, char *string, const char *errIn, 818 char **errPos) 819 { 820 return readCheckMacroString(dialogParent, string, NULL, errIn, errPos); 821 } 822 823 /* 824 ** Parse and optionally execute a macro string including macro definitions. 825 ** Report parsing errors in a dialog posted over dialogParent, using the 826 ** string errIn to identify the entity being parsed (filename, macro string, 827 ** etc.). If runWindow is specified, runs the macro against the window. If 828 ** runWindow is passed as NULL, does parse only. If errPos is non-null, 829 ** returns a pointer to the error location in the string. 830 */ 831 static int readCheckMacroString(Widget dialogParent, char *string, 832 WindowInfo *runWindow, const char *errIn, char **errPos) 833 { 834 char *stoppedAt, *inPtr, *namePtr, *errMsg; 835 char subrName[MAX_SYM_LEN]; 836 Program *prog; 837 Symbol *sym; 838 DataValue subrPtr; 839 Stack* progStack = (Stack*) NEditMalloc(sizeof(Stack)); 840 progStack->top = NULL; 841 progStack->size = 0; 842 843 inPtr = string; 844 while (*inPtr != '\0') { 845 846 /* skip over white space and comments */ 847 while (*inPtr==' ' || *inPtr=='\t' || *inPtr=='\n'|| *inPtr=='#') { 848 if (*inPtr == '#') 849 while (*inPtr != '\n' && *inPtr != '\0') inPtr++; 850 else 851 inPtr++; 852 } 853 if (*inPtr == '\0') 854 break; 855 856 /* look for define keyword, and compile and store defined routines */ 857 if (!strncmp(inPtr, "define", 6) && (inPtr[6]==' ' || inPtr[6]=='\t')) { 858 inPtr += 6; 859 inPtr += strspn(inPtr, " \t\n"); 860 namePtr = subrName; 861 while ((namePtr < &subrName[MAX_SYM_LEN - 1]) 862 && (isalnum((unsigned char)*inPtr) || *inPtr == '_')) { 863 *namePtr++ = *inPtr++; 864 } 865 *namePtr = '\0'; 866 if (isalnum((unsigned char)*inPtr) || *inPtr == '_') { 867 return ParseError(dialogParent, string, inPtr, errIn, 868 "subroutine name too long"); 869 } 870 inPtr += strspn(inPtr, " \t\n"); 871 if (*inPtr != '{') { 872 if (errPos != NULL) *errPos = stoppedAt; 873 return ParseError(dialogParent, string, inPtr, 874 errIn, "expected ''{''"); 875 } 876 prog = ParseMacro(inPtr, &errMsg, &stoppedAt); 877 if (prog == NULL) { 878 if (errPos != NULL) *errPos = stoppedAt; 879 return ParseError(dialogParent, string, stoppedAt, 880 errIn, errMsg); 881 } 882 if (runWindow != NULL) { 883 sym = LookupSymbol(subrName); 884 if (sym == NULL) { 885 subrPtr.val.prog = prog; 886 subrPtr.tag = NO_TAG; 887 sym = InstallSymbol(subrName, MACRO_FUNCTION_SYM, subrPtr); 888 } else { 889 if (sym->type == MACRO_FUNCTION_SYM) 890 FreeProgram(sym->value.val.prog); 891 else 892 sym->type = MACRO_FUNCTION_SYM; 893 sym->value.val.prog = prog; 894 } 895 } 896 inPtr = stoppedAt; 897 898 /* Parse and execute immediate (outside of any define) macro commands 899 and WAIT for them to finish executing before proceeding. Note that 900 the code below is not perfect. If you interleave code blocks with 901 definitions in a file which is loaded from another macro file, it 902 will probably run the code blocks in reverse order! */ 903 } else { 904 prog = ParseMacro(inPtr, &errMsg, &stoppedAt); 905 if (prog == NULL) { 906 if (errPos != NULL) { 907 *errPos = stoppedAt; 908 } 909 910 return ParseError(dialogParent, string, stoppedAt, 911 errIn, errMsg); 912 } 913 914 if (runWindow != NULL) { 915 XEvent nextEvent; 916 if (runWindow->macroCmdData == NULL) { 917 runMacro(runWindow, prog); 918 while (runWindow->macroCmdData != NULL) { 919 XtAppNextEvent(XtWidgetToApplicationContext( 920 runWindow->shell), &nextEvent); 921 ServerDispatchEvent(&nextEvent); 922 } 923 } else { 924 /* If we come here this means that the string was parsed 925 from within another macro via load_macro_file(). In 926 this case, plain code segments outside of define 927 blocks are rolled into one Program each and put on 928 the stack. At the end, the stack is unrolled, so the 929 plain Programs would be executed in the wrong order. 930 931 So we don't hand the Programs over to the interpreter 932 just yet (via RunMacroAsSubrCall()), but put it on a 933 stack of our own, reversing order once again. */ 934 Push(progStack, (void*) prog); 935 } 936 } 937 inPtr = stoppedAt; 938 } 939 } 940 941 /* Unroll reversal stack for macros loaded from macros. */ 942 while (NULL != (prog = (Program*) Pop(progStack))) { 943 RunMacroAsSubrCall(prog); 944 } 945 946 /* This stack is empty, so just free it without checking the members. */ 947 NEditFree(progStack); 948 949 return True; 950 } 951 952 /* 953 ** Run a pre-compiled macro, changing the interface state to reflect that 954 ** a macro is running, and handling preemption, resumption, and cancellation. 955 ** frees prog when macro execution is complete; 956 */ 957 static void runMacro(WindowInfo *window, Program *prog) 958 { 959 DataValue result; 960 char *errMsg; 961 int stat; 962 macroCmdInfo *cmdData; 963 XmString s; 964 965 /* If a macro is already running, just call the program as a subroutine, 966 instead of starting a new one, so we don't have to keep a separate 967 context, and the macros will serialize themselves automatically */ 968 if (window->macroCmdData != NULL) { 969 RunMacroAsSubrCall(prog); 970 return; 971 } 972 973 /* put up a watch cursor over the waiting window */ 974 BeginWait(window->shell); 975 976 /* enable the cancel menu item */ 977 XtVaSetValues(window->cancelMacroItem, XmNlabelString, 978 s=XmStringCreateSimple("Cancel Macro"), NULL); 979 XmStringFree(s); 980 SetSensitive(window, window->cancelMacroItem, True); 981 982 /* Create a data structure for passing macro execution information around 983 amongst the callback routines which will process i/o and completion */ 984 cmdData = (macroCmdInfo *)NEditMalloc(sizeof(macroCmdInfo)); 985 window->macroCmdData = cmdData; 986 cmdData->bannerIsUp = False; 987 cmdData->closeOnCompletion = False; 988 cmdData->program = prog; 989 cmdData->context = NULL; 990 cmdData->continueWorkProcID = 0; 991 cmdData->dialog = NULL; 992 993 /* Set up timer proc for putting up banner when macro takes too long */ 994 cmdData->bannerTimeoutID = XtAppAddTimeOut( 995 XtWidgetToApplicationContext(window->shell), BANNER_WAIT_TIME, 996 bannerTimeoutProc, window); 997 998 /* Begin macro execution */ 999 stat = ExecuteMacro(window, prog, 0, NULL, &result, &cmdData->context, 1000 &errMsg); 1001 1002 if (stat == MACRO_ERROR) 1003 { 1004 finishMacroCmdExecution(window); 1005 DialogF(DF_ERR, window->shell, 1, "Macro Error", 1006 "Error executing macro: %s", "OK", errMsg); 1007 return; 1008 } 1009 1010 if (stat == MACRO_DONE) { 1011 finishMacroCmdExecution(window); 1012 return; 1013 } 1014 if (stat == MACRO_TIME_LIMIT) { 1015 ResumeMacroExecution(window); 1016 return; 1017 } 1018 /* (stat == MACRO_PREEMPT) Macro was preempted */ 1019 } 1020 1021 /* 1022 ** Continue with macro execution after preemption. Called by the routines 1023 ** whose actions cause preemption when they have completed their lengthy tasks. 1024 ** Re-establishes macro execution work proc. Window must be the window in 1025 ** which the macro is executing (the window to which macroCmdData is attached), 1026 ** and not the window to which operations are focused. 1027 */ 1028 void ResumeMacroExecution(WindowInfo *window) 1029 { 1030 macroCmdInfo *cmdData = (macroCmdInfo *)window->macroCmdData; 1031 1032 if (cmdData != NULL) 1033 cmdData->continueWorkProcID = XtAppAddWorkProc( 1034 XtWidgetToApplicationContext(window->shell), 1035 continueWorkProc, window); 1036 } 1037 1038 /* 1039 ** Cancel the macro command in progress (user cancellation via GUI) 1040 */ 1041 void AbortMacroCommand(WindowInfo *window) 1042 { 1043 if (window->macroCmdData == NULL) 1044 return; 1045 1046 if (window->macroBlocking) 1047 return; 1048 1049 /* If there's both a macro and a shell command executing, the shell command 1050 must have been called from the macro. When called from a macro, shell 1051 commands don't put up cancellation controls of their own, but rely 1052 instead on the macro cancellation mechanism (here) */ 1053 if (window->shellCmdData != NULL) 1054 AbortShellCommand(window); 1055 1056 /* Free the continuation */ 1057 FreeRestartData(((macroCmdInfo *)window->macroCmdData)->context); 1058 1059 /* Kill the macro command */ 1060 finishMacroCmdExecution(window); 1061 } 1062 1063 /* 1064 ** Call this before closing a window, to clean up macro references to the 1065 ** window, stop any macro which might be running from it, free associated 1066 ** memory, and check that a macro is not attempting to close the window from 1067 ** which it is run. If this is being called from a macro, and the window 1068 ** this routine is examining is the window from which the macro was run, this 1069 ** routine will return False, and the caller must NOT CLOSE THE WINDOW. 1070 ** Instead, empty it and make it Untitled, and let the macro completion 1071 ** process close the window when the macro is finished executing. 1072 */ 1073 int MacroWindowCloseActions(WindowInfo *window) 1074 { 1075 macroCmdInfo *mcd, *cmdData = window->macroCmdData; 1076 WindowInfo *w; 1077 1078 if (MacroRecordActionHook != 0 && MacroRecordWindow == window) { 1079 FinishLearn(); 1080 } 1081 1082 /* If no macro is executing in the window, allow the close, but check 1083 if macros executing in other windows have it as focus. If so, set 1084 their focus back to the window from which they were originally run */ 1085 if (cmdData == NULL) { 1086 for (w=WindowList; w!=NULL; w=w->next) { 1087 mcd = (macroCmdInfo *)w->macroCmdData; 1088 if (w == MacroRunWindow() && MacroFocusWindow() == window) 1089 SetMacroFocusWindow(MacroRunWindow()); 1090 else if (mcd != NULL && mcd->context->focusWindow == window) 1091 mcd->context->focusWindow = mcd->context->runWindow; 1092 } 1093 return True; 1094 } 1095 1096 /* If the macro currently running (and therefore calling us, because 1097 execution must otherwise return to the main loop to execute any 1098 commands), is running in this window, tell the caller not to close, 1099 and schedule window close on completion of macro */ 1100 if (window == MacroRunWindow()) { 1101 cmdData->closeOnCompletion = True; 1102 return False; 1103 } 1104 1105 /* Free the continuation */ 1106 FreeRestartData(cmdData->context); 1107 1108 /* Kill the macro command */ 1109 finishMacroCmdExecution(window); 1110 return True; 1111 } 1112 1113 /* 1114 ** Clean up after the execution of a macro command: free memory, and restore 1115 ** the user interface state. 1116 */ 1117 static void finishMacroCmdExecution(WindowInfo *window) 1118 { 1119 macroCmdInfo *cmdData = window->macroCmdData; 1120 int closeOnCompletion = cmdData->closeOnCompletion; 1121 XmString s; 1122 XClientMessageEvent event; 1123 1124 /* Cancel pending timeout and work proc */ 1125 if (cmdData->bannerTimeoutID != 0) 1126 XtRemoveTimeOut(cmdData->bannerTimeoutID); 1127 if (cmdData->continueWorkProcID != 0) 1128 XtRemoveWorkProc(cmdData->continueWorkProcID); 1129 1130 /* Clean up waiting-for-macro-command-to-complete mode */ 1131 EndWait(window->shell); 1132 XtVaSetValues(window->cancelMacroItem, XmNlabelString, 1133 s=XmStringCreateSimple("Cancel Learn"), NULL); 1134 XmStringFree(s); 1135 SetSensitive(window, window->cancelMacroItem, False); 1136 if (cmdData->bannerIsUp) 1137 ClearModeMessage(window); 1138 1139 /* If a dialog was up, get rid of it */ 1140 if (cmdData->dialog != NULL) 1141 XtDestroyWidget(XtParent(cmdData->dialog)); 1142 1143 /* Free execution information */ 1144 FreeProgram(cmdData->program); 1145 NEditFree(cmdData); 1146 window->macroCmdData = NULL; 1147 1148 /* If macro closed its own window, window was made empty and untitled, 1149 but close was deferred until completion. This is completion, so if 1150 the window is still empty, do the close */ 1151 if (closeOnCompletion && !window->filenameSet && !window->fileChanged) { 1152 CloseWindow(window); 1153 window = NULL; 1154 } 1155 1156 /* If no other macros are executing, do garbage collection */ 1157 SafeGC(); 1158 1159 /* In processing the .neditmacro file (and possibly elsewhere), there 1160 is an event loop which waits for macro completion. Send an event 1161 to wake up that loop, otherwise execution will stall until the user 1162 does something to the window. */ 1163 if (!closeOnCompletion) { 1164 event.format = 8; 1165 event.type = ClientMessage; 1166 XSendEvent(XtDisplay(window->shell), XtWindow(window->shell), False, 1167 NoEventMask, (XEvent *)&event); 1168 } 1169 } 1170 1171 /* 1172 ** Do garbage collection of strings if there are no macros currently 1173 ** executing. NEdit's macro language GC strategy is to call this routine 1174 ** whenever a macro completes. If other macros are still running (preempted 1175 ** or waiting for a shell command or dialog), this does nothing and therefore 1176 ** defers GC to the completion of the last macro out. 1177 */ 1178 void SafeGC(void) 1179 { 1180 WindowInfo *win; 1181 1182 for (win=WindowList; win!=NULL; win=win->next) 1183 if (win->macroCmdData != NULL || InSmartIndentMacros(win)) 1184 return; 1185 GarbageCollectStrings(); 1186 } 1187 1188 /* 1189 ** Executes macro string "macro" using the lastFocus pane in "window". 1190 ** Reports errors via a dialog posted over "window", integrating the name 1191 ** "errInName" into the message to help identify the source of the error. 1192 */ 1193 void DoMacro(WindowInfo *window, const char *macro, const char *errInName) 1194 { 1195 Program *prog; 1196 char *errMsg, *stoppedAt, *tMacro; 1197 int macroLen; 1198 1199 /* Add a terminating newline (which command line users are likely to omit 1200 since they are typically invoking a single routine) */ 1201 macroLen = strlen(macro); 1202 tMacro = (char*)NEditMalloc(strlen(macro)+2); 1203 strncpy(tMacro, macro, macroLen); 1204 tMacro[macroLen] = '\n'; 1205 tMacro[macroLen+1] = '\0'; 1206 1207 /* Parse the macro and report errors if it fails */ 1208 prog = ParseMacro(tMacro, &errMsg, &stoppedAt); 1209 if (prog == NULL) { 1210 ParseError(window->shell, tMacro, stoppedAt, errInName, errMsg); 1211 NEditFree(tMacro); 1212 return; 1213 } 1214 NEditFree(tMacro); 1215 1216 /* run the executable program (prog is freed upon completion) */ 1217 runMacro(window, prog); 1218 } 1219 1220 /* 1221 ** Get the current Learn/Replay macro in text form. Returned string is a 1222 ** pointer to the stored macro and should not be freed by the caller (and 1223 ** will cease to exist when the next replay macro is installed) 1224 */ 1225 char *GetReplayMacro(void) 1226 { 1227 return ReplayMacro; 1228 } 1229 1230 /* 1231 ** Present the user a dialog for "Repeat" command 1232 */ 1233 void RepeatDialog(WindowInfo *window) 1234 { 1235 Widget form, selBox, radioBox, timesForm; 1236 repeatDialog *rd; 1237 Arg selBoxArgs[1]; 1238 char *lastCmdLabel, *parenChar; 1239 XmString s1; 1240 int cmdNameLen; 1241 1242 if (LastCommand == NULL) 1243 { 1244 DialogF(DF_WARN, window->shell, 1, "Repeat Macro", 1245 "No previous commands or learn/\nreplay sequences to repeat", 1246 "OK"); 1247 return; 1248 } 1249 1250 /* Remeber the last command, since the user is allowed to work in the 1251 window while the dialog is up */ 1252 rd = (repeatDialog *)NEditMalloc(sizeof(repeatDialog)); 1253 rd->lastCommand = NEditStrdup(LastCommand); 1254 1255 /* make a label for the Last command item of the dialog, which includes 1256 the last executed action name */ 1257 parenChar = strchr(LastCommand, '('); 1258 if (parenChar == NULL) 1259 return; 1260 cmdNameLen = parenChar-LastCommand; 1261 lastCmdLabel = (char*)NEditMalloc(16 + cmdNameLen); 1262 strcpy(lastCmdLabel, "Last Command ("); 1263 strncpy(&lastCmdLabel[14], LastCommand, cmdNameLen); 1264 strcpy(&lastCmdLabel[14 + cmdNameLen], ")"); 1265 1266 XtSetArg(selBoxArgs[0], XmNautoUnmanage, False); 1267 selBox = CreatePromptDialog(window->shell, "repeat", selBoxArgs, 1); 1268 rd->shell = XtParent(selBox); 1269 XtAddCallback(rd->shell, XmNdestroyCallback, repeatDestroyCB, rd); 1270 XtAddCallback(selBox, XmNokCallback, repeatOKCB, rd); 1271 XtAddCallback(selBox, XmNapplyCallback, repeatApplyCB, rd); 1272 XtAddCallback(selBox, XmNcancelCallback, repeatCancelCB, rd); 1273 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_TEXT)); 1274 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_SELECTION_LABEL)); 1275 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_HELP_BUTTON)); 1276 XtUnmanageChild(XmSelectionBoxGetChild(selBox, XmDIALOG_APPLY_BUTTON)); 1277 XtVaSetValues(XtParent(selBox), XmNtitle, "Repeat Macro", NULL); 1278 AddMotifCloseCallback(XtParent(selBox), repeatCancelCB, rd); 1279 1280 form = XtVaCreateManagedWidget("form", xmFormWidgetClass, selBox, NULL); 1281 1282 radioBox = XtVaCreateManagedWidget("cmdSrc", xmRowColumnWidgetClass, form, 1283 XmNradioBehavior, True, 1284 XmNorientation, XmHORIZONTAL, 1285 XmNpacking, XmPACK_TIGHT, 1286 XmNtopAttachment, XmATTACH_FORM, 1287 XmNleftAttachment, XmATTACH_FORM, NULL); 1288 rd->lastCmdToggle = XtVaCreateManagedWidget("lastCmdToggle", 1289 xmToggleButtonWidgetClass, radioBox, XmNset, True, 1290 XmNlabelString, s1=XmStringCreateSimple(lastCmdLabel), 1291 XmNmnemonic, 'C', NULL); 1292 XmStringFree(s1); 1293 NEditFree(lastCmdLabel); 1294 XtVaCreateManagedWidget("learnReplayToggle", 1295 xmToggleButtonWidgetClass, radioBox, XmNset, False, 1296 XmNlabelString, 1297 s1=XmStringCreateSimple("Learn/Replay"), 1298 XmNmnemonic, 'L', 1299 XmNsensitive, ReplayMacro != NULL, NULL); 1300 XmStringFree(s1); 1301 1302 timesForm = XtVaCreateManagedWidget("form", xmFormWidgetClass, form, 1303 XmNtopAttachment, XmATTACH_WIDGET, 1304 XmNtopWidget, radioBox, 1305 XmNtopOffset, 10, 1306 XmNleftAttachment, XmATTACH_FORM, NULL); 1307 radioBox = XtVaCreateManagedWidget("method", xmRowColumnWidgetClass, 1308 timesForm, 1309 XmNradioBehavior, True, 1310 XmNorientation, XmHORIZONTAL, 1311 XmNpacking, XmPACK_TIGHT, 1312 XmNtopAttachment, XmATTACH_FORM, 1313 XmNbottomAttachment, XmATTACH_FORM, 1314 XmNleftAttachment, XmATTACH_FORM, NULL); 1315 rd->inSelToggle = XtVaCreateManagedWidget("inSelToggle", 1316 xmToggleButtonWidgetClass, radioBox, XmNset, False, 1317 XmNlabelString, s1=XmStringCreateSimple("In Selection"), 1318 XmNmnemonic, 'I', NULL); 1319 XmStringFree(s1); 1320 rd->toEndToggle = XtVaCreateManagedWidget("toEndToggle", 1321 xmToggleButtonWidgetClass, radioBox, XmNset, False, 1322 XmNlabelString, s1=XmStringCreateSimple("To End"), 1323 XmNmnemonic, 'T', NULL); 1324 XmStringFree(s1); 1325 XtVaCreateManagedWidget("nTimesToggle", 1326 xmToggleButtonWidgetClass, radioBox, XmNset, True, 1327 XmNlabelString, s1=XmStringCreateSimple("N Times"), 1328 XmNmnemonic, 'N', 1329 XmNset, True, NULL); 1330 XmStringFree(s1); 1331 rd->repeatText = XtVaCreateManagedWidget("repeatText", xmTextWidgetClass, 1332 timesForm, 1333 XmNcolumns, 5, 1334 XmNtopAttachment, XmATTACH_FORM, 1335 XmNbottomAttachment, XmATTACH_FORM, 1336 XmNleftAttachment, XmATTACH_WIDGET, 1337 XmNleftWidget, radioBox, NULL); 1338 RemapDeleteKey(rd->repeatText); 1339 1340 /* Handle mnemonic selection of buttons and focus to dialog */ 1341 AddDialogMnemonicHandler(form, FALSE); 1342 1343 /* Set initial focus */ 1344 #if XmVersion >= 1002 1345 XtVaSetValues(form, XmNinitialFocus, timesForm, NULL); 1346 XtVaSetValues(timesForm, XmNinitialFocus, rd->repeatText, NULL); 1347 #endif 1348 1349 /* put up dialog */ 1350 rd->forWindow = window; 1351 ManageDialogCenteredOnPointer(selBox); 1352 } 1353 1354 static void repeatOKCB(Widget w, XtPointer clientData, XtPointer callData) 1355 { 1356 repeatDialog *rd = (repeatDialog *)clientData; 1357 1358 if (doRepeatDialogAction(rd, ((XmAnyCallbackStruct *)callData)->event)) 1359 XtDestroyWidget(rd->shell); 1360 } 1361 1362 /* Note that the apply button is not managed in the repeat dialog. The dialog 1363 itself is capable of non-modal operation, but to be complete, it needs 1364 to dynamically update last command, dimming of learn/replay, possibly a 1365 stop button for the macro, and possibly in-selection with selection */ 1366 static void repeatApplyCB(Widget w, XtPointer clientData, XtPointer callData) 1367 { 1368 doRepeatDialogAction((repeatDialog *)clientData, 1369 ((XmAnyCallbackStruct *)callData)->event); 1370 } 1371 1372 static int doRepeatDialogAction(repeatDialog *rd, XEvent *event) 1373 { 1374 int nTimes; 1375 char nTimesStr[TYPE_INT_STR_SIZE(int)]; 1376 char *params[2]; 1377 1378 /* Find out from the dialog how to repeat the command */ 1379 if (XmToggleButtonGetState(rd->inSelToggle)) 1380 { 1381 if (!rd->forWindow->buffer->primary.selected) 1382 { 1383 DialogF(DF_WARN, rd->shell, 1, "Repeat Macro", 1384 "No selection in window to repeat within", "OK"); 1385 XmProcessTraversal(rd->inSelToggle, XmTRAVERSE_CURRENT); 1386 return False; 1387 } 1388 params[0] = "in_selection"; 1389 } else if (XmToggleButtonGetState(rd->toEndToggle)) 1390 { 1391 params[0] = "to_end"; 1392 } else 1393 { 1394 if (GetIntTextWarn(rd->repeatText, &nTimes, "number of times", True) 1395 != TEXT_READ_OK) 1396 { 1397 XmProcessTraversal(rd->repeatText, XmTRAVERSE_CURRENT); 1398 return False; 1399 } 1400 sprintf(nTimesStr, "%d", nTimes); 1401 params[0] = nTimesStr; 1402 } 1403 1404 /* Figure out which command user wants to repeat */ 1405 if (XmToggleButtonGetState(rd->lastCmdToggle)) 1406 params[1] = NEditStrdup(rd->lastCommand); 1407 else { 1408 if (ReplayMacro == NULL) 1409 return False; 1410 params[1] = NEditStrdup(ReplayMacro); 1411 } 1412 1413 /* call the action routine repeat_macro to do the work */ 1414 XtCallActionProc(rd->forWindow->lastFocus, "repeat_macro", event, params,2); 1415 NEditFree(params[1]); 1416 return True; 1417 } 1418 1419 static void repeatCancelCB(Widget w, XtPointer clientData, XtPointer callData) 1420 { 1421 repeatDialog *rd = (repeatDialog *)clientData; 1422 1423 XtDestroyWidget(rd->shell); 1424 } 1425 1426 static void repeatDestroyCB(Widget w, XtPointer clientData, XtPointer callData) 1427 { 1428 repeatDialog *rd = (repeatDialog *)clientData; 1429 1430 NEditFree(rd->lastCommand); 1431 NEditFree(rd); 1432 } 1433 1434 /* 1435 ** Dispatches a macro to which repeats macro command in "command", either 1436 ** an integer number of times ("how" == positive integer), or within a 1437 ** selected range ("how" == REPEAT_IN_SEL), or to the end of the window 1438 ** ("how == REPEAT_TO_END). 1439 ** 1440 ** Note that as with most macro routines, this returns BEFORE the macro is 1441 ** finished executing 1442 */ 1443 void RepeatMacro(WindowInfo *window, const char *command, int how) 1444 { 1445 Program *prog; 1446 char *errMsg, *stoppedAt, *loopMacro, *loopedCmd; 1447 1448 if (command == NULL) 1449 return; 1450 1451 /* Wrap a for loop and counter/tests around the command */ 1452 if (how == REPEAT_TO_END) 1453 loopMacro = "lastCursor=-1\nstartPos=$cursor\n\ 1454 while($cursor>=startPos&&$cursor!=lastCursor){\nlastCursor=$cursor\n%s\n}\n"; 1455 else if (how == REPEAT_IN_SEL) 1456 loopMacro = "selStart = $selection_start\nif (selStart == -1)\nreturn\n\ 1457 selEnd = $selection_end\nset_cursor_pos(selStart)\nselect(0,0)\n\ 1458 boundText = get_range(selEnd, selEnd+10)\n\ 1459 while($cursor >= selStart && $cursor < selEnd && \\\n\ 1460 get_range(selEnd, selEnd+10) == boundText) {\n\ 1461 startLength = $text_length\n%s\n\ 1462 selEnd += $text_length - startLength\n}\n"; 1463 else 1464 loopMacro = "for(i=0;i<%d;i++){\n%s\n}\n"; 1465 loopedCmd = (char*)NEditMalloc(strlen(command) + strlen(loopMacro) + 25); 1466 if (how == REPEAT_TO_END || how == REPEAT_IN_SEL) 1467 sprintf(loopedCmd, loopMacro, command); 1468 else 1469 sprintf(loopedCmd, loopMacro, how, command); 1470 1471 /* Parse the resulting macro into an executable program "prog" */ 1472 prog = ParseMacro(loopedCmd, &errMsg, &stoppedAt); 1473 if (prog == NULL) { 1474 fprintf(stderr, "XNEdit internal error, repeat macro syntax wrong: %s\n", 1475 errMsg); 1476 return; 1477 } 1478 NEditFree(loopedCmd); 1479 1480 /* run the executable program */ 1481 runMacro(window, prog); 1482 } 1483 1484 /* 1485 ** Macro recording action hook for Learn/Replay, added temporarily during 1486 ** learn. 1487 */ 1488 static void learnActionHook(Widget w, XtPointer clientData, String actionName, 1489 XEvent *event, String *params, Cardinal *numParams) 1490 { 1491 WindowInfo *window; 1492 int i; 1493 char *actionString; 1494 1495 /* Select only actions in text panes in the window for which this 1496 action hook is recording macros (from clientData). */ 1497 for (window=WindowList; window!=NULL; window=window->next) { 1498 if (window->textArea == w) 1499 break; 1500 for (i=0; i<window->nPanes; i++) { 1501 if (window->textPanes[i] == w) 1502 break; 1503 } 1504 if (i < window->nPanes) 1505 break; 1506 } 1507 if (window == NULL || window != (WindowInfo *)clientData) 1508 return; 1509 1510 /* beep on un-recordable operations which require a mouse position, to 1511 remind the user that the action was not recorded */ 1512 if (isMouseAction(actionName)) { 1513 XBell(XtDisplay(w), 0); 1514 return; 1515 } 1516 1517 /* Record the action and its parameters */ 1518 actionString = actionToString(w, actionName, event, params, *numParams); 1519 if (actionString != NULL) { 1520 BufInsert(MacroRecordBuf, MacroRecordBuf->length, actionString); 1521 NEditFree(actionString); 1522 } 1523 } 1524 1525 /* 1526 ** Permanent action hook for remembering last action for possible replay 1527 */ 1528 static void lastActionHook(Widget w, XtPointer clientData, String actionName, 1529 XEvent *event, String *params, Cardinal *numParams) 1530 { 1531 WindowInfo *window; 1532 int i; 1533 char *actionString; 1534 1535 /* Find the window to which this action belongs */ 1536 for (window=WindowList; window!=NULL; window=window->next) { 1537 if (window->textArea == w) 1538 break; 1539 for (i=0; i<window->nPanes; i++) { 1540 if (window->textPanes[i] == w) 1541 break; 1542 } 1543 if (i < window->nPanes) 1544 break; 1545 } 1546 if (window == NULL) 1547 return; 1548 1549 /* The last action is recorded for the benefit of repeating the last 1550 action. Don't record repeat_macro and wipe out the real action */ 1551 if (!strcmp(actionName, "repeat_macro")) 1552 return; 1553 1554 /* Record the action and its parameters */ 1555 actionString = actionToString(w, actionName, event, params, *numParams); 1556 if (actionString != NULL) { 1557 NEditFree(LastCommand); 1558 LastCommand = actionString; 1559 } 1560 } 1561 1562 /* 1563 ** Create a macro string to represent an invocation of an action routine. 1564 ** Returns NULL for non-operational or un-recordable actions. 1565 */ 1566 static char *actionToString(Widget w, char *actionName, XEvent *event, 1567 String *params, Cardinal numParams) 1568 { 1569 char chars[512], *charList[1], *outStr, *outPtr; 1570 KeySym keysym; 1571 int i, nChars, nParams, length, nameLength; 1572 #ifndef NO_XMIM 1573 int status; 1574 #endif 1575 1576 if (isIgnoredAction(actionName) || isRedundantAction(actionName) || 1577 isMouseAction(actionName)) 1578 return NULL; 1579 1580 /* Convert self_insert actions, to insert_string */ 1581 if (!strcmp(actionName, "self_insert") || 1582 !strcmp(actionName, "self-insert")) { 1583 actionName = "insert_string"; 1584 1585 nChars = TextLookupString(w, (XKeyEvent *)event, chars, 511, &keysym, &status); 1586 if (nChars == 0 || status == XLookupNone || 1587 status == XLookupKeySym || status == XBufferOverflow) 1588 return NULL; 1589 1590 chars[nChars] = '\0'; 1591 charList[0] = chars; 1592 params = charList; 1593 nParams = 1; 1594 } else 1595 nParams = numParams; 1596 1597 /* Figure out the length of string required */ 1598 nameLength = strlen(actionName); 1599 length = nameLength + 3; 1600 for (i=0; i<nParams; i++) { 1601 length += escapedStringLength(params[i] ? params[i] : "") + 4; 1602 } 1603 1604 /* Allocate the string and copy the information to it */ 1605 outPtr = outStr = (char*)NEditMalloc(length + 1); 1606 strcpy(outPtr, actionName); 1607 outPtr += nameLength; 1608 *outPtr++ = '('; 1609 for (i=0; i<nParams; i++) { 1610 *outPtr++ = '\"'; 1611 outPtr += escapeStringChars(params[i] ? params[i] : "", outPtr); 1612 *outPtr++ = '\"'; *outPtr++ = ','; *outPtr++ = ' '; 1613 } 1614 if (nParams != 0) 1615 outPtr -= 2; 1616 *outPtr++ = ')'; *outPtr++ = '\n'; *outPtr++ = '\0'; 1617 return outStr; 1618 } 1619 1620 static int isMouseAction(const char *action) 1621 { 1622 int i; 1623 1624 for (i=0; i<(int)XtNumber(MouseActions); i++) 1625 if (!strcmp(action, MouseActions[i])) 1626 return True; 1627 return False; 1628 } 1629 1630 static int isRedundantAction(const char *action) 1631 { 1632 int i; 1633 1634 for (i=0; i<(int)XtNumber(RedundantActions); i++) 1635 if (!strcmp(action, RedundantActions[i])) 1636 return True; 1637 return False; 1638 } 1639 1640 static int isIgnoredAction(const char *action) 1641 { 1642 int i; 1643 1644 for (i=0; i<(int)XtNumber(IgnoredActions); i++) 1645 if (!strcmp(action, IgnoredActions[i])) 1646 return True; 1647 return False; 1648 } 1649 1650 /* 1651 ** Timer proc for putting up the "Macro Command in Progress" banner if 1652 ** the process is taking too long. 1653 */ 1654 #define MAX_TIMEOUT_MSG_LEN (MAX_ACCEL_LEN + 60) 1655 static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id) 1656 { 1657 WindowInfo *window = (WindowInfo *)clientData; 1658 macroCmdInfo *cmdData = window->macroCmdData; 1659 XmString xmCancel; 1660 char *cCancel = "\0"; 1661 char message[MAX_TIMEOUT_MSG_LEN]; 1662 1663 cmdData->bannerIsUp = True; 1664 1665 /* Extract accelerator text from menu PushButtons */ 1666 XtVaGetValues(window->cancelMacroItem, XmNacceleratorText, &xmCancel, NULL); 1667 1668 if (!XmStringEmpty(xmCancel)) 1669 { 1670 /* Translate Motif string to char* */ 1671 cCancel = GetXmStringText(xmCancel); 1672 1673 /* Free Motif String */ 1674 XmStringFree(xmCancel); 1675 } 1676 1677 /* Create message */ 1678 if (cCancel[0] == '\0') { 1679 strncpy(message, "Macro Command in Progress", MAX_TIMEOUT_MSG_LEN); 1680 message[MAX_TIMEOUT_MSG_LEN - 1] = '\0'; 1681 } 1682 else { 1683 sprintf(message, 1684 "Macro Command in Progress -- Press %s to Cancel", 1685 cCancel); 1686 } 1687 1688 /* Free C-string */ 1689 NEditFree(cCancel); 1690 1691 SetModeMessage(window, message); 1692 cmdData->bannerTimeoutID = 0; 1693 } 1694 1695 /* 1696 ** Work proc for continuing execution of a preempted macro. 1697 ** 1698 ** Xt WorkProcs are designed to run first-in first-out, which makes them 1699 ** very bad at sharing time between competing tasks. For this reason, it's 1700 ** usually bad to use work procs anywhere where their execution is likely to 1701 ** overlap. Using a work proc instead of a timer proc (which I usually 1702 ** prefer) here means macros will probably share time badly, but we're more 1703 ** interested in making the macros cancelable, and in continuing other work 1704 ** than having users run a bunch of them at once together. 1705 */ 1706 static Boolean continueWorkProc(XtPointer clientData) 1707 { 1708 WindowInfo *window = (WindowInfo *)clientData; 1709 macroCmdInfo *cmdData = window->macroCmdData; 1710 char *errMsg; 1711 int stat; 1712 DataValue result; 1713 1714 stat = ContinueMacro(cmdData->context, &result, &errMsg); 1715 if (stat == MACRO_ERROR) 1716 { 1717 finishMacroCmdExecution(window); 1718 DialogF(DF_ERR, window->shell, 1, "Macro Error", 1719 "Error executing macro: %s", "OK", errMsg); 1720 return True; 1721 } else if (stat == MACRO_DONE) 1722 { 1723 finishMacroCmdExecution(window); 1724 return True; 1725 } else if (stat == MACRO_PREEMPT) 1726 { 1727 cmdData->continueWorkProcID = 0; 1728 return True; 1729 } 1730 1731 /* Macro exceeded time slice, re-schedule it */ 1732 if (stat != MACRO_TIME_LIMIT) 1733 return True; /* shouldn't happen */ 1734 return False; 1735 } 1736 1737 /* 1738 ** Copy fromString to toString replacing special characters in strings, such 1739 ** that they can be read back by the macro parser's string reader. i.e. double 1740 ** quotes are replaced by \", backslashes are replaced with \\, C-std control 1741 ** characters like \n are replaced with their backslash counterparts. This 1742 ** routine should be kept reasonably in sync with yylex in parse.y. Companion 1743 ** routine escapedStringLength predicts the length needed to write the string 1744 ** when it is expanded with the additional characters. Returns the number 1745 ** of characters to which the string expanded. 1746 */ 1747 static int escapeStringChars(char *fromString, char *toString) 1748 { 1749 char *e, *c, *outPtr = toString; 1750 1751 /* substitute escape sequences */ 1752 for (c=fromString; *c!='\0'; c++) { 1753 for (e=EscapeChars; *e!='\0'; e++) { 1754 if (*c == *e) { 1755 *outPtr++ = '\\'; 1756 *outPtr++ = ReplaceChars[e-EscapeChars]; 1757 break; 1758 } 1759 } 1760 if (*e == '\0') 1761 *outPtr++ = *c; 1762 } 1763 *outPtr = '\0'; 1764 return outPtr - toString; 1765 } 1766 1767 /* 1768 ** Predict the length of a string needed to hold a copy of "string" with 1769 ** special characters replaced with escape sequences by escapeStringChars. 1770 */ 1771 static int escapedStringLength(char *string) 1772 { 1773 char *c, *e; 1774 int length = 0; 1775 1776 /* calculate length and allocate returned string */ 1777 for (c=string; *c!='\0'; c++) { 1778 for (e=EscapeChars; *e!='\0'; e++) { 1779 if (*c == *e) { 1780 length++; 1781 break; 1782 } 1783 } 1784 length++; 1785 } 1786 return length; 1787 } 1788 1789 /* 1790 ** Built-in macro subroutine for getting the length of a string 1791 */ 1792 static int lengthMS(WindowInfo *window, DataValue *argList, int nArgs, 1793 DataValue *result, char **errMsg) 1794 { 1795 char *string, stringStorage[TYPE_INT_STR_SIZE(int)]; 1796 1797 if (nArgs != 1) 1798 return wrongNArgsErr(errMsg); 1799 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) 1800 return False; 1801 result->tag = INT_TAG; 1802 result->val.n = strlen(string); 1803 return True; 1804 } 1805 1806 /* 1807 ** Built-in macro subroutines for min and max 1808 */ 1809 static int minMS(WindowInfo *window, DataValue *argList, int nArgs, 1810 DataValue *result, char **errMsg) 1811 { 1812 int minVal, value, i; 1813 1814 if (nArgs == 1) 1815 return tooFewArgsErr(errMsg); 1816 if (!readIntArg(argList[0], &minVal, errMsg)) 1817 return False; 1818 for (i=0; i<nArgs; i++) { 1819 if (!readIntArg(argList[i], &value, errMsg)) 1820 return False; 1821 minVal = value < minVal ? value : minVal; 1822 } 1823 result->tag = INT_TAG; 1824 result->val.n = minVal; 1825 return True; 1826 } 1827 static int maxMS(WindowInfo *window, DataValue *argList, int nArgs, 1828 DataValue *result, char **errMsg) 1829 { 1830 int maxVal, value, i; 1831 1832 if (nArgs == 1) 1833 return tooFewArgsErr(errMsg); 1834 if (!readIntArg(argList[0], &maxVal, errMsg)) 1835 return False; 1836 for (i=0; i<nArgs; i++) { 1837 if (!readIntArg(argList[i], &value, errMsg)) 1838 return False; 1839 maxVal = value > maxVal ? value : maxVal; 1840 } 1841 result->tag = INT_TAG; 1842 result->val.n = maxVal; 1843 return True; 1844 } 1845 1846 static int focusWindowMS(WindowInfo *window, DataValue *argList, int nArgs, 1847 DataValue *result, char **errMsg) 1848 { 1849 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 1850 WindowInfo *w; 1851 char fullname[2*MAXPATHLEN]; 1852 char normalizedString[MAXPATHLEN]; 1853 1854 /* Read the argument representing the window to focus to, and translate 1855 it into a pointer to a real WindowInfo */ 1856 if (nArgs != 1) 1857 return wrongNArgsErr(errMsg); 1858 1859 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) { 1860 return False; 1861 } else if (!strcmp(string, "last")) { 1862 w = WindowList; 1863 } else if (!strcmp(string, "next")) { 1864 w = window->next; 1865 } else if (strlen(string) >= MAXPATHLEN) { 1866 *errMsg = "Pathname too long in focus_window()"; 1867 return False; 1868 } else { 1869 /* just use the plain name as supplied */ 1870 for (w=WindowList; w != NULL; w = w->next) { 1871 snprintf(fullname, sizeof(fullname), "%s%s", w->path, w->filename); 1872 if (!strcmp(string, fullname)) { 1873 break; 1874 } 1875 } 1876 /* didn't work? try normalizing the string passed in */ 1877 if (w == NULL) { 1878 strncpy(normalizedString, string, MAXPATHLEN); 1879 normalizedString[MAXPATHLEN-1] = '\0'; 1880 if (1 == NormalizePathname(normalizedString)) { 1881 /* Something is broken with the input pathname. */ 1882 *errMsg = "Pathname too long in focus_window()"; 1883 return False; 1884 } 1885 for (w=WindowList; w != NULL; w = w->next) { 1886 snprintf(fullname, sizeof(fullname), 1887 "%s%s", w->path, w->filename); 1888 if (!strcmp(normalizedString, fullname)) 1889 break; 1890 } 1891 } 1892 } 1893 1894 /* If no matching window was found, return empty string and do nothing */ 1895 if (w == NULL) { 1896 result->tag = STRING_TAG; 1897 result->val.str.rep = PERM_ALLOC_STR(""); 1898 result->val.str.len = 0; 1899 return True; 1900 } 1901 1902 /* Change the focused window to the requested one */ 1903 SetMacroFocusWindow(w); 1904 1905 /* turn on syntax highlight that might have been deferred */ 1906 if (w->highlightSyntax && w->highlightData==NULL) 1907 StartHighlighting(w, False); 1908 1909 /* Return the name of the window */ 1910 result->tag = STRING_TAG; 1911 size_t result_str_len = strlen(w->path)+strlen(w->filename)+1; 1912 AllocNString(&result->val.str, result_str_len); 1913 snprintf(result->val.str.rep, result_str_len, "%s%s", w->path, w->filename); 1914 return True; 1915 } 1916 1917 /* 1918 ** Built-in macro subroutine for getting text from the current window's text 1919 ** buffer 1920 */ 1921 static int getRangeMS(WindowInfo *window, DataValue *argList, int nArgs, 1922 DataValue *result, char **errMsg) 1923 { 1924 int from, to; 1925 textBuffer *buf = window->buffer; 1926 char *rangeText; 1927 1928 /* Validate arguments and convert to int */ 1929 if (nArgs != 2) 1930 return wrongNArgsErr(errMsg); 1931 if (!readIntArg(argList[0], &from, errMsg)) 1932 return False; 1933 if (!readIntArg(argList[1], &to, errMsg)) 1934 return False; 1935 if (from < 0) from = 0; 1936 if (from > buf->length) from = buf->length; 1937 if (to < 0) to = 0; 1938 if (to > buf->length) to = buf->length; 1939 if (from > to) {int temp = from; from = to; to = temp;} 1940 1941 /* Copy text from buffer (this extra copy could be avoided if textBuf.c 1942 provided a routine for writing into a pre-allocated string) */ 1943 result->tag = STRING_TAG; 1944 AllocNString(&result->val.str, to - from + 1); 1945 rangeText = BufGetRange(buf, from, to); 1946 BufUnsubstituteNullChars(rangeText, buf); 1947 strcpy(result->val.str.rep, rangeText); 1948 /* Note: after the un-substitution, it is possible that strlen() != len, 1949 but that's because strlen() can't deal with 0-characters. */ 1950 NEditFree(rangeText); 1951 return True; 1952 } 1953 1954 /* 1955 ** Built-in macro subroutine for getting a single character at the position 1956 ** given, from the current window 1957 */ 1958 static int getCharacterMS(WindowInfo *window, DataValue *argList, int nArgs, 1959 DataValue *result, char **errMsg) 1960 { 1961 int pos; 1962 textBuffer *buf = window->buffer; 1963 1964 /* Validate argument and convert it to int */ 1965 if (nArgs != 1) 1966 return wrongNArgsErr(errMsg); 1967 if (!readIntArg(argList[0], &pos, errMsg)) 1968 return False; 1969 if (pos < 0) pos = 0; 1970 if (pos > buf->length) pos = buf->length; 1971 1972 /* Return the character in a pre-allocated string) */ 1973 result->tag = STRING_TAG; 1974 AllocNString(&result->val.str, 2); 1975 result->val.str.rep[0] = BufGetCharacter(buf, pos); 1976 BufUnsubstituteNullChars(result->val.str.rep, buf); 1977 /* Note: after the un-substitution, it is possible that strlen() != len, 1978 but that's because strlen() can't deal with 0-characters. */ 1979 return True; 1980 } 1981 1982 /* 1983 ** Built-in macro subroutine for replacing text in the current window's text 1984 ** buffer 1985 */ 1986 static int replaceRangeMS(WindowInfo *window, DataValue *argList, int nArgs, 1987 DataValue *result, char **errMsg) 1988 { 1989 int from, to; 1990 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 1991 textBuffer *buf = window->buffer; 1992 1993 /* Validate arguments and convert to int */ 1994 if (nArgs != 3) 1995 return wrongNArgsErr(errMsg); 1996 if (!readIntArg(argList[0], &from, errMsg)) 1997 return False; 1998 if (!readIntArg(argList[1], &to, errMsg)) 1999 return False; 2000 if (!readStringArg(argList[2], &string, stringStorage, errMsg)) 2001 return False; 2002 if (from < 0) from = 0; 2003 if (from > buf->length) from = buf->length; 2004 if (to < 0) to = 0; 2005 if (to > buf->length) to = buf->length; 2006 if (from > to) {int temp = from; from = to; to = temp;} 2007 2008 /* Don't allow modifications if the window is read-only */ 2009 if (IS_ANY_LOCKED(window->lockReasons)) { 2010 XBell(XtDisplay(window->shell), 0); 2011 result->tag = NO_TAG; 2012 return True; 2013 } 2014 2015 /* There are no null characters in the string (because macro strings 2016 still have null termination), but if the string contains the 2017 character used by the buffer for null substitution, it could 2018 theoretically become a null. In the highly unlikely event that 2019 all of the possible substitution characters in the buffer are used 2020 up, stop the macro and tell the user of the failure */ 2021 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) { 2022 *errMsg = "Too much binary data in file"; 2023 return False; 2024 } 2025 2026 /* Do the replace */ 2027 BufReplace(buf, from, to, string); 2028 result->tag = NO_TAG; 2029 return True; 2030 } 2031 2032 /* 2033 ** Built-in macro subroutine for replacing the primary-selection selected 2034 ** text in the current window's text buffer 2035 */ 2036 static int replaceSelectionMS(WindowInfo *window, DataValue *argList, int nArgs, 2037 DataValue *result, char **errMsg) 2038 { 2039 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 2040 2041 /* Validate argument and convert to string */ 2042 if (nArgs != 1) 2043 return wrongNArgsErr(errMsg); 2044 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) 2045 return False; 2046 2047 /* Don't allow modifications if the window is read-only */ 2048 if (IS_ANY_LOCKED(window->lockReasons)) { 2049 XBell(XtDisplay(window->shell), 0); 2050 result->tag = NO_TAG; 2051 return True; 2052 } 2053 2054 /* There are no null characters in the string (because macro strings 2055 still have null termination), but if the string contains the 2056 character used by the buffer for null substitution, it could 2057 theoretically become a null. In the highly unlikely event that 2058 all of the possible substitution characters in the buffer are used 2059 up, stop the macro and tell the user of the failure */ 2060 if (!BufSubstituteNullChars(string, strlen(string), window->buffer)) { 2061 *errMsg = "Too much binary data in file"; 2062 return False; 2063 } 2064 2065 /* Do the replace */ 2066 BufReplaceSelected(window->buffer, string); 2067 result->tag = NO_TAG; 2068 return True; 2069 } 2070 2071 /* 2072 ** Built-in macro subroutine for getting the text currently selected by 2073 ** the primary selection in the current window's text buffer, or in any 2074 ** part of screen if "any" argument is given 2075 */ 2076 static int getSelectionMS(WindowInfo *window, DataValue *argList, int nArgs, 2077 DataValue *result, char **errMsg) 2078 { 2079 char *selText; 2080 2081 /* Read argument list to check for "any" keyword, and get the appropriate 2082 selection */ 2083 if (nArgs != 0 && nArgs != 1) 2084 return wrongNArgsErr(errMsg); 2085 if (nArgs == 1) { 2086 if (argList[0].tag != STRING_TAG || strcmp(argList[0].val.str.rep, "any")) { 2087 *errMsg = "Unrecognized argument to %s"; 2088 return False; 2089 } 2090 selText = GetAnySelection(window); 2091 if (selText == NULL) 2092 selText = NEditStrdup(""); 2093 } else { 2094 selText = BufGetSelectionText(window->buffer); 2095 BufUnsubstituteNullChars(selText, window->buffer); 2096 } 2097 2098 /* Return the text as an allocated string */ 2099 result->tag = STRING_TAG; 2100 AllocNStringCpy(&result->val.str, selText); 2101 NEditFree(selText); 2102 return True; 2103 } 2104 2105 /* 2106 ** Built-in macro subroutine for determining if implicit conversion of 2107 ** a string to number will succeed or fail 2108 */ 2109 static int validNumberMS(WindowInfo *window, DataValue *argList, int nArgs, 2110 DataValue *result, char **errMsg) 2111 { 2112 char *string, stringStorage[TYPE_INT_STR_SIZE(int)]; 2113 2114 if (nArgs != 1) { 2115 return wrongNArgsErr(errMsg); 2116 } 2117 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) { 2118 return False; 2119 } 2120 2121 result->tag = INT_TAG; 2122 result->val.n = StringToNum(string, NULL); 2123 2124 return True; 2125 } 2126 2127 /* 2128 ** Built-in macro subroutine for replacing a substring within another string 2129 */ 2130 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs, 2131 DataValue *result, char **errMsg) 2132 { 2133 int from, to, length, replaceLen, outLen; 2134 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *replStr; 2135 2136 /* Validate arguments and convert to int */ 2137 if (nArgs != 4) 2138 return wrongNArgsErr(errMsg); 2139 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg)) 2140 return False; 2141 if (!readIntArg(argList[1], &from, errMsg)) 2142 return False; 2143 if (!readIntArg(argList[2], &to, errMsg)) 2144 return False; 2145 if (!readStringArg(argList[3], &replStr, stringStorage[1], errMsg)) 2146 return False; 2147 length = strlen(string); 2148 if (from < 0) from = 0; 2149 if (from > length) from = length; 2150 if (to < 0) to = 0; 2151 if (to > length) to = length; 2152 if (from > to) {int temp = from; from = to; to = temp;} 2153 2154 /* Allocate a new string and do the replacement */ 2155 replaceLen = strlen(replStr); 2156 outLen = length - (to - from) + replaceLen; 2157 result->tag = STRING_TAG; 2158 AllocNString(&result->val.str, outLen+1); 2159 strncpy(result->val.str.rep, string, from); 2160 strncpy(&result->val.str.rep[from], replStr, replaceLen); 2161 strncpy(&result->val.str.rep[from + replaceLen], &string[to], length - to); 2162 return True; 2163 } 2164 2165 /* 2166 ** Built-in macro subroutine for getting a substring of a string. 2167 ** Called as substring(string, from [, to]) 2168 */ 2169 static int substringMS(WindowInfo *window, DataValue *argList, int nArgs, 2170 DataValue *result, char **errMsg) 2171 { 2172 int from, to, length; 2173 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 2174 2175 /* Validate arguments and convert to int */ 2176 if (nArgs != 2 && nArgs != 3) 2177 return wrongNArgsErr(errMsg); 2178 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) 2179 return False; 2180 if (!readIntArg(argList[1], &from, errMsg)) 2181 return False; 2182 length = to = strlen(string); 2183 if (nArgs == 3) 2184 if (!readIntArg(argList[2], &to, errMsg)) 2185 return False; 2186 if (from < 0) from += length; 2187 if (from < 0) from = 0; 2188 if (from > length) from = length; 2189 if (to < 0) to += length; 2190 if (to < 0) to = 0; 2191 if (to > length) to = length; 2192 if (from > to) to = from; 2193 2194 /* Allocate a new string and copy the sub-string into it */ 2195 result->tag = STRING_TAG; 2196 AllocNStringNCpy(&result->val.str, &string[from], to - from); 2197 return True; 2198 } 2199 2200 static int toupperMS(WindowInfo *window, DataValue *argList, int nArgs, 2201 DataValue *result, char **errMsg) 2202 { 2203 int length; 2204 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 2205 2206 /* Validate arguments and convert to int */ 2207 if (nArgs != 1) 2208 return wrongNArgsErr(errMsg); 2209 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) 2210 return False; 2211 length = strlen(string); 2212 2213 /* Allocate a new string and copy an uppercased version of the string it */ 2214 result->tag = STRING_TAG; 2215 AllocNString(&result->val.str, length + 1); 2216 UpCaseString(result->val.str.rep, string); 2217 return True; 2218 } 2219 2220 static int tolowerMS(WindowInfo *window, DataValue *argList, int nArgs, 2221 DataValue *result, char **errMsg) 2222 { 2223 int length; 2224 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 2225 2226 /* Validate arguments and convert to int */ 2227 if (nArgs != 1) 2228 return wrongNArgsErr(errMsg); 2229 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) 2230 return False; 2231 length = strlen(string); 2232 2233 /* Allocate a new string and copy an lowercased version of the string it */ 2234 result->tag = STRING_TAG; 2235 AllocNString(&result->val.str, length + 1); 2236 DownCaseString(result->val.str.rep, string); 2237 return True; 2238 } 2239 2240 static int stringToClipboardMS(WindowInfo *window, DataValue *argList, int nArgs, 2241 DataValue *result, char **errMsg) 2242 { 2243 long itemID = 0; 2244 XmString s; 2245 int stat; 2246 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 2247 2248 /* Get the string argument */ 2249 if (nArgs != 1) 2250 return wrongNArgsErr(errMsg); 2251 if (!readStringArg(argList[0], &string, stringStorage, errMsg)) 2252 return False; 2253 2254 /* Use the XmClipboard routines to copy the text to the clipboard. 2255 If errors occur, just give up. */ 2256 result->tag = NO_TAG; 2257 stat = SpinClipboardStartCopy(TheDisplay, XtWindow(window->textArea), 2258 s=XmStringCreateSimple("NEdit"), XtLastTimestampProcessed(TheDisplay), 2259 window->textArea, NULL, &itemID); 2260 XmStringFree(s); 2261 if (stat != ClipboardSuccess) 2262 return True; 2263 char *format = "UTF8_STRING"; 2264 for(int i=0;i<2;i++) { 2265 (void)SpinClipboardCopy(TheDisplay, XtWindow(window->textArea), itemID, format, 2266 string, strlen(string), 0, NULL); 2267 format = "STRING"; 2268 } 2269 SpinClipboardEndCopy(TheDisplay, XtWindow(window->textArea), itemID); 2270 return True; 2271 } 2272 2273 static int clipboardToStringMS(WindowInfo *window, DataValue *argList, int nArgs, 2274 DataValue *result, char **errMsg) 2275 { 2276 unsigned long length, retLength; 2277 long id = 0; 2278 2279 /* Should have no arguments */ 2280 if (nArgs != 0) 2281 return wrongNArgsErr(errMsg); 2282 2283 // Ask if there's a string in the clipboard, and get its length 2284 // try UTF8_STRING first, if it isn't available, try STRING 2285 char *format_name = "UTF8_STRING"; 2286 int res = ClipboardNoData; 2287 for(int i=0;i<2;i++) { 2288 res = SpinClipboardInquireLength( 2289 TheDisplay, 2290 XtWindow(window->shell), 2291 format_name, 2292 &length); 2293 if(res == ClipboardSuccess) { 2294 break; 2295 } 2296 format_name = "STRING"; 2297 } 2298 2299 if (res != ClipboardSuccess) { 2300 result->tag = STRING_TAG; 2301 result->val.str.rep = PERM_ALLOC_STR(""); 2302 result->val.str.len = 0; 2303 /* 2304 * Possibly, the clipboard can remain in a locked state after 2305 * a failure, so we try to remove the lock, just to be sure. 2306 */ 2307 SpinClipboardUnlock(TheDisplay, XtWindow(window->shell)); 2308 return True; 2309 } 2310 2311 /* Allocate a new string to hold the data */ 2312 result->tag = STRING_TAG; 2313 AllocNString(&result->val.str, (int)length + 1); 2314 2315 /* Copy the clipboard contents to the string */ 2316 if (SpinClipboardRetrieve(TheDisplay, XtWindow(window->shell), format_name, 2317 result->val.str.rep, length, &retLength, &id) != ClipboardSuccess) { 2318 retLength = 0; 2319 /* 2320 * Possibly, the clipboard can remain in a locked state after 2321 * a failure, so we try to remove the lock, just to be sure. 2322 */ 2323 SpinClipboardUnlock(TheDisplay, XtWindow(window->shell)); 2324 } 2325 result->val.str.rep[retLength] = '\0'; 2326 result->val.str.len = retLength; 2327 2328 return True; 2329 } 2330 2331 2332 /* 2333 ** Built-in macro subroutine for reading the contents of a text file into 2334 ** a string. On success, returns 1 in $readStatus, and the contents of the 2335 ** file as a string in the subroutine return value. On failure, returns 2336 ** the empty string "" and an 0 $readStatus. 2337 */ 2338 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs, 2339 DataValue *result, char **errMsg) 2340 { 2341 char stringStorage[TYPE_INT_STR_SIZE(int)], *name; 2342 struct stat statbuf; 2343 FILE *fp; 2344 int readLen; 2345 2346 /* Validate arguments and convert to int */ 2347 if (nArgs != 1) 2348 return wrongNArgsErr(errMsg); 2349 if (!readStringArg(argList[0], &name, stringStorage, errMsg)) 2350 return False; 2351 2352 /* Read the whole file into an allocated string */ 2353 if ((fp = fopen(name, "r")) == NULL) 2354 goto errorNoClose; 2355 if (fstat(fileno(fp), &statbuf) != 0) 2356 goto error; 2357 result->tag = STRING_TAG; 2358 AllocNString(&result->val.str, statbuf.st_size+1); 2359 readLen = fread(result->val.str.rep, sizeof(char), statbuf.st_size+1, fp); 2360 if (ferror(fp)) 2361 goto error; 2362 if(!feof(fp)){ 2363 /* Couldn't trust file size. Use slower but more general method */ 2364 int chunkSize = 1024; 2365 char *buffer; 2366 2367 buffer = (char*)NEditMalloc(readLen * sizeof(char)); 2368 memcpy(buffer, result->val.str.rep, readLen * sizeof(char)); 2369 while (!feof(fp)){ 2370 buffer = NEditRealloc(buffer, (readLen+chunkSize)*sizeof(char)); 2371 readLen += fread(&buffer[readLen], sizeof(char), chunkSize, fp); 2372 if (ferror(fp)){ 2373 NEditFree(buffer); 2374 goto error; 2375 } 2376 } 2377 AllocNString(&result->val.str, readLen + 1); 2378 memcpy(result->val.str.rep, buffer, readLen * sizeof(char)); 2379 NEditFree(buffer); 2380 } 2381 fclose(fp); 2382 2383 /* Return the results */ 2384 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG; 2385 ReturnGlobals[READ_STATUS]->value.val.n = True; 2386 return True; 2387 2388 error: 2389 fclose(fp); 2390 2391 errorNoClose: 2392 ReturnGlobals[READ_STATUS]->value.tag = INT_TAG; 2393 ReturnGlobals[READ_STATUS]->value.val.n = False; 2394 result->tag = STRING_TAG; 2395 result->val.str.rep = PERM_ALLOC_STR(""); 2396 result->val.str.len = 0; 2397 return True; 2398 } 2399 2400 /* 2401 ** Built-in macro subroutines for writing or appending a string (parameter $1) 2402 ** to a file named in parameter $2. Returns 1 on successful write, or 0 if 2403 ** unsuccessful. 2404 */ 2405 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs, 2406 DataValue *result, char **errMsg) 2407 { 2408 return writeOrAppendFile(False, window, argList, nArgs, result, errMsg); 2409 } 2410 2411 static int appendFileMS(WindowInfo *window, DataValue *argList, int nArgs, 2412 DataValue *result, char **errMsg) 2413 { 2414 return writeOrAppendFile(True, window, argList, nArgs, result, errMsg); 2415 } 2416 2417 static int writeOrAppendFile(int append, WindowInfo *window, 2418 DataValue *argList, int nArgs, DataValue *result, char **errMsg) 2419 { 2420 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *name, *string; 2421 FILE *fp; 2422 2423 /* Validate argument */ 2424 if (nArgs != 2) 2425 return wrongNArgsErr(errMsg); 2426 if (!readStringArg(argList[0], &string, stringStorage[1], errMsg)) 2427 return False; 2428 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg)) 2429 return False; 2430 2431 /* open the file */ 2432 if ((fp = fopen(name, append ? "a" : "w")) == NULL) { 2433 result->tag = INT_TAG; 2434 result->val.n = False; 2435 return True; 2436 } 2437 2438 /* write the string to the file */ 2439 fwrite(string, sizeof(char), strlen(string), fp); 2440 if (ferror(fp)) { 2441 fclose(fp); 2442 result->tag = INT_TAG; 2443 result->val.n = False; 2444 return True; 2445 } 2446 fclose(fp); 2447 2448 /* return the status */ 2449 result->tag = INT_TAG; 2450 result->val.n = True; 2451 return True; 2452 } 2453 2454 /* 2455 ** Built-in macro subroutine for searching silently in a window without 2456 ** dialogs, beeps, or changes to the selection. Arguments are: $1: string to 2457 ** search for, $2: starting position. Optional arguments may include the 2458 ** strings: "wrap" to make the search wrap around the beginning or end of the 2459 ** string, "backward" or "forward" to change the search direction ("forward" is 2460 ** the default), "literal", "case" or "regex" to change the search type 2461 ** (default is "literal"). 2462 ** 2463 ** Returns the starting position of the match, or -1 if nothing matched. 2464 ** also returns the ending position of the match in $searchEndPos 2465 */ 2466 static int searchMS(WindowInfo *window, DataValue *argList, int nArgs, 2467 DataValue *result, char **errMsg) 2468 { 2469 DataValue newArgList[9]; 2470 2471 /* Use the search string routine, by adding the buffer contents as 2472 the string argument */ 2473 if (nArgs > 8) 2474 return wrongNArgsErr(errMsg); 2475 2476 /* we remove constness from BufAsString() result since we know 2477 searchStringMS will not modify the result */ 2478 newArgList[0].tag = STRING_TAG; 2479 newArgList[0].val.str.rep = (char *)BufAsString(window->buffer); 2480 newArgList[0].val.str.len = window->buffer->length; 2481 2482 /* copy other arguments to the new argument list */ 2483 memcpy(&newArgList[1], argList, nArgs * sizeof(DataValue)); 2484 2485 return searchStringMS(window, newArgList, nArgs+1, result, errMsg); 2486 } 2487 2488 /* 2489 ** Built-in macro subroutine for searching a string. Arguments are $1: 2490 ** string to search in, $2: string to search for, $3: starting position. 2491 ** Optional arguments may include the strings: "wrap" to make the search 2492 ** wrap around the beginning or end of the string, "backward" or "forward" 2493 ** to change the search direction ("forward" is the default), "literal", 2494 ** "case" or "regex" to change the search type (default is "literal"). 2495 ** 2496 ** Returns the starting position of the match, or -1 if nothing matched. 2497 ** also returns the ending position of the match in $searchEndPos 2498 */ 2499 static int searchStringMS(WindowInfo *window, DataValue *argList, int nArgs, 2500 DataValue *result, char **errMsg) 2501 { 2502 int beginPos, wrap, direction, found = False, foundStart, foundEnd, type; 2503 int skipSearch = False, len; 2504 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *string, *searchStr; 2505 2506 /* Validate arguments and convert to proper types */ 2507 if (nArgs < 3) 2508 return tooFewArgsErr(errMsg); 2509 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg)) 2510 return False; 2511 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg)) 2512 return False; 2513 if (!readIntArg(argList[2], &beginPos, errMsg)) 2514 return False; 2515 if (!readSearchArgs(&argList[3], nArgs-3, &direction, &type, &wrap, errMsg)) 2516 return False; 2517 2518 len = argList[0].val.str.len; 2519 if (beginPos > len) { 2520 if (direction == SEARCH_FORWARD) { 2521 if (wrap) { 2522 beginPos = 0; /* Wrap immediately */ 2523 } else { 2524 found = False; 2525 skipSearch = True; 2526 } 2527 } else { 2528 beginPos = len; 2529 } 2530 } else if (beginPos < 0) { 2531 if (direction == SEARCH_BACKWARD) { 2532 if (wrap) { 2533 beginPos = len; /* Wrap immediately */ 2534 } else { 2535 found = False; 2536 skipSearch = True; 2537 } 2538 } else { 2539 beginPos = 0; 2540 } 2541 } 2542 2543 if (!skipSearch) 2544 found = SearchString(string, searchStr, direction, type, wrap, beginPos, 2545 &foundStart, &foundEnd, NULL, NULL, GetWindowDelimiters(window)); 2546 2547 /* Return the results */ 2548 ReturnGlobals[SEARCH_END]->value.tag = INT_TAG; 2549 ReturnGlobals[SEARCH_END]->value.val.n = found ? foundEnd : 0; 2550 result->tag = INT_TAG; 2551 result->val.n = found ? foundStart : -1; 2552 return True; 2553 } 2554 2555 /* 2556 ** Built-in macro subroutine for replacing all occurences of a search string in 2557 ** a string with a replacement string. Arguments are $1: string to search in, 2558 ** $2: string to search for, $3: replacement string. Also takes an optional 2559 ** search type: one of "literal", "case" or "regex" (default is "literal"), and 2560 ** an optional "copy" argument. 2561 ** 2562 ** Returns a new string with all of the replacements done. If no replacements 2563 ** were performed and "copy" was specified, returns a copy of the original 2564 ** string. Otherwise returns an empty string (""). 2565 */ 2566 static int replaceInStringMS(WindowInfo *window, DataValue *argList, int nArgs, 2567 DataValue *result, char **errMsg) 2568 { 2569 char stringStorage[3][TYPE_INT_STR_SIZE(int)], *string, *searchStr, *replaceStr; 2570 char *argStr, *replacedStr; 2571 int searchType = SEARCH_LITERAL, copyStart, copyEnd; 2572 int replacedLen, replaceEnd, force=False, i; 2573 2574 /* Validate arguments and convert to proper types */ 2575 if (nArgs < 3 || nArgs > 5) 2576 return wrongNArgsErr(errMsg); 2577 if (!readStringArg(argList[0], &string, stringStorage[0], errMsg)) 2578 return False; 2579 if (!readStringArg(argList[1], &searchStr, stringStorage[1], errMsg)) 2580 return False; 2581 if (!readStringArg(argList[2], &replaceStr, stringStorage[2], errMsg)) 2582 return False; 2583 for (i = 3; i < nArgs; i++) { 2584 /* Read the optional search type and force arguments */ 2585 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg)) 2586 return False; 2587 if (!StringToSearchType(argStr, &searchType)) { 2588 /* It's not a search type. is it "copy"? */ 2589 if (!strcmp(argStr, "copy")) { 2590 force = True; 2591 } else { 2592 *errMsg = "unrecognized argument to %s"; 2593 return False; 2594 } 2595 } 2596 } 2597 2598 /* Do the replace */ 2599 replacedStr = ReplaceAllInString(string, searchStr, replaceStr, searchType, 2600 &copyStart, &copyEnd, &replacedLen, GetWindowDelimiters(window)); 2601 2602 /* Return the results */ 2603 result->tag = STRING_TAG; 2604 if (replacedStr == NULL) { 2605 if (force) { 2606 /* Just copy the original DataValue */ 2607 if (argList[0].tag == STRING_TAG) { 2608 result->val.str.rep = argList[0].val.str.rep; 2609 result->val.str.len = argList[0].val.str.len; 2610 } 2611 else { 2612 AllocNStringCpy(&result->val.str, string); 2613 } 2614 } 2615 else { 2616 result->val.str.rep = PERM_ALLOC_STR(""); 2617 result->val.str.len = 0; 2618 } 2619 } 2620 else { 2621 size_t remainder = strlen(&string[copyEnd]); 2622 replaceEnd = copyStart + replacedLen; 2623 AllocNString(&result->val.str, replaceEnd + remainder + 1); 2624 strncpy(result->val.str.rep, string, copyStart); 2625 strcpy(&result->val.str.rep[copyStart], replacedStr); 2626 strcpy(&result->val.str.rep[replaceEnd], &string[copyEnd]); 2627 NEditFree(replacedStr); 2628 } 2629 return True; 2630 } 2631 2632 static int readSearchArgs(DataValue *argList, int nArgs, int *searchDirection, 2633 int *searchType, int *wrap, char **errMsg) 2634 { 2635 int i; 2636 char *argStr, stringStorage[TYPE_INT_STR_SIZE(int)]; 2637 2638 *wrap = False; 2639 *searchDirection = SEARCH_FORWARD; 2640 *searchType = SEARCH_LITERAL; 2641 for (i=0; i<nArgs; i++) { 2642 if (!readStringArg(argList[i], &argStr, stringStorage, errMsg)) 2643 return False; 2644 else if (!strcmp(argStr, "wrap")) 2645 *wrap = True; 2646 else if (!strcmp(argStr, "nowrap")) 2647 *wrap = False; 2648 else if (!strcmp(argStr, "backward")) 2649 *searchDirection = SEARCH_BACKWARD; 2650 else if (!strcmp(argStr, "forward")) 2651 *searchDirection = SEARCH_FORWARD; 2652 else if (!StringToSearchType(argStr, searchType)) { 2653 *errMsg = "Unrecognized argument to %s"; 2654 return False; 2655 } 2656 } 2657 return True; 2658 } 2659 2660 static int setCursorPosMS(WindowInfo *window, DataValue *argList, int nArgs, 2661 DataValue *result, char **errMsg) 2662 { 2663 int pos; 2664 2665 /* Get argument and convert to int */ 2666 if (nArgs != 1) 2667 return wrongNArgsErr(errMsg); 2668 if (!readIntArg(argList[0], &pos, errMsg)) 2669 return False; 2670 2671 /* Set the position */ 2672 TextSetCursorPos(window->lastFocus, pos); 2673 result->tag = NO_TAG; 2674 return True; 2675 } 2676 2677 static int selectMS(WindowInfo *window, DataValue *argList, int nArgs, 2678 DataValue *result, char **errMsg) 2679 { 2680 int start, end, startTmp; 2681 2682 /* Get arguments and convert to int */ 2683 if (nArgs != 2) 2684 return wrongNArgsErr(errMsg); 2685 if (!readIntArg(argList[0], &start, errMsg)) 2686 return False; 2687 if (!readIntArg(argList[1], &end, errMsg)) 2688 return False; 2689 2690 /* Verify integrity of arguments */ 2691 if (start > end) { 2692 startTmp = start; 2693 start = end; 2694 end = startTmp; 2695 } 2696 if (start < 0) start = 0; 2697 if (start > window->buffer->length) start = window->buffer->length; 2698 if (end < 0) end = 0; 2699 if (end > window->buffer->length) end = window->buffer->length; 2700 2701 /* Make the selection */ 2702 BufSelect(window->buffer, start, end); 2703 result->tag = NO_TAG; 2704 return True; 2705 } 2706 2707 static int selectRectangleMS(WindowInfo *window, DataValue *argList, int nArgs, 2708 DataValue *result, char **errMsg) 2709 { 2710 int start, end, left, right; 2711 2712 /* Get arguments and convert to int */ 2713 if (nArgs != 4) 2714 return wrongNArgsErr(errMsg); 2715 if (!readIntArg(argList[0], &start, errMsg)) 2716 return False; 2717 if (!readIntArg(argList[1], &end, errMsg)) 2718 return False; 2719 if (!readIntArg(argList[2], &left, errMsg)) 2720 return False; 2721 if (!readIntArg(argList[3], &right, errMsg)) 2722 return False; 2723 2724 /* Make the selection */ 2725 BufRectSelect(window->buffer, start, end, left, right); 2726 result->tag = NO_TAG; 2727 return True; 2728 } 2729 2730 /* 2731 ** Macro subroutine to ring the bell 2732 */ 2733 static int beepMS(WindowInfo *window, DataValue *argList, int nArgs, 2734 DataValue *result, char **errMsg) 2735 { 2736 if (nArgs != 0) 2737 return wrongNArgsErr(errMsg); 2738 XBell(XtDisplay(window->shell), 0); 2739 result->tag = NO_TAG; 2740 return True; 2741 } 2742 2743 static int tPrintMS(WindowInfo *window, DataValue *argList, int nArgs, 2744 DataValue *result, char **errMsg) 2745 { 2746 char stringStorage[TYPE_INT_STR_SIZE(int)], *string; 2747 int i; 2748 2749 if (nArgs == 0) 2750 return tooFewArgsErr(errMsg); 2751 for (i=0; i<nArgs; i++) { 2752 if (!readStringArg(argList[i], &string, stringStorage, errMsg)) 2753 return False; 2754 printf("%s%s", string, i==nArgs-1 ? "" : " "); 2755 } 2756 fflush( stdout ); 2757 result->tag = NO_TAG; 2758 return True; 2759 } 2760 2761 /* 2762 ** Built-in macro subroutine for getting the value of an environment variable 2763 */ 2764 static int getenvMS(WindowInfo *window, DataValue *argList, int nArgs, 2765 DataValue *result, char **errMsg) 2766 { 2767 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 2768 char *name; 2769 char *value; 2770 2771 /* Get name of variable to get */ 2772 if (nArgs != 1) 2773 return wrongNArgsErr(errMsg); 2774 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) { 2775 *errMsg = "argument to %s must be a string"; 2776 return False; 2777 } 2778 value = getenv(name); 2779 if (value == NULL) 2780 value = ""; 2781 2782 /* Return the text as an allocated string */ 2783 result->tag = STRING_TAG; 2784 AllocNStringCpy(&result->val.str, value); 2785 return True; 2786 } 2787 2788 static int shellCmdMS(WindowInfo *window, DataValue *argList, int nArgs, 2789 DataValue *result, char **errMsg) 2790 { 2791 char stringStorage[2][TYPE_INT_STR_SIZE(int)], *cmdString, *inputString; 2792 2793 if (nArgs != 2) 2794 return wrongNArgsErr(errMsg); 2795 if (!readStringArg(argList[0], &cmdString, stringStorage[0], errMsg)) 2796 return False; 2797 if (!readStringArg(argList[1], &inputString, stringStorage[1], errMsg)) 2798 return False; 2799 2800 /* Shell command execution requires that the macro be suspended, so 2801 this subroutine can't be run if macro execution can't be interrupted */ 2802 if (MacroRunWindow()->macroCmdData == NULL) { 2803 *errMsg = "%s can''t be called from non-suspendable context"; 2804 return False; 2805 } 2806 2807 ShellCmdToMacroString(window, cmdString, inputString); 2808 result->tag = INT_TAG; 2809 result->val.n = 0; 2810 return True; 2811 } 2812 2813 /* 2814 ** Method used by ShellCmdToMacroString (called by shellCmdMS), for returning 2815 ** macro string and exit status after the execution of a shell command is 2816 ** complete. (Sorry about the poor modularity here, it's just not worth 2817 ** teaching other modules about macro return globals, since other than this, 2818 ** they're not used outside of macro.c) 2819 */ 2820 void ReturnShellCommandOutput(WindowInfo *window, const char *outText, int status) 2821 { 2822 DataValue retVal; 2823 macroCmdInfo *cmdData = window->macroCmdData; 2824 2825 if (cmdData == NULL) 2826 return; 2827 retVal.tag = STRING_TAG; 2828 AllocNStringCpy(&retVal.val.str, outText); 2829 ModifyReturnedValue(cmdData->context, retVal); 2830 ReturnGlobals[SHELL_CMD_STATUS]->value.tag = INT_TAG; 2831 ReturnGlobals[SHELL_CMD_STATUS]->value.val.n = status; 2832 } 2833 2834 static int dialogMS(WindowInfo *window, DataValue *argList, int nArgs, 2835 DataValue *result, char **errMsg) 2836 { 2837 macroCmdInfo *cmdData; 2838 char stringStorage[TYPE_INT_STR_SIZE(int)]; 2839 char btnStorage[TYPE_INT_STR_SIZE(int)]; 2840 char *btnLabel; 2841 char *message; 2842 Arg al[20]; 2843 int ac; 2844 Widget dialog, btn; 2845 int i, nBtns; 2846 XmString s1, s2; 2847 2848 /* Ignore the focused window passed as the function argument and put 2849 the dialog up over the window which is executing the macro */ 2850 window = MacroRunWindow(); 2851 cmdData = window->macroCmdData; 2852 2853 /* Dialogs require macro to be suspended and interleaved with other macros. 2854 This subroutine can't be run if macro execution can't be interrupted */ 2855 if (!cmdData) { 2856 *errMsg = "%s can''t be called from non-suspendable context"; 2857 return False; 2858 } 2859 2860 /* Read and check the arguments. The first being the dialog message, 2861 and the rest being the button labels */ 2862 if (nArgs == 0) { 2863 *errMsg = "%s subroutine called with no arguments"; 2864 return False; 2865 } 2866 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) { 2867 return False; 2868 } 2869 2870 /* check that all button labels can be read */ 2871 for (i=1; i<nArgs; i++) { 2872 if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg)) { 2873 return False; 2874 } 2875 } 2876 2877 /* pick up the first button */ 2878 if (nArgs == 1) { 2879 btnLabel = "OK"; 2880 nBtns = 1; 2881 } 2882 else { 2883 nBtns = nArgs - 1; 2884 argList++; 2885 readStringArg(argList[0], &btnLabel, btnStorage, errMsg); 2886 } 2887 2888 /* Create the message box dialog widget and its dialog shell parent */ 2889 ac = 0; 2890 XtSetArg(al[ac], XmNtitle, " "); ac++; 2891 XtSetArg(al[ac], XmNmessageString, s1=MKSTRING(message)); ac++; 2892 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++; 2893 dialog = CreateMessageDialog(window->shell, "macroDialog", al, ac); 2894 if (1 == nArgs) 2895 { 2896 /* Only set margin width for the default OK button */ 2897 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON), 2898 XmNmarginWidth, BUTTON_WIDTH_MARGIN, 2899 NULL); 2900 } 2901 2902 XmStringFree(s1); 2903 XmStringFree(s2); 2904 AddMotifCloseCallback(XtParent(dialog), dialogCloseCB, window); 2905 XtAddCallback(dialog, XmNokCallback, dialogBtnCB, window); 2906 XtVaSetValues(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON), 2907 XmNuserData, (XtPointer)1, NULL); 2908 cmdData->dialog = dialog; 2909 2910 /* Unmanage default buttons, except for "OK" */ 2911 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON)); 2912 XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); 2913 2914 /* Make callback for the unmanaged cancel button (which can 2915 still get executed via the esc key) activate close box action */ 2916 XtAddCallback(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON), 2917 XmNactivateCallback, dialogCloseCB, window); 2918 2919 /* Add user specified buttons (1st is already done) */ 2920 for (i=1; i<nBtns; i++) { 2921 readStringArg(argList[i], &btnLabel, btnStorage, errMsg); 2922 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog, 2923 XmNlabelString, s1=XmStringCreateSimple(btnLabel), 2924 XmNuserData, (XtPointer)(intptr_t)(i+1), NULL); 2925 XtAddCallback(btn, XmNactivateCallback, dialogBtnCB, window); 2926 XmStringFree(s1); 2927 } 2928 2929 #ifdef LESSTIF_VERSION 2930 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle 2931 the escape key for closing the dialog (probably because the 2932 cancel button is not managed). */ 2933 XtAddEventHandler(dialog, KeyPressMask, False, dialogEscCB, 2934 (XtPointer)window); 2935 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0, 2936 True, GrabModeAsync, GrabModeAsync); 2937 #endif /* LESSTIF_VERSION */ 2938 2939 /* Put up the dialog */ 2940 ManageDialogCenteredOnPointer(dialog); 2941 2942 /* Stop macro execution until the dialog is complete */ 2943 PreemptMacro(); 2944 2945 /* Return placeholder result. Value will be changed by button callback */ 2946 result->tag = INT_TAG; 2947 result->val.n = 0; 2948 return True; 2949 } 2950 2951 static void dialogBtnCB(Widget w, XtPointer clientData, XtPointer callData) 2952 { 2953 WindowInfo *window = (WindowInfo *)clientData; 2954 macroCmdInfo *cmdData = window->macroCmdData; 2955 XtPointer userData; 2956 DataValue retVal; 2957 2958 /* Return the index of the button which was pressed (stored in the userData 2959 field of the button widget). The 1st button, being a gadget, is not 2960 returned in w. */ 2961 if (cmdData == NULL) 2962 return; /* shouldn't happen */ 2963 if (XtClass(w) == xmPushButtonWidgetClass) { 2964 XtVaGetValues(w, XmNuserData, &userData, NULL); 2965 retVal.val.n = (int)(intptr_t)userData; 2966 } else 2967 retVal.val.n = 1; 2968 retVal.tag = INT_TAG; 2969 ModifyReturnedValue(cmdData->context, retVal); 2970 2971 /* Pop down the dialog */ 2972 XtDestroyWidget(XtParent(cmdData->dialog)); 2973 cmdData->dialog = NULL; 2974 2975 /* Continue preempted macro execution */ 2976 ResumeMacroExecution(window); 2977 } 2978 2979 static void dialogCloseCB(Widget w, XtPointer clientData, XtPointer callData) 2980 { 2981 WindowInfo *window = (WindowInfo *)clientData; 2982 macroCmdInfo *cmdData = window->macroCmdData; 2983 DataValue retVal; 2984 2985 /* Return 0 to show that the dialog was closed via the window close box */ 2986 retVal.val.n = 0; 2987 retVal.tag = INT_TAG; 2988 ModifyReturnedValue(cmdData->context, retVal); 2989 2990 /* Pop down the dialog */ 2991 XtDestroyWidget(XtParent(cmdData->dialog)); 2992 cmdData->dialog = NULL; 2993 2994 /* Continue preempted macro execution */ 2995 ResumeMacroExecution(window); 2996 } 2997 2998 #ifdef LESSTIF_VERSION 2999 static void dialogEscCB(Widget w, XtPointer clientData, XEvent *event, 3000 Boolean *cont) 3001 { 3002 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape)) 3003 return; 3004 if (clientData != NULL) { 3005 dialogCloseCB(w, (WindowInfo *)clientData, NULL); 3006 } 3007 *cont = False; 3008 } 3009 #endif /* LESSTIF_VERSION */ 3010 3011 static int stringDialogMS(WindowInfo *window, DataValue *argList, int nArgs, 3012 DataValue *result, char **errMsg) 3013 { 3014 macroCmdInfo *cmdData; 3015 char stringStorage[TYPE_INT_STR_SIZE(int)]; 3016 char btnStorage[TYPE_INT_STR_SIZE(int)]; 3017 char *btnLabel; 3018 char *message; 3019 Widget dialog, btn; 3020 int i, nBtns; 3021 XmString s1, s2; 3022 Arg al[20]; 3023 int ac; 3024 3025 /* Ignore the focused window passed as the function argument and put 3026 the dialog up over the window which is executing the macro */ 3027 window = MacroRunWindow(); 3028 cmdData = window->macroCmdData; 3029 3030 /* Dialogs require macro to be suspended and interleaved with other macros. 3031 This subroutine can't be run if macro execution can't be interrupted */ 3032 if (!cmdData) { 3033 *errMsg = "%s can''t be called from non-suspendable context"; 3034 return False; 3035 } 3036 3037 /* Read and check the arguments. The first being the dialog message, 3038 and the rest being the button labels */ 3039 if (nArgs == 0) { 3040 *errMsg = "%s subroutine called with no arguments"; 3041 return False; 3042 } 3043 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) { 3044 return False; 3045 } 3046 /* check that all button labels can be read */ 3047 for (i=1; i<nArgs; i++) { 3048 if (!readStringArg(argList[i], &btnLabel, stringStorage, errMsg)) { 3049 return False; 3050 } 3051 } 3052 if (nArgs == 1) { 3053 btnLabel = "OK"; 3054 nBtns = 1; 3055 } 3056 else { 3057 nBtns = nArgs - 1; 3058 argList++; 3059 readStringArg(argList[0], &btnLabel, btnStorage, errMsg); 3060 } 3061 3062 /* Create the selection box dialog widget and its dialog shell parent */ 3063 ac = 0; 3064 XtSetArg(al[ac], XmNtitle, " "); ac++; 3065 XtSetArg(al[ac], XmNselectionLabelString, s1=MKSTRING(message)); ac++; 3066 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++; 3067 dialog = CreatePromptDialog(window->shell, "macroStringDialog", al, ac); 3068 if (1 == nArgs) 3069 { 3070 /* Only set margin width for the default OK button */ 3071 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), 3072 XmNmarginWidth, BUTTON_WIDTH_MARGIN, 3073 NULL); 3074 } 3075 3076 XmStringFree(s1); 3077 XmStringFree(s2); 3078 AddMotifCloseCallback(XtParent(dialog), stringDialogCloseCB, window); 3079 XtAddCallback(dialog, XmNokCallback, stringDialogBtnCB, window); 3080 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), 3081 XmNuserData, (XtPointer)1, NULL); 3082 cmdData->dialog = dialog; 3083 3084 /* Unmanage unneded widgets */ 3085 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON)); 3086 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); 3087 3088 /* Make callback for the unmanaged cancel button (which can 3089 still get executed via the esc key) activate close box action */ 3090 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON), 3091 XmNactivateCallback, stringDialogCloseCB, window); 3092 3093 /* Add user specified buttons (1st is already done). Selection box 3094 requires a place-holder widget to be added before buttons can be 3095 added, that's what the separator below is for */ 3096 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL); 3097 for (i=1; i<nBtns; i++) { 3098 readStringArg(argList[i], &btnLabel, btnStorage, errMsg); 3099 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog, 3100 XmNlabelString, s1=XmStringCreateSimple(btnLabel), 3101 XmNuserData, (XtPointer)(intptr_t)(i+1), NULL); 3102 XtAddCallback(btn, XmNactivateCallback, stringDialogBtnCB, window); 3103 XmStringFree(s1); 3104 } 3105 3106 #ifdef LESSTIF_VERSION 3107 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle 3108 the escape key for closing the dialog (probably because the 3109 cancel button is not managed). */ 3110 XtAddEventHandler(dialog, KeyPressMask, False, stringDialogEscCB, 3111 (XtPointer)window); 3112 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0, 3113 True, GrabModeAsync, GrabModeAsync); 3114 #endif /* LESSTIF_VERSION */ 3115 3116 /* Put up the dialog */ 3117 ManageDialogCenteredOnPointer(dialog); 3118 3119 /* Stop macro execution until the dialog is complete */ 3120 PreemptMacro(); 3121 3122 /* Return placeholder result. Value will be changed by button callback */ 3123 result->tag = INT_TAG; 3124 result->val.n = 0; 3125 return True; 3126 } 3127 3128 static void stringDialogBtnCB(Widget w, XtPointer clientData, 3129 XtPointer callData) 3130 { 3131 WindowInfo *window = (WindowInfo *)clientData; 3132 macroCmdInfo *cmdData = window->macroCmdData; 3133 XtPointer userData; 3134 DataValue retVal; 3135 char *text; 3136 int btnNum; 3137 3138 /* shouldn't happen, but would crash if it did */ 3139 if (cmdData == NULL) 3140 return; 3141 3142 /* Return the string entered in the selection text area */ 3143 text = XmTextGetString(XmSelectionBoxGetChild(cmdData->dialog, 3144 XmDIALOG_TEXT)); 3145 retVal.tag = STRING_TAG; 3146 AllocNStringCpy(&retVal.val.str, text); 3147 NEditFree(text); 3148 ModifyReturnedValue(cmdData->context, retVal); 3149 3150 /* Find the index of the button which was pressed (stored in the userData 3151 field of the button widget). The 1st button, being a gadget, is not 3152 returned in w. */ 3153 if (XtClass(w) == xmPushButtonWidgetClass) { 3154 XtVaGetValues(w, XmNuserData, &userData, NULL); 3155 btnNum = (int)(intptr_t)userData; 3156 } else 3157 btnNum = 1; 3158 3159 /* Return the button number in the global variable $string_dialog_button */ 3160 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG; 3161 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = btnNum; 3162 3163 /* Pop down the dialog */ 3164 XtDestroyWidget(XtParent(cmdData->dialog)); 3165 cmdData->dialog = NULL; 3166 3167 /* Continue preempted macro execution */ 3168 ResumeMacroExecution(window); 3169 } 3170 3171 static void stringDialogCloseCB(Widget w, XtPointer clientData, 3172 XtPointer callData) 3173 { 3174 WindowInfo *window = (WindowInfo *)clientData; 3175 macroCmdInfo *cmdData = window->macroCmdData; 3176 DataValue retVal; 3177 3178 /* shouldn't happen, but would crash if it did */ 3179 if (cmdData == NULL) 3180 return; 3181 3182 /* Return an empty string */ 3183 retVal.tag = STRING_TAG; 3184 retVal.val.str.rep = PERM_ALLOC_STR(""); 3185 retVal.val.str.len = 0; 3186 ModifyReturnedValue(cmdData->context, retVal); 3187 3188 /* Return button number 0 in the global variable $string_dialog_button */ 3189 ReturnGlobals[STRING_DIALOG_BUTTON]->value.tag = INT_TAG; 3190 ReturnGlobals[STRING_DIALOG_BUTTON]->value.val.n = 0; 3191 3192 /* Pop down the dialog */ 3193 XtDestroyWidget(XtParent(cmdData->dialog)); 3194 cmdData->dialog = NULL; 3195 3196 /* Continue preempted macro execution */ 3197 ResumeMacroExecution(window); 3198 } 3199 3200 #ifdef LESSTIF_VERSION 3201 static void stringDialogEscCB(Widget w, XtPointer clientData, XEvent *event, 3202 Boolean *cont) 3203 { 3204 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape)) 3205 return; 3206 if (clientData != NULL) { 3207 stringDialogCloseCB(w, (WindowInfo *)clientData, NULL); 3208 } 3209 *cont = False; 3210 } 3211 #endif /* LESSTIF_VERSION */ 3212 3213 /* 3214 ** A subroutine to put up a calltip 3215 ** First arg is either text to be displayed or a key for tip/tag lookup. 3216 ** Optional second arg is the buffer position beneath which to display the 3217 ** upper-left corner of the tip. Default (or -1) puts it under the cursor. 3218 ** Additional optional arguments: 3219 ** "tipText": (default) Indicates first arg is text to be displayed in tip. 3220 ** "tipKey": Indicates first arg is key in calltips database. If key 3221 ** is not found in tip database then the tags database is also 3222 ** searched. 3223 ** "tagKey": Indicates first arg is key in tags database. (Skips 3224 ** search in calltips database.) 3225 ** "center": Horizontally center the calltip at the position 3226 ** "right": Put the right edge of the calltip at the position 3227 ** "center" and "right" cannot both be specified. 3228 ** "above": Place the calltip above the position 3229 ** "strict": Don't move the calltip to keep it on-screen and away 3230 ** from the cursor's line. 3231 ** 3232 ** Returns the new calltip's ID on success, 0 on failure. 3233 ** 3234 ** Does this need to go on IgnoredActions? I don't think so, since 3235 ** showing a calltip may be part of the action you want to learn. 3236 */ 3237 static int calltipMS(WindowInfo *window, DataValue *argList, int nArgs, 3238 DataValue *result, char **errMsg) 3239 { 3240 char stringStorage[TYPE_INT_STR_SIZE(int)], *tipText, *txtArg; 3241 Boolean anchored = False, lookup = True; 3242 int mode = -1, i; 3243 int anchorPos, hAlign = TIP_LEFT, vAlign = TIP_BELOW, 3244 alignMode = TIP_SLOPPY; 3245 3246 /* Read and check the string */ 3247 if (nArgs < 1) { 3248 *errMsg = "%s subroutine called with too few arguments"; 3249 return False; 3250 } 3251 if (nArgs > 6) { 3252 *errMsg = "%s subroutine called with too many arguments"; 3253 return False; 3254 } 3255 3256 /* Read the tip text or key */ 3257 if (!readStringArg(argList[0], &tipText, stringStorage, errMsg)) 3258 return False; 3259 3260 /* Read the anchor position (-1 for unanchored) */ 3261 if (nArgs > 1) { 3262 if (!readIntArg(argList[1], &anchorPos, errMsg)) 3263 return False; 3264 } else { 3265 anchorPos = -1; 3266 } 3267 if (anchorPos >= 0) anchored = True; 3268 3269 /* Any further args are directives for relative positioning */ 3270 for (i = 2; i < nArgs; ++i) { 3271 if (!readStringArg(argList[i], &txtArg, stringStorage, errMsg)){ 3272 return False; 3273 } 3274 switch( txtArg[0] ) { 3275 case 'c': 3276 if (strcmp(txtArg, "center")) 3277 goto bad_arg; 3278 hAlign = TIP_CENTER; 3279 break; 3280 case 'r': 3281 if (strcmp(txtArg, "right")) 3282 goto bad_arg; 3283 hAlign = TIP_RIGHT; 3284 break; 3285 case 'a': 3286 if (strcmp(txtArg, "above")) 3287 goto bad_arg; 3288 vAlign = TIP_ABOVE; 3289 break; 3290 case 's': 3291 if (strcmp(txtArg, "strict")) 3292 goto bad_arg; 3293 alignMode = TIP_STRICT; 3294 break; 3295 case 't': 3296 if (!strcmp(txtArg, "tipText")) 3297 mode = -1; 3298 else if (!strcmp(txtArg, "tipKey")) 3299 mode = TIP; 3300 else if (!strcmp(txtArg, "tagKey")) 3301 mode = TIP_FROM_TAG; 3302 else 3303 goto bad_arg; 3304 break; 3305 default: 3306 goto bad_arg; 3307 } 3308 } 3309 3310 result->tag = INT_TAG; 3311 if (mode < 0) lookup = False; 3312 /* Look up (maybe) a calltip and display it */ 3313 result->val.n = ShowTipString( window, tipText, anchored, anchorPos, lookup, 3314 mode, hAlign, vAlign, alignMode ); 3315 3316 return True; 3317 3318 bad_arg: 3319 /* This is how the (more informative) global var. version would work, 3320 assuming there was a global buffer called msg. */ 3321 /* sprintf(msg, "unrecognized argument to %%s: \"%s\"", txtArg); 3322 *errMsg = msg; */ 3323 *errMsg = "unrecognized argument to %s"; 3324 return False; 3325 } 3326 3327 /* 3328 ** A subroutine to kill the current calltip 3329 */ 3330 static int killCalltipMS(WindowInfo *window, DataValue *argList, int nArgs, 3331 DataValue *result, char **errMsg) 3332 { 3333 int calltipID = 0; 3334 3335 if (nArgs > 1) { 3336 *errMsg = "%s subroutine called with too many arguments"; 3337 return False; 3338 } 3339 if (nArgs > 0) { 3340 if (!readIntArg(argList[0], &calltipID, errMsg)) 3341 return False; 3342 } 3343 3344 KillCalltip( window, calltipID ); 3345 3346 result->tag = NO_TAG; 3347 return True; 3348 } 3349 3350 /* 3351 * A subroutine to get the ID of the current calltip, or 0 if there is none. 3352 */ 3353 static int calltipIDMV(WindowInfo *window, DataValue *argList, 3354 int nArgs, DataValue *result, char **errMsg) 3355 { 3356 result->tag = INT_TAG; 3357 result->val.n = GetCalltipID(window, 0); 3358 return True; 3359 } 3360 3361 /* 3362 ** filename_dialog([title[, mode[, defaultPath[, filter[, defaultName]]]]]) 3363 ** 3364 ** Presents a FileSelectionDialog to the user prompting for a new file. 3365 ** 3366 ** Options are: 3367 ** title - will be the title of the dialog, defaults to "Choose file". 3368 ** mode - if set to "exist" (default), the "New File Name" TextField 3369 ** of the FSB will be unmanaged. If "new", the TextField will 3370 ** be managed. 3371 ** defaultPath - is the default path to use. Default (or "") will use the 3372 ** active document's directory. 3373 ** filter - the file glob which determines which files to display. 3374 ** Is set to "*" if filter is "" and by default. 3375 ** defaultName - is the default filename that is filled in automatically. 3376 ** 3377 ** Returns "" if the user cancelled the dialog, otherwise returns the path to 3378 ** the file that was selected 3379 ** 3380 ** Note that defaultName doesn't work on all *tifs. :-( 3381 */ 3382 static int filenameDialogMS(WindowInfo* window, DataValue* argList, int nArgs, 3383 DataValue* result, char** errMsg) 3384 { 3385 char stringStorage[5][TYPE_INT_STR_SIZE(int)]; 3386 char* title = "Choose Filename"; 3387 char* mode = "exist"; 3388 char* defaultPath = ""; 3389 char* filter = ""; 3390 char* defaultName = ""; 3391 char* orgDefaultPath; 3392 char* orgFilter; 3393 int gfnResult; 3394 3395 /* Ignore the focused window passed as the function argument and put 3396 the dialog up over the window which is executing the macro */ 3397 window = MacroRunWindow(); 3398 3399 /* Dialogs require macro to be suspended and interleaved with other macros. 3400 This subroutine can't be run if macro execution can't be interrupted */ 3401 if (NULL == window->macroCmdData) { 3402 M_FAILURE("%s can''t be called from non-suspendable context"); 3403 } 3404 3405 /* Get the argument list. */ 3406 if (nArgs > 0 && !readStringArg(argList[0], &title, stringStorage[0], 3407 errMsg)) { 3408 return False; 3409 } 3410 3411 if (nArgs > 1 && !readStringArg(argList[1], &mode, stringStorage[1], 3412 errMsg)) { 3413 return False; 3414 } 3415 if (0 != strcmp(mode, "exist") && 0 != strcmp(mode, "new")) { 3416 M_FAILURE("Invalid value for mode in %s"); 3417 } 3418 3419 if (nArgs > 2 && !readStringArg(argList[2], &defaultPath, stringStorage[2], 3420 errMsg)) { 3421 return False; 3422 } 3423 3424 if (nArgs > 3 && !readStringArg(argList[3], &filter, stringStorage[3], 3425 errMsg)) { 3426 return False; 3427 } 3428 3429 if (nArgs > 4 && !readStringArg(argList[4], &defaultName, stringStorage[4], 3430 errMsg)) { 3431 return False; 3432 } 3433 3434 if (nArgs > 5) { 3435 M_FAILURE("%s called with too many arguments. Expects at most 5 arguments."); 3436 } 3437 3438 /* Set default directory (saving original for later) */ 3439 orgDefaultPath = GetFileDialogDefaultDirectory(); 3440 if ('\0' != defaultPath[0]) { 3441 SetFileDialogDefaultDirectory(defaultPath); 3442 } else { 3443 SetFileDialogDefaultDirectory(window->path); 3444 } 3445 3446 /* Set filter (saving original for later) */ 3447 orgFilter = FileDialogGetFilter(); 3448 if ('\0' != filter[0]) { 3449 FileDialogSetFilter(filter); 3450 } 3451 3452 /* Fork to one of the worker methods from util/getfiles.c. 3453 (This should obviously be refactored.) */ 3454 window->macroBlocking = True; 3455 FileSelection getfile = { NULL, NULL }; 3456 if (0 == strcmp(mode, "exist")) { 3457 gfnResult = GetExistingFilename(window->shell, title, &getfile); 3458 } else { 3459 gfnResult = GetNewFilename(window->shell, title, &getfile, defaultName); 3460 } /* Invalid values are weeded out above. */ 3461 window->macroBlocking = False; 3462 3463 /* Reset original values and free temps */ 3464 SetFileDialogDefaultDirectory(orgDefaultPath); 3465 FileDialogSetFilter(orgFilter); 3466 NEditFree(orgDefaultPath); 3467 NEditFree(orgFilter); 3468 3469 result->tag = STRING_TAG; 3470 if (GFN_OK == gfnResult) { 3471 /* Got a string, copy it to the result */ 3472 if (!AllocNStringNCpy(&result->val.str, getfile.path, MAXPATHLEN)) { 3473 M_FAILURE("failed to allocate return value: %s"); 3474 } 3475 NEditFree(getfile.path); 3476 } else { 3477 /* User cancelled. Return "" */ 3478 result->val.str.rep = PERM_ALLOC_STR(""); 3479 result->val.str.len = 0; 3480 } 3481 3482 return True; 3483 } 3484 3485 /* T Balinski */ 3486 static int listDialogMS(WindowInfo *window, DataValue *argList, int nArgs, 3487 DataValue *result, char **errMsg) 3488 { 3489 macroCmdInfo *cmdData; 3490 char stringStorage[TYPE_INT_STR_SIZE(int)]; 3491 char textStorage[TYPE_INT_STR_SIZE(int)]; 3492 char btnStorage[TYPE_INT_STR_SIZE(int)]; 3493 char *btnLabel; 3494 char *message, *text; 3495 Widget dialog, btn; 3496 int i, nBtns; 3497 XmString s1, s2; 3498 long nlines = 0; 3499 char *p, *old_p, **text_lines, *tmp; 3500 int tmp_len; 3501 int n, is_last; 3502 XmString *test_strings; 3503 int tabDist; 3504 Arg al[20]; 3505 int ac; 3506 3507 3508 /* Ignore the focused window passed as the function argument and put 3509 the dialog up over the window which is executing the macro */ 3510 window = MacroRunWindow(); 3511 cmdData = window->macroCmdData; 3512 3513 /* Dialogs require macro to be suspended and interleaved with other macros. 3514 This subroutine can't be run if macro execution can't be interrupted */ 3515 if (!cmdData) { 3516 *errMsg = "%s can''t be called from non-suspendable context"; 3517 return False; 3518 } 3519 3520 /* Read and check the arguments. The first being the dialog message, 3521 and the rest being the button labels */ 3522 if (nArgs < 2) { 3523 *errMsg = "%s subroutine called with no message, string or arguments"; 3524 return False; 3525 } 3526 3527 if (!readStringArg(argList[0], &message, stringStorage, errMsg)) 3528 return False; 3529 3530 if (!readStringArg(argList[1], &text, textStorage, errMsg)) 3531 return False; 3532 3533 if (!text || text[0] == '\0') { 3534 *errMsg = "%s subroutine called with empty list data"; 3535 return False; 3536 } 3537 3538 /* check that all button labels can be read */ 3539 for (i=2; i<nArgs; i++) 3540 if (!readStringArg(argList[i], &btnLabel, btnStorage, errMsg)) 3541 return False; 3542 3543 /* pick up the first button */ 3544 if (nArgs == 2) { 3545 btnLabel = "OK"; 3546 nBtns = 1; 3547 } 3548 else { 3549 nBtns = nArgs - 2; 3550 argList += 2; 3551 readStringArg(argList[0], &btnLabel, btnStorage, errMsg); 3552 } 3553 3554 /* count the lines in the text - add one for unterminated last line */ 3555 nlines = 1; 3556 for (p = text; *p; p++) 3557 if (*p == '\n') 3558 nlines++; 3559 3560 /* now set up arrays of pointers to lines */ 3561 /* test_strings to hold the display strings (tab expanded) */ 3562 /* text_lines to hold the original text lines (without the '\n's) */ 3563 test_strings = (XmString *) NEditMalloc(sizeof(XmString) * nlines); 3564 text_lines = (char **)NEditMalloc(sizeof(char *) * (nlines + 1)); 3565 for (n = 0; n < nlines; n++) { 3566 test_strings[n] = (XmString)0; 3567 text_lines[n] = (char *)0; 3568 } 3569 text_lines[n] = (char *)0; /* make sure this is a null-terminated table */ 3570 3571 /* pick up the tabDist value */ 3572 tabDist = window->buffer->tabDist; 3573 3574 /* load the table */ 3575 n = 0; 3576 is_last = 0; 3577 p = old_p = text; 3578 tmp_len = 0; /* current allocated size of temporary buffer tmp */ 3579 tmp = (char*)NEditMalloc(1); /* temporary buffer into which to expand tabs */ 3580 do { 3581 is_last = (*p == '\0'); 3582 if (*p == '\n' || is_last) { 3583 *p = '\0'; 3584 if (strlen(old_p) > 0) { /* only include non-empty lines */ 3585 char *s, *t; 3586 int l; 3587 3588 /* save the actual text line in text_lines[n] */ 3589 text_lines[n] = (char *)NEditMalloc(strlen(old_p) + 1); 3590 strcpy(text_lines[n], old_p); 3591 3592 /* work out the tabs expanded length */ 3593 for (s = old_p, l = 0; *s; s++) 3594 l += (*s == '\t') ? tabDist - (l % tabDist) : 1; 3595 3596 /* verify tmp is big enough then tab-expand old_p into tmp */ 3597 if (l > tmp_len) 3598 tmp = (char*)NEditRealloc(tmp, (tmp_len = l) + 1); 3599 for (s = old_p, t = tmp, l = 0; *s; s++) { 3600 if (*s == '\t') { 3601 for (i = tabDist - (l % tabDist); i--; l++) 3602 *t++ = ' '; 3603 } 3604 else { 3605 *t++ = *s; 3606 l++; 3607 } 3608 } 3609 *t = '\0'; 3610 /* that's it: tmp is the tab-expanded version of old_p */ 3611 test_strings[n] = MKSTRING(tmp); 3612 n++; 3613 } 3614 old_p = p + 1; 3615 if (!is_last) 3616 *p = '\n'; /* put back our newline */ 3617 } 3618 p++; 3619 } while (!is_last); 3620 3621 NEditFree(tmp); /* don't need this anymore */ 3622 nlines = n; 3623 if (nlines == 0) { 3624 test_strings[0] = MKSTRING(""); 3625 nlines = 1; 3626 } 3627 3628 /* Create the selection box dialog widget and its dialog shell parent */ 3629 ac = 0; 3630 XtSetArg(al[ac], XmNtitle, " "); ac++; 3631 XtSetArg(al[ac], XmNlistLabelString, s1=MKSTRING(message)); ac++; 3632 XtSetArg(al[ac], XmNlistItems, test_strings); ac++; 3633 XtSetArg(al[ac], XmNlistItemCount, nlines); ac++; 3634 XtSetArg(al[ac], XmNlistVisibleItemCount, (nlines > 10) ? 10 : nlines); ac++; 3635 XtSetArg(al[ac], XmNokLabelString, s2=XmStringCreateSimple(btnLabel)); ac++; 3636 dialog = CreateSelectionDialog(window->shell, "macroListDialog", al, ac); 3637 if (2 == nArgs) 3638 { 3639 /* Only set margin width for the default OK button */ 3640 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), 3641 XmNmarginWidth, BUTTON_WIDTH_MARGIN, 3642 NULL); 3643 } 3644 3645 AddMotifCloseCallback(XtParent(dialog), listDialogCloseCB, window); 3646 XtAddCallback(dialog, XmNokCallback, listDialogBtnCB, window); 3647 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), 3648 XmNuserData, (XtPointer)1, NULL); 3649 XmStringFree(s1); 3650 XmStringFree(s2); 3651 cmdData->dialog = dialog; 3652 3653 /* forget lines stored in list */ 3654 while (n--) 3655 XmStringFree(test_strings[n]); 3656 NEditFree(test_strings); 3657 3658 /* modify the list */ 3659 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST), 3660 XmNselectionPolicy, XmSINGLE_SELECT, 3661 XmNuserData, (XtPointer)text_lines, NULL); 3662 3663 /* Unmanage unneeded widgets */ 3664 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON)); 3665 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON)); 3666 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); 3667 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT)); 3668 XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL)); 3669 3670 /* Make callback for the unmanaged cancel button (which can 3671 still get executed via the esc key) activate close box action */ 3672 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON), 3673 XmNactivateCallback, listDialogCloseCB, window); 3674 3675 /* Add user specified buttons (1st is already done). Selection box 3676 requires a place-holder widget to be added before buttons can be 3677 added, that's what the separator below is for */ 3678 XtVaCreateWidget("x", xmSeparatorWidgetClass, dialog, NULL); 3679 for (i=1; i<nBtns; i++) { 3680 readStringArg(argList[i], &btnLabel, btnStorage, errMsg); 3681 btn = XtVaCreateManagedWidget("mdBtn", xmPushButtonWidgetClass, dialog, 3682 XmNlabelString, s1=XmStringCreateSimple(btnLabel), 3683 XmNuserData, (XtPointer)(intptr_t)(i+1), NULL); 3684 XtAddCallback(btn, XmNactivateCallback, listDialogBtnCB, window); 3685 XmStringFree(s1); 3686 } 3687 3688 #ifdef LESSTIF_VERSION 3689 /* Workaround for Lesstif (e.g. v2.1 r0.93.18) that doesn't handle 3690 the escape key for closing the dialog. */ 3691 XtAddEventHandler(dialog, KeyPressMask, False, listDialogEscCB, 3692 (XtPointer)window); 3693 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0, 3694 True, GrabModeAsync, GrabModeAsync); 3695 #endif /* LESSTIF_VERSION */ 3696 3697 /* Put up the dialog */ 3698 ManageDialogCenteredOnPointer(dialog); 3699 3700 /* Stop macro execution until the dialog is complete */ 3701 PreemptMacro(); 3702 3703 /* Return placeholder result. Value will be changed by button callback */ 3704 result->tag = INT_TAG; 3705 result->val.n = 0; 3706 return True; 3707 } 3708 3709 static void listDialogBtnCB(Widget w, XtPointer clientData, 3710 XtPointer callData) 3711 { 3712 WindowInfo *window = (WindowInfo *)clientData; 3713 macroCmdInfo *cmdData = window->macroCmdData; 3714 XtPointer userData; 3715 DataValue retVal; 3716 char *text; 3717 char **text_lines; 3718 int btnNum; 3719 int n_sel, *seltable, sel_index = 0; 3720 Widget theList; 3721 size_t length; 3722 3723 /* shouldn't happen, but would crash if it did */ 3724 if (cmdData == NULL) 3725 return; 3726 3727 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST); 3728 /* Return the string selected in the selection list area */ 3729 XtVaGetValues(theList, XmNuserData, &text_lines, NULL); 3730 if (!XmListGetSelectedPos(theList, &seltable, &n_sel)) { 3731 n_sel = 0; 3732 } 3733 else { 3734 sel_index = seltable[0] - 1; 3735 NEditFree(seltable); 3736 } 3737 3738 if (!n_sel) { 3739 text = NEditStrdup(""); 3740 length = 0; 3741 } 3742 else { 3743 length = strlen((char *)text_lines[sel_index]); 3744 text = NEditStrdup(text_lines[sel_index]); 3745 } 3746 3747 /* don't need text_lines anymore: free it */ 3748 for (sel_index = 0; text_lines[sel_index]; sel_index++) 3749 NEditFree(text_lines[sel_index]); 3750 NEditFree(text_lines); 3751 3752 retVal.tag = STRING_TAG; 3753 retVal.val.str.rep = text; 3754 retVal.val.str.len = length; 3755 ModifyReturnedValue(cmdData->context, retVal); 3756 3757 /* Find the index of the button which was pressed (stored in the userData 3758 field of the button widget). The 1st button, being a gadget, is not 3759 returned in w. */ 3760 if (XtClass(w) == xmPushButtonWidgetClass) { 3761 XtVaGetValues(w, XmNuserData, &userData, NULL); 3762 btnNum = (int)(intptr_t)userData; 3763 } else 3764 btnNum = 1; 3765 3766 /* Return the button number in the global variable $list_dialog_button */ 3767 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG; 3768 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = btnNum; 3769 3770 /* Pop down the dialog */ 3771 XtDestroyWidget(XtParent(cmdData->dialog)); 3772 cmdData->dialog = NULL; 3773 3774 /* Continue preempted macro execution */ 3775 ResumeMacroExecution(window); 3776 } 3777 3778 static void listDialogCloseCB(Widget w, XtPointer clientData, 3779 XtPointer callData) 3780 { 3781 WindowInfo *window = (WindowInfo *)clientData; 3782 macroCmdInfo *cmdData = window->macroCmdData; 3783 DataValue retVal; 3784 char **text_lines; 3785 int sel_index; 3786 Widget theList; 3787 3788 /* shouldn't happen, but would crash if it did */ 3789 if (cmdData == NULL) 3790 return; 3791 3792 /* don't need text_lines anymore: retrieve it then free it */ 3793 theList = XmSelectionBoxGetChild(cmdData->dialog, XmDIALOG_LIST); 3794 XtVaGetValues(theList, XmNuserData, &text_lines, NULL); 3795 for (sel_index = 0; text_lines[sel_index]; sel_index++) 3796 NEditFree(text_lines[sel_index]); 3797 NEditFree(text_lines); 3798 3799 /* Return an empty string */ 3800 retVal.tag = STRING_TAG; 3801 retVal.val.str.rep = NEditStrdup(""); 3802 retVal.val.str.len = 0; 3803 ModifyReturnedValue(cmdData->context, retVal); 3804 3805 /* Return button number 0 in the global variable $list_dialog_button */ 3806 ReturnGlobals[LIST_DIALOG_BUTTON]->value.tag = INT_TAG; 3807 ReturnGlobals[LIST_DIALOG_BUTTON]->value.val.n = 0; 3808 3809 /* Pop down the dialog */ 3810 XtDestroyWidget(XtParent(cmdData->dialog)); 3811 cmdData->dialog = NULL; 3812 3813 /* Continue preempted macro execution */ 3814 ResumeMacroExecution(window); 3815 } 3816 /* T Balinski End */ 3817 3818 #ifdef LESSTIF_VERSION 3819 static void listDialogEscCB(Widget w, XtPointer clientData, XEvent *event, 3820 Boolean *cont) 3821 { 3822 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape)) 3823 return; 3824 if (clientData != NULL) { 3825 listDialogCloseCB(w, (WindowInfo *)clientData, NULL); 3826 } 3827 *cont = False; 3828 } 3829 #endif /* LESSTIF_VERSION */ 3830 3831 3832 static int stringCompareMS(WindowInfo *window, DataValue *argList, int nArgs, 3833 DataValue *result, char **errMsg) 3834 { 3835 char stringStorage[3][TYPE_INT_STR_SIZE(int)]; 3836 char *leftStr, *rightStr, *argStr; 3837 int considerCase = True; 3838 int i; 3839 int compareResult; 3840 3841 if (nArgs < 2) { 3842 return(wrongNArgsErr(errMsg)); 3843 } 3844 if (!readStringArg(argList[0], &leftStr, stringStorage[0], errMsg)) 3845 return False; 3846 if (!readStringArg(argList[1], &rightStr, stringStorage[1], errMsg)) 3847 return False; 3848 for (i = 2; i < nArgs; ++i) { 3849 if (!readStringArg(argList[i], &argStr, stringStorage[2], errMsg)) 3850 return False; 3851 else if (!strcmp(argStr, "case")) 3852 considerCase = True; 3853 else if (!strcmp(argStr, "nocase")) 3854 considerCase = False; 3855 else { 3856 *errMsg = "Unrecognized argument to %s"; 3857 return False; 3858 } 3859 } 3860 if (considerCase) { 3861 compareResult = strcmp(leftStr, rightStr); 3862 compareResult = (compareResult > 0) ? 1 : ((compareResult < 0) ? -1 : 0); 3863 } 3864 else { 3865 compareResult = strcasecmp(leftStr, rightStr); 3866 } 3867 result->tag = INT_TAG; 3868 result->val.n = compareResult; 3869 return True; 3870 } 3871 3872 /* 3873 ** This function is intended to split strings into an array of substrings 3874 ** Importatnt note: It should always return at least one entry with key 0 3875 ** split("", ",") result[0] = "" 3876 ** split("1,2", ",") result[0] = "1" result[1] = "2" 3877 ** split("1,2,", ",") result[0] = "1" result[1] = "2" result[2] = "" 3878 ** 3879 ** This behavior is specifically important when used to break up 3880 ** array sub-scripts 3881 */ 3882 3883 static int splitMS(WindowInfo *window, DataValue *argList, int nArgs, 3884 DataValue *result, char **errMsg) 3885 { 3886 char stringStorage[3][TYPE_INT_STR_SIZE(int)]; 3887 char *sourceStr, *splitStr, *typeSplitStr; 3888 int searchType, beginPos, foundStart, foundEnd, strLength, lastEnd; 3889 int found, elementEnd, indexNum; 3890 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr; 3891 DataValue element; 3892 int elementLen; 3893 3894 if (nArgs < 2) { 3895 return(wrongNArgsErr(errMsg)); 3896 } 3897 if (!readStringArg(argList[0], &sourceStr, stringStorage[0], errMsg)) { 3898 *errMsg = "first argument must be a string: %s"; 3899 return(False); 3900 } 3901 if (!readStringArg(argList[1], &splitStr, stringStorage[1], errMsg)) { 3902 splitStr = NULL; 3903 } 3904 else { 3905 if (splitStr[0] == 0) { 3906 splitStr = NULL; 3907 } 3908 } 3909 if (splitStr == NULL) { 3910 *errMsg = "second argument must be a non-empty string: %s"; 3911 return(False); 3912 } 3913 if (nArgs > 2 && readStringArg(argList[2], &typeSplitStr, stringStorage[2], errMsg)) { 3914 if (!StringToSearchType(typeSplitStr, &searchType)) { 3915 *errMsg = "unrecognized argument to %s"; 3916 return(False); 3917 } 3918 } 3919 else { 3920 searchType = SEARCH_LITERAL; 3921 } 3922 3923 result->tag = ARRAY_TAG; 3924 result->val.arrayPtr = ArrayNew(); 3925 3926 beginPos = 0; 3927 lastEnd = 0; 3928 indexNum = 0; 3929 strLength = strlen(sourceStr); 3930 found = 1; 3931 while (found && beginPos < strLength) { 3932 sprintf(indexStr, "%d", indexNum); 3933 allocIndexStr = AllocString(strlen(indexStr) + 1); 3934 if (!allocIndexStr) { 3935 *errMsg = "array element failed to allocate key: %s"; 3936 return(False); 3937 } 3938 strcpy(allocIndexStr, indexStr); 3939 found = SearchString(sourceStr, splitStr, SEARCH_FORWARD, searchType, 3940 False, beginPos, &foundStart, &foundEnd, 3941 NULL, NULL, GetWindowDelimiters(window)); 3942 elementEnd = found ? foundStart : strLength; 3943 elementLen = elementEnd - lastEnd; 3944 element.tag = STRING_TAG; 3945 if (!AllocNStringNCpy(&element.val.str, &sourceStr[lastEnd], elementLen)) { 3946 *errMsg = "failed to allocate element value: %s"; 3947 return(False); 3948 } 3949 3950 if (!ArrayInsert(result, allocIndexStr, &element)) { 3951 M_ARRAY_INSERT_FAILURE(); 3952 } 3953 3954 if (found) { 3955 if (foundStart == foundEnd) { 3956 beginPos = foundEnd + 1; /* Avoid endless loop for 0-width match */ 3957 } else { 3958 beginPos = foundEnd; 3959 } 3960 } else { 3961 beginPos = strLength; /* Break the loop */ 3962 } 3963 lastEnd = foundEnd; 3964 ++indexNum; 3965 } 3966 if (found) { 3967 sprintf(indexStr, "%d", indexNum); 3968 allocIndexStr = AllocString(strlen(indexStr) + 1); 3969 if (!allocIndexStr) { 3970 *errMsg = "array element failed to allocate key: %s"; 3971 return(False); 3972 } 3973 strcpy(allocIndexStr, indexStr); 3974 element.tag = STRING_TAG; 3975 if (lastEnd == strLength) { 3976 /* The pattern mathed the end of the string. Add an empty chunk. */ 3977 element.val.str.rep = PERM_ALLOC_STR(""); 3978 element.val.str.len = 0; 3979 3980 if (!ArrayInsert(result, allocIndexStr, &element)) { 3981 M_ARRAY_INSERT_FAILURE(); 3982 } 3983 } else { 3984 /* We skipped the last character to prevent an endless loop. 3985 Add it to the list. */ 3986 elementLen = strLength - lastEnd; 3987 if (!AllocNStringNCpy(&element.val.str, &sourceStr[lastEnd], elementLen)) { 3988 *errMsg = "failed to allocate element value: %s"; 3989 return(False); 3990 } 3991 3992 if (!ArrayInsert(result, allocIndexStr, &element)) { 3993 M_ARRAY_INSERT_FAILURE(); 3994 } 3995 3996 /* If the pattern can match zero-length strings, we may have to 3997 add a final empty chunk. 3998 For instance: split("abc\n", "$", "regex") 3999 -> matches before \n and at end of string 4000 -> expected output: "abc", "\n", "" 4001 The '\n' gets added in the lines above, but we still have to 4002 verify whether the pattern also matches the end of the string, 4003 and add an empty chunk in case it does. */ 4004 found = SearchString(sourceStr, splitStr, SEARCH_FORWARD, 4005 searchType, False, strLength, &foundStart, &foundEnd, 4006 NULL, NULL, GetWindowDelimiters(window)); 4007 if (found) { 4008 ++indexNum; 4009 sprintf(indexStr, "%d", indexNum); 4010 allocIndexStr = AllocString(strlen(indexStr) + 1); 4011 if (!allocIndexStr) { 4012 *errMsg = "array element failed to allocate key: %s"; 4013 return(False); 4014 } 4015 strcpy(allocIndexStr, indexStr); 4016 element.tag = STRING_TAG; 4017 element.val.str.rep = PERM_ALLOC_STR(""); 4018 element.val.str.len = 0; 4019 4020 if (!ArrayInsert(result, allocIndexStr, &element)) { 4021 M_ARRAY_INSERT_FAILURE(); 4022 } 4023 } 4024 } 4025 } 4026 return(True); 4027 } 4028 4029 /* 4030 ** Set the backlighting string resource for the current window. If no parameter 4031 ** is passed or the value "default" is passed, it attempts to set the preference 4032 ** value of the resource. If the empty string is passed, the backlighting string 4033 ** will be cleared, turning off backlighting. 4034 */ 4035 /* DISABLED for 5.4 4036 static int setBacklightStringMS(WindowInfo *window, DataValue *argList, 4037 int nArgs, DataValue *result, char **errMsg) 4038 { 4039 char *backlightString; 4040 4041 if (nArgs == 0) { 4042 backlightString = GetPrefBacklightCharTypes(); 4043 } 4044 else if (nArgs == 1) { 4045 if (argList[0].tag != STRING_TAG) { 4046 *errMsg = "%s not called with a string parameter"; 4047 return False; 4048 } 4049 backlightString = argList[0].val.str.rep; 4050 } 4051 else 4052 return wrongNArgsErr(errMsg); 4053 4054 if (strcmp(backlightString, "default") == 0) 4055 backlightString = GetPrefBacklightCharTypes(); 4056 if (backlightString && *backlightString == '\0') / * empty string param * / 4057 backlightString = NULL; / * turns of backlighting * / 4058 4059 SetBacklightChars(window, backlightString); 4060 return True; 4061 } */ 4062 4063 static int cursorMV(WindowInfo *window, DataValue *argList, int nArgs, 4064 DataValue *result, char **errMsg) 4065 { 4066 result->tag = INT_TAG; 4067 result->val.n = TextGetCursorPos(window->lastFocus); 4068 return True; 4069 } 4070 4071 static int lineMV(WindowInfo *window, DataValue *argList, int nArgs, 4072 DataValue *result, char **errMsg) 4073 { 4074 int line, cursorPos, colNum; 4075 4076 result->tag = INT_TAG; 4077 cursorPos = TextGetCursorPos(window->lastFocus); 4078 if (!TextPosToLineAndCol(window->lastFocus, cursorPos, &line, &colNum)) 4079 line = BufCountLines(window->buffer, 0, cursorPos) + 1; 4080 result->val.n = line; 4081 return True; 4082 } 4083 4084 static int columnMV(WindowInfo *window, DataValue *argList, int nArgs, 4085 DataValue *result, char **errMsg) 4086 { 4087 textBuffer *buf = window->buffer; 4088 int cursorPos; 4089 4090 result->tag = INT_TAG; 4091 cursorPos = TextGetCursorPos(window->lastFocus); 4092 result->val.n = BufCountDispChars(buf, BufStartOfLine(buf, cursorPos), 4093 cursorPos); 4094 return True; 4095 } 4096 4097 static int fileNameMV(WindowInfo *window, DataValue *argList, int nArgs, 4098 DataValue *result, char **errMsg) 4099 { 4100 result->tag = STRING_TAG; 4101 AllocNStringCpy(&result->val.str, window->filename); 4102 return True; 4103 } 4104 4105 static int filePathMV(WindowInfo *window, DataValue *argList, int nArgs, 4106 DataValue *result, char **errMsg) 4107 { 4108 result->tag = STRING_TAG; 4109 AllocNStringCpy(&result->val.str, window->path); 4110 return True; 4111 } 4112 4113 static int lengthMV(WindowInfo *window, DataValue *argList, int nArgs, 4114 DataValue *result, char **errMsg) 4115 { 4116 result->tag = INT_TAG; 4117 result->val.n = window->buffer->length; 4118 return True; 4119 } 4120 4121 static int selectionStartMV(WindowInfo *window, DataValue *argList, int nArgs, 4122 DataValue *result, char **errMsg) 4123 { 4124 result->tag = INT_TAG; 4125 result->val.n = window->buffer->primary.selected ? 4126 window->buffer->primary.start : -1; 4127 return True; 4128 } 4129 4130 static int selectionEndMV(WindowInfo *window, DataValue *argList, int nArgs, 4131 DataValue *result, char **errMsg) 4132 { 4133 result->tag = INT_TAG; 4134 result->val.n = window->buffer->primary.selected ? 4135 window->buffer->primary.end : -1; 4136 return True; 4137 } 4138 4139 static int selectionLeftMV(WindowInfo *window, DataValue *argList, int nArgs, 4140 DataValue *result, char **errMsg) 4141 { 4142 selection *sel = &window->buffer->primary; 4143 4144 result->tag = INT_TAG; 4145 result->val.n = sel->selected && sel->rectangular ? sel->rectStart : -1; 4146 return True; 4147 } 4148 4149 static int selectionRightMV(WindowInfo *window, DataValue *argList, int nArgs, 4150 DataValue *result, char **errMsg) 4151 { 4152 selection *sel = &window->buffer->primary; 4153 4154 result->tag = INT_TAG; 4155 result->val.n = sel->selected && sel->rectangular ? sel->rectEnd : -1; 4156 return True; 4157 } 4158 4159 static int wrapMarginMV(WindowInfo *window, DataValue *argList, int nArgs, 4160 DataValue *result, char **errMsg) 4161 { 4162 int margin, nCols; 4163 4164 XtVaGetValues(window->textArea, textNcolumns, &nCols, 4165 textNwrapMargin, &margin, NULL); 4166 result->tag = INT_TAG; 4167 result->val.n = margin == 0 ? nCols : margin; 4168 return True; 4169 } 4170 4171 static int statisticsLineMV(WindowInfo *window, DataValue *argList, int nArgs, 4172 DataValue *result, char **errMsg) 4173 { 4174 result->tag = INT_TAG; 4175 result->val.n = window->showStats ? 1 : 0; 4176 return True; 4177 } 4178 4179 static int incSearchLineMV(WindowInfo *window, DataValue *argList, int nArgs, 4180 DataValue *result, char **errMsg) 4181 { 4182 result->tag = INT_TAG; 4183 result->val.n = window->showISearchLine ? 1 : 0; 4184 return True; 4185 } 4186 4187 static int showLineNumbersMV(WindowInfo *window, DataValue *argList, int nArgs, 4188 DataValue *result, char **errMsg) 4189 { 4190 result->tag = INT_TAG; 4191 result->val.n = window->showLineNumbers ? 1 : 0; 4192 return True; 4193 } 4194 4195 static int autoIndentMV(WindowInfo *window, DataValue *argList, int nArgs, 4196 DataValue *result, char **errMsg) 4197 { 4198 char *res = NULL; 4199 4200 switch (window->indentStyle) { 4201 case NO_AUTO_INDENT: 4202 res = PERM_ALLOC_STR("off"); 4203 break; 4204 case AUTO_INDENT: 4205 res = PERM_ALLOC_STR("on"); 4206 break; 4207 case SMART_INDENT: 4208 res = PERM_ALLOC_STR("smart"); 4209 break; 4210 default: 4211 *errMsg = "Invalid indent style value encountered in %s"; 4212 return False; 4213 break; 4214 } 4215 result->tag = STRING_TAG; 4216 result->val.str.rep = res; 4217 result->val.str.len = strlen(res); 4218 return True; 4219 } 4220 4221 static int wrapTextMV(WindowInfo *window, DataValue *argList, int nArgs, 4222 DataValue *result, char **errMsg) 4223 { 4224 char *res = NULL; 4225 4226 switch (window->wrapMode) { 4227 case NO_WRAP: 4228 res = PERM_ALLOC_STR("none"); 4229 break; 4230 case NEWLINE_WRAP: 4231 res = PERM_ALLOC_STR("auto"); 4232 break; 4233 case CONTINUOUS_WRAP: 4234 res = PERM_ALLOC_STR("continuous"); 4235 break; 4236 default: 4237 *errMsg = "Invalid wrap style value encountered in %s"; 4238 return False; 4239 break; 4240 } 4241 result->tag = STRING_TAG; 4242 result->val.str.rep = res; 4243 result->val.str.len = strlen(res); 4244 return True; 4245 } 4246 4247 static int highlightSyntaxMV(WindowInfo *window, DataValue *argList, int nArgs, 4248 DataValue *result, char **errMsg) 4249 { 4250 result->tag = INT_TAG; 4251 result->val.n = window->highlightSyntax ? 1 : 0; 4252 return True; 4253 } 4254 4255 static int makeBackupCopyMV(WindowInfo *window, DataValue *argList, int nArgs, 4256 DataValue *result, char **errMsg) 4257 { 4258 result->tag = INT_TAG; 4259 result->val.n = window->saveOldVersion ? 1 : 0; 4260 return True; 4261 } 4262 4263 static int incBackupMV(WindowInfo *window, DataValue *argList, int nArgs, 4264 DataValue *result, char **errMsg) 4265 { 4266 result->tag = INT_TAG; 4267 result->val.n = window->autoSave ? 1 : 0; 4268 return True; 4269 } 4270 4271 static int showMatchingMV(WindowInfo *window, DataValue *argList, int nArgs, 4272 DataValue *result, char **errMsg) 4273 { 4274 char *res = NULL; 4275 4276 switch (window->showMatchingStyle) { 4277 case NO_FLASH: 4278 res = PERM_ALLOC_STR(NO_FLASH_STRING); 4279 break; 4280 case FLASH_DELIMIT: 4281 res = PERM_ALLOC_STR(FLASH_DELIMIT_STRING); 4282 break; 4283 case FLASH_RANGE: 4284 res = PERM_ALLOC_STR(FLASH_RANGE_STRING); 4285 break; 4286 default: 4287 *errMsg = "Invalid match flashing style value encountered in %s"; 4288 return False; 4289 break; 4290 } 4291 result->tag = STRING_TAG; 4292 result->val.str.rep = res; 4293 result->val.str.len = strlen(res); 4294 return True; 4295 } 4296 4297 static int matchSyntaxBasedMV(WindowInfo *window, DataValue *argList, int nArgs, 4298 DataValue *result, char **errMsg) 4299 { 4300 result->tag = INT_TAG; 4301 result->val.n = window->matchSyntaxBased ? 1 : 0; 4302 return True; 4303 } 4304 4305 4306 4307 static int overTypeModeMV(WindowInfo *window, DataValue *argList, int nArgs, 4308 DataValue *result, char **errMsg) 4309 { 4310 result->tag = INT_TAG; 4311 result->val.n = window->overstrike ? 1 : 0; 4312 return True; 4313 } 4314 4315 static int readOnlyMV(WindowInfo *window, DataValue *argList, int nArgs, 4316 DataValue *result, char **errMsg) 4317 { 4318 result->tag = INT_TAG; 4319 result->val.n = (IS_ANY_LOCKED(window->lockReasons)) ? 1 : 0; 4320 return True; 4321 } 4322 4323 static int lockedMV(WindowInfo *window, DataValue *argList, int nArgs, 4324 DataValue *result, char **errMsg) 4325 { 4326 result->tag = INT_TAG; 4327 result->val.n = (IS_USER_LOCKED(window->lockReasons)) ? 1 : 0; 4328 return True; 4329 } 4330 4331 static int fileFormatMV(WindowInfo *window, DataValue *argList, int nArgs, 4332 DataValue *result, char **errMsg) 4333 { 4334 char *res = NULL; 4335 4336 switch (window->fileFormat) { 4337 case UNIX_FILE_FORMAT: 4338 res = PERM_ALLOC_STR("unix"); 4339 break; 4340 case DOS_FILE_FORMAT: 4341 res = PERM_ALLOC_STR("dos"); 4342 break; 4343 case MAC_FILE_FORMAT: 4344 res = PERM_ALLOC_STR("macintosh"); 4345 break; 4346 default: 4347 *errMsg = "Invalid linefeed style value encountered in %s"; 4348 return False; 4349 } 4350 result->tag = STRING_TAG; 4351 result->val.str.rep = res; 4352 result->val.str.len = strlen(res); 4353 return True; 4354 } 4355 4356 static int fontNameMV(WindowInfo *window, DataValue *argList, int nArgs, 4357 DataValue *result, char **errMsg) 4358 { 4359 result->tag = STRING_TAG; 4360 AllocNStringCpy(&result->val.str, window->fontName); 4361 return True; 4362 } 4363 4364 static int fontNameItalicMV(WindowInfo *window, DataValue *argList, int nArgs, 4365 DataValue *result, char **errMsg) 4366 { 4367 result->tag = STRING_TAG; 4368 AllocNStringCpy(&result->val.str, window->italicFontName); 4369 return True; 4370 } 4371 4372 static int fontNameBoldMV(WindowInfo *window, DataValue *argList, int nArgs, 4373 DataValue *result, char **errMsg) 4374 { 4375 result->tag = STRING_TAG; 4376 AllocNStringCpy(&result->val.str, window->boldFontName); 4377 return True; 4378 } 4379 4380 static int fontNameBoldItalicMV(WindowInfo *window, DataValue *argList, int nArgs, 4381 DataValue *result, char **errMsg) 4382 { 4383 result->tag = STRING_TAG; 4384 AllocNStringCpy(&result->val.str, window->boldItalicFontName); 4385 return True; 4386 } 4387 4388 static int subscriptSepMV(WindowInfo *window, DataValue *argList, int nArgs, 4389 DataValue *result, char **errMsg) 4390 { 4391 result->tag = STRING_TAG; 4392 result->val.str.rep = PERM_ALLOC_STR(ARRAY_DIM_SEP); 4393 result->val.str.len = strlen(result->val.str.rep); 4394 return True; 4395 } 4396 4397 static int minFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs, 4398 DataValue *result, char **errMsg) 4399 { 4400 result->tag = INT_TAG; 4401 result->val.n = TextGetMinFontWidth(window->textArea, window->highlightSyntax); 4402 return True; 4403 } 4404 4405 static int maxFontWidthMV(WindowInfo *window, DataValue *argList, int nArgs, 4406 DataValue *result, char **errMsg) 4407 { 4408 result->tag = INT_TAG; 4409 result->val.n = TextGetMaxFontWidth(window->textArea, window->highlightSyntax); 4410 return True; 4411 } 4412 4413 static int topLineMV(WindowInfo *window, DataValue *argList, int nArgs, 4414 DataValue *result, char **errMsg) 4415 { 4416 result->tag = INT_TAG; 4417 result->val.n = TextFirstVisibleLine(window->lastFocus); 4418 return True; 4419 } 4420 4421 static int numDisplayLinesMV(WindowInfo *window, DataValue *argList, int nArgs, 4422 DataValue *result, char **errMsg) 4423 { 4424 result->tag = INT_TAG; 4425 result->val.n = TextNumVisibleLines(window->lastFocus); 4426 return True; 4427 } 4428 4429 static int displayWidthMV(WindowInfo *window, DataValue *argList, int nArgs, 4430 DataValue *result, char **errMsg) 4431 { 4432 result->tag = INT_TAG; 4433 result->val.n = TextVisibleWidth(window->lastFocus); 4434 return True; 4435 } 4436 4437 static int activePaneMV(WindowInfo *window, DataValue *argList, int nArgs, 4438 DataValue *result, char **errMsg) 4439 { 4440 result->tag = INT_TAG; 4441 result->val.n = WidgetToPaneIndex(window, window->lastFocus) + 1; 4442 return True; 4443 } 4444 4445 static int nPanesMV(WindowInfo *window, DataValue *argList, int nArgs, 4446 DataValue *result, char **errMsg) 4447 { 4448 result->tag = INT_TAG; 4449 result->val.n = window->nPanes + 1; 4450 return True; 4451 } 4452 4453 static int emptyArrayMV(WindowInfo *window, DataValue *argList, int nArgs, 4454 DataValue *result, char **errMsg) 4455 { 4456 result->tag = ARRAY_TAG; 4457 result->val.arrayPtr = NULL; 4458 return True; 4459 } 4460 4461 static int serverNameMV(WindowInfo *window, DataValue *argList, int nArgs, 4462 DataValue *result, char **errMsg) 4463 { 4464 result->tag = STRING_TAG; 4465 AllocNStringCpy(&result->val.str, GetPrefServerName()); 4466 return True; 4467 } 4468 4469 static int tabDistMV(WindowInfo *window, DataValue *argList, int nArgs, 4470 DataValue *result, char **errMsg) 4471 { 4472 result->tag = INT_TAG; 4473 result->val.n = window->buffer->tabDist; 4474 return True; 4475 } 4476 4477 static int emTabDistMV(WindowInfo *window, DataValue *argList, int nArgs, 4478 DataValue *result, char **errMsg) 4479 { 4480 int dist; 4481 4482 XtVaGetValues(window->textArea, textNemulateTabs, &dist, NULL); 4483 result->tag = INT_TAG; 4484 result->val.n = dist; 4485 return True; 4486 } 4487 4488 static int useTabsMV(WindowInfo *window, DataValue *argList, int nArgs, 4489 DataValue *result, char **errMsg) 4490 { 4491 result->tag = INT_TAG; 4492 result->val.n = window->buffer->useTabs; 4493 return True; 4494 } 4495 4496 static int modifiedMV(WindowInfo *window, DataValue *argList, int nArgs, 4497 DataValue *result, char **errMsg) 4498 { 4499 result->tag = INT_TAG; 4500 result->val.n = window->fileChanged; 4501 return True; 4502 } 4503 4504 static int languageModeMV(WindowInfo *window, DataValue *argList, int nArgs, 4505 DataValue *result, char **errMsg) 4506 { 4507 char *lmName = LanguageModeName(window->languageMode); 4508 4509 if (lmName == NULL) 4510 lmName = "Plain"; 4511 result->tag = STRING_TAG; 4512 AllocNStringCpy(&result->val.str, lmName); 4513 return True; 4514 } 4515 4516 /* DISABLED for 5.4 4517 static int backlightStringMV(WindowInfo *window, DataValue *argList, 4518 int nArgs, DataValue *result, char **errMsg) 4519 { 4520 char *backlightString = window->backlightCharTypes; 4521 4522 result->tag = STRING_TAG; 4523 if (!backlightString || !window->backlightChars) 4524 backlightString = ""; 4525 AllocNStringCpy(&result->val.str, backlightString); 4526 return True; 4527 } */ 4528 4529 /* -------------------------------------------------------------------------- */ 4530 4531 /* 4532 ** Range set macro variables and functions 4533 */ 4534 static int rangesetListMV(WindowInfo *window, DataValue *argList, int nArgs, 4535 DataValue *result, char **errMsg) 4536 { 4537 RangesetTable *rangesetTable = window->buffer->rangesetTable; 4538 unsigned char *rangesetList; 4539 char *allocIndexStr; 4540 char indexStr[TYPE_INT_STR_SIZE(int)] ; 4541 int nRangesets, i; 4542 DataValue element; 4543 4544 result->tag = ARRAY_TAG; 4545 result->val.arrayPtr = ArrayNew(); 4546 4547 if (rangesetTable == NULL) { 4548 return True; 4549 } 4550 4551 rangesetList = RangesetGetList(rangesetTable); 4552 nRangesets = strlen((char*)rangesetList); 4553 for(i = 0; i < nRangesets; i++) { 4554 element.tag = INT_TAG; 4555 element.val.n = rangesetList[i]; 4556 4557 sprintf(indexStr, "%d", nRangesets - i - 1); 4558 allocIndexStr = AllocString(strlen(indexStr) + 1); 4559 if (allocIndexStr == NULL) 4560 M_FAILURE("Failed to allocate array key in %s"); 4561 strcpy(allocIndexStr, indexStr); 4562 4563 if (!ArrayInsert(result, allocIndexStr, &element)) 4564 M_FAILURE("Failed to insert array element in %s"); 4565 } 4566 4567 return True; 4568 } 4569 4570 /* 4571 ** Returns the version number of the current macro language implementation. 4572 ** For releases, this is the same number as NEdit's major.minor version 4573 ** number to keep things simple. For developer versions this could really 4574 ** be anything. 4575 ** 4576 ** Note that the current way to build $VERSION builds the same value for 4577 ** different point revisions. This is done because the macro interface 4578 ** does not change for the same version. 4579 */ 4580 static int versionMV(WindowInfo* window, DataValue* argList, int nArgs, 4581 DataValue* result, char** errMsg) 4582 { 4583 static unsigned version = NEDIT_VERSION * 1000 + NEDIT_REVISION; 4584 4585 result->tag = INT_TAG; 4586 result->val.n = version; 4587 return True; 4588 } 4589 4590 /* 4591 ** Built-in macro subroutine to create a new rangeset or rangesets. 4592 ** If called with one argument: $1 is the number of rangesets required and 4593 ** return value is an array indexed 0 to n, with the rangeset labels as values; 4594 ** (or an empty array if the requested number of rangesets are not available). 4595 ** If called with no arguments, returns a single rangeset label (not an array), 4596 ** or an empty string if there are no rangesets available. 4597 */ 4598 static int rangesetCreateMS(WindowInfo *window, DataValue *argList, int nArgs, 4599 DataValue *result, char **errMsg) 4600 { 4601 int label; 4602 int i, nRangesetsRequired; 4603 DataValue element; 4604 char indexStr[TYPE_INT_STR_SIZE(int)], *allocIndexStr; 4605 4606 RangesetTable *rangesetTable = window->buffer->rangesetTable; 4607 4608 if (nArgs > 1) 4609 return wrongNArgsErr(errMsg); 4610 4611 if (rangesetTable == NULL) { 4612 window->buffer->rangesetTable = rangesetTable = 4613 RangesetTableAlloc(window->buffer); 4614 } 4615 4616 if (nArgs == 0) { 4617 label = RangesetCreate(rangesetTable); 4618 4619 result->tag = INT_TAG; 4620 result->val.n = label; 4621 return True; 4622 } 4623 else { 4624 if (!readIntArg(argList[0], &nRangesetsRequired, errMsg)) 4625 return False; 4626 4627 result->tag = ARRAY_TAG; 4628 result->val.arrayPtr = ArrayNew(); 4629 4630 if (nRangesetsRequired > nRangesetsAvailable(rangesetTable)) 4631 return True; 4632 4633 for (i = 0; i < nRangesetsRequired; i++) { 4634 element.tag = INT_TAG; 4635 element.val.n = RangesetCreate(rangesetTable); 4636 4637 sprintf(indexStr, "%d", i); 4638 allocIndexStr = AllocString(strlen(indexStr) + 1); 4639 if (!allocIndexStr) { 4640 *errMsg = "Array element failed to allocate key: %s"; 4641 return(False); 4642 } 4643 strcpy(allocIndexStr, indexStr); 4644 ArrayInsert(result, allocIndexStr, &element); 4645 } 4646 4647 return True; 4648 } 4649 } 4650 4651 /* 4652 ** Built-in macro subroutine for forgetting a range set. 4653 */ 4654 static int rangesetDestroyMS(WindowInfo *window, DataValue *argList, int nArgs, 4655 DataValue *result, char **errMsg) 4656 { 4657 RangesetTable *rangesetTable = window->buffer->rangesetTable; 4658 DataValue *array; 4659 DataValue element; 4660 char keyString[TYPE_INT_STR_SIZE(int)]; 4661 int deleteLabels[N_RANGESETS]; 4662 int i, arraySize; 4663 int label = 0; 4664 4665 if (nArgs != 1) { 4666 return wrongNArgsErr(errMsg); 4667 } 4668 4669 if (argList[0].tag == ARRAY_TAG) { 4670 array = &argList[0]; 4671 arraySize = ArraySize(array); 4672 4673 if (arraySize > N_RANGESETS) { 4674 M_FAILURE("Too many elements in array in %s"); 4675 } 4676 4677 for (i = 0; i < arraySize; i++) { 4678 sprintf(keyString, "%d", i); 4679 4680 if (!ArrayGet(array, keyString, &element)) { 4681 M_FAILURE("Invalid key in array in %s"); 4682 } 4683 4684 if (!readIntArg(element, &label, errMsg) 4685 || !RangesetLabelOK(label)) { 4686 M_FAILURE("Invalid rangeset label in array in %s"); 4687 } 4688 4689 deleteLabels[i] = label; 4690 } 4691 4692 for (i = 0; i < arraySize; i++) { 4693 RangesetForget(rangesetTable, deleteLabels[i]); 4694 } 4695 } else { 4696 if (!readIntArg(argList[0], &label, errMsg) 4697 || !RangesetLabelOK(label)) { 4698 M_FAILURE("Invalid rangeset label in %s"); 4699 } 4700 4701 if(rangesetTable != NULL) { 4702 RangesetForget(rangesetTable, label); 4703 } 4704 } 4705 4706 /* set up result */ 4707 result->tag = NO_TAG; 4708 return True; 4709 } 4710 4711 4712 /* 4713 ** Built-in macro subroutine for getting all range sets with a specfic name. 4714 ** Arguments are $1: range set name. 4715 ** return value is an array indexed 0 to n, with the rangeset labels as values; 4716 */ 4717 static int rangesetGetByNameMS(WindowInfo *window, DataValue *argList, int nArgs, 4718 DataValue *result, char **errMsg) 4719 { 4720 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 4721 Rangeset *rangeset; 4722 int label; 4723 char *name, *rangeset_name; 4724 RangesetTable *rangesetTable = window->buffer->rangesetTable; 4725 unsigned char *rangesetList; 4726 char *allocIndexStr; 4727 char indexStr[TYPE_INT_STR_SIZE(int)] ; 4728 int nRangesets, i, insertIndex = 0; 4729 DataValue element; 4730 4731 if (nArgs != 1) { 4732 return wrongNArgsErr(errMsg); 4733 } 4734 4735 if (!readStringArg(argList[0], &name, stringStorage[0], errMsg)) { 4736 M_FAILURE("First parameter is not a name string in %s"); 4737 } 4738 4739 result->tag = ARRAY_TAG; 4740 result->val.arrayPtr = ArrayNew(); 4741 4742 if (rangesetTable == NULL) { 4743 return True; 4744 } 4745 4746 rangesetList = RangesetGetList(rangesetTable); 4747 nRangesets = strlen((char *)rangesetList); 4748 for (i = 0; i < nRangesets; ++i) { 4749 label = rangesetList[i]; 4750 rangeset = RangesetFetch(rangesetTable, label); 4751 if (rangeset) { 4752 rangeset_name = RangesetGetName(rangeset); 4753 if (strcmp(name, rangeset_name ? rangeset_name : "") == 0) { 4754 element.tag = INT_TAG; 4755 element.val.n = label; 4756 4757 sprintf(indexStr, "%d", insertIndex); 4758 allocIndexStr = AllocString(strlen(indexStr) + 1); 4759 if (allocIndexStr == NULL) 4760 M_FAILURE("Failed to allocate array key in %s"); 4761 4762 strcpy(allocIndexStr, indexStr); 4763 4764 if (!ArrayInsert(result, allocIndexStr, &element)) 4765 M_FAILURE("Failed to insert array element in %s"); 4766 4767 ++insertIndex; 4768 } 4769 } 4770 } 4771 4772 return True; 4773 } 4774 4775 /* 4776 ** Built-in macro subroutine for adding to a range set. Arguments are $1: range 4777 ** set label (one integer), then either (a) $2: source range set label, 4778 ** (b) $2: int start-range, $3: int end-range, (c) nothing (use selection 4779 ** if any to specify range to add - must not be rectangular). Returns the 4780 ** index of the newly added range (cases b and c), or 0 (case a). 4781 */ 4782 static int rangesetAddMS(WindowInfo *window, DataValue *argList, int nArgs, 4783 DataValue *result, char **errMsg) 4784 { 4785 textBuffer *buffer = window->buffer; 4786 RangesetTable *rangesetTable = buffer->rangesetTable; 4787 Rangeset *targetRangeset, *sourceRangeset; 4788 int start, end, isRect, rectStart, rectEnd, maxpos, index; 4789 int label = 0; 4790 4791 if (nArgs < 1 || nArgs > 3) 4792 return wrongNArgsErr(errMsg); 4793 4794 if (!readIntArg(argList[0], &label, errMsg) 4795 || !RangesetLabelOK(label)) { 4796 M_FAILURE("First parameter is an invalid rangeset label in %s"); 4797 } 4798 4799 if (rangesetTable == NULL) { 4800 M_FAILURE("Rangeset does not exist in %s"); 4801 } 4802 4803 targetRangeset = RangesetFetch(rangesetTable, label); 4804 4805 if (targetRangeset == NULL) { 4806 M_FAILURE("Rangeset does not exist in %s"); 4807 } 4808 4809 start = end = -1; 4810 4811 if (nArgs == 1) { 4812 /* pick up current selection in this window */ 4813 if (!BufGetSelectionPos(buffer, &start, &end, 4814 &isRect, &rectStart, &rectEnd) || isRect) { 4815 M_FAILURE("Selection missing or rectangular in call to %s"); 4816 } 4817 if (!RangesetAddBetween(targetRangeset, start, end)) { 4818 M_FAILURE("Failure to add selection in %s"); 4819 } 4820 } 4821 4822 if (nArgs == 2) { 4823 /* add ranges taken from a second set */ 4824 if (!readIntArg(argList[1], &label, errMsg) 4825 || !RangesetLabelOK(label)) { 4826 M_FAILURE("Second parameter is an invalid rangeset label in %s"); 4827 } 4828 4829 sourceRangeset = RangesetFetch(rangesetTable, label); 4830 if (sourceRangeset == NULL) { 4831 M_FAILURE("Second rangeset does not exist in %s"); 4832 } 4833 4834 RangesetAdd(targetRangeset, sourceRangeset); 4835 } 4836 4837 if (nArgs == 3) { 4838 /* add a range bounded by the start and end positions in $2, $3 */ 4839 if (!readIntArg(argList[1], &start, errMsg)) { 4840 return False; 4841 } 4842 if (!readIntArg(argList[2], &end, errMsg)) { 4843 return False; 4844 } 4845 4846 /* make sure range is in order and fits buffer size */ 4847 maxpos = buffer->length; 4848 if (start < 0) start = 0; 4849 if (start > maxpos) start = maxpos; 4850 if (end < 0) end = 0; 4851 if (end > maxpos) end = maxpos; 4852 if (start > end) {int temp = start; start = end; end = temp;} 4853 4854 if ((start != end) && !RangesetAddBetween(targetRangeset, start, end)) { 4855 M_FAILURE("Failed to add range in %s"); 4856 } 4857 } 4858 4859 /* (to) which range did we just add? */ 4860 if (nArgs != 2 && start >= 0) { 4861 start = (start + end) / 2; /* "middle" of added range */ 4862 index = 1 + RangesetFindRangeOfPos(targetRangeset, start, False); 4863 } 4864 else { 4865 index = 0; 4866 } 4867 4868 /* set up result */ 4869 result->tag = INT_TAG; 4870 result->val.n = index; 4871 return True; 4872 } 4873 4874 4875 /* 4876 ** Built-in macro subroutine for removing from a range set. Almost identical to 4877 ** rangesetAddMS() - only changes are from RangesetAdd()/RangesetAddBetween() 4878 ** to RangesetSubtract()/RangesetSubtractBetween(), the handling of an 4879 ** undefined destination range, and that it returns no value. 4880 */ 4881 static int rangesetSubtractMS(WindowInfo *window, DataValue *argList, int nArgs, 4882 DataValue *result, char **errMsg) 4883 { 4884 textBuffer *buffer = window->buffer; 4885 RangesetTable *rangesetTable = buffer->rangesetTable; 4886 Rangeset *targetRangeset, *sourceRangeset; 4887 int start, end, isRect, rectStart, rectEnd, maxpos; 4888 int label = 0; 4889 4890 if (nArgs < 1 || nArgs > 3) { 4891 return wrongNArgsErr(errMsg); 4892 } 4893 4894 if (!readIntArg(argList[0], &label, errMsg) 4895 || !RangesetLabelOK(label)) { 4896 M_FAILURE("First parameter is an invalid rangeset label in %s"); 4897 } 4898 4899 if (rangesetTable == NULL) { 4900 M_FAILURE("Rangeset does not exist in %s"); 4901 } 4902 4903 targetRangeset = RangesetFetch(rangesetTable, label); 4904 if (targetRangeset == NULL) { 4905 M_FAILURE("Rangeset does not exist in %s"); 4906 } 4907 4908 if (nArgs == 1) { 4909 /* remove current selection in this window */ 4910 if (!BufGetSelectionPos(buffer, &start, &end, &isRect, &rectStart, &rectEnd) 4911 || isRect) { 4912 M_FAILURE("Selection missing or rectangular in call to %s"); 4913 } 4914 RangesetRemoveBetween(targetRangeset, start, end); 4915 } 4916 4917 if (nArgs == 2) { 4918 /* remove ranges taken from a second set */ 4919 if (!readIntArg(argList[1], &label, errMsg) 4920 || !RangesetLabelOK(label)) { 4921 M_FAILURE("Second parameter is an invalid rangeset label in %s"); 4922 } 4923 4924 sourceRangeset = RangesetFetch(rangesetTable, label); 4925 if (sourceRangeset == NULL) { 4926 M_FAILURE("Second rangeset does not exist in %s"); 4927 } 4928 RangesetRemove(targetRangeset, sourceRangeset); 4929 } 4930 4931 if (nArgs == 3) { 4932 /* remove a range bounded by the start and end positions in $2, $3 */ 4933 if (!readIntArg(argList[1], &start, errMsg)) 4934 return False; 4935 if (!readIntArg(argList[2], &end, errMsg)) 4936 return False; 4937 4938 /* make sure range is in order and fits buffer size */ 4939 maxpos = buffer->length; 4940 if (start < 0) start = 0; 4941 if (start > maxpos) start = maxpos; 4942 if (end < 0) end = 0; 4943 if (end > maxpos) end = maxpos; 4944 if (start > end) {int temp = start; start = end; end = temp;} 4945 4946 RangesetRemoveBetween(targetRangeset, start, end); 4947 } 4948 4949 /* set up result */ 4950 result->tag = NO_TAG; 4951 return True; 4952 } 4953 4954 4955 /* 4956 ** Built-in macro subroutine to invert a range set. Argument is $1: range set 4957 ** label (one alphabetic character). Returns nothing. Fails if range set 4958 ** undefined. 4959 */ 4960 static int rangesetInvertMS(WindowInfo *window, DataValue *argList, int nArgs, 4961 DataValue *result, char **errMsg) 4962 { 4963 4964 RangesetTable *rangesetTable = window->buffer->rangesetTable; 4965 Rangeset *rangeset; 4966 int label = 0; 4967 4968 if (nArgs != 1) 4969 return wrongNArgsErr(errMsg); 4970 4971 if (!readIntArg(argList[0], &label, errMsg) 4972 || !RangesetLabelOK(label)) { 4973 M_FAILURE("First parameter is an invalid rangeset label in %s"); 4974 } 4975 4976 if (rangesetTable == NULL) { 4977 M_FAILURE("Rangeset does not exist in %s"); 4978 } 4979 4980 rangeset = RangesetFetch(rangesetTable, label); 4981 if (rangeset == NULL) { 4982 M_FAILURE("Rangeset does not exist in %s"); 4983 } 4984 4985 if (RangesetInverse(rangeset) < 0) { 4986 M_FAILURE("Problem inverting rangeset in %s"); 4987 } 4988 4989 /* set up result */ 4990 result->tag = NO_TAG; 4991 return True; 4992 } 4993 4994 4995 /* 4996 ** Built-in macro subroutine for finding out info about a rangeset. Takes one 4997 ** argument of a rangeset label. Returns an array with the following keys: 4998 ** defined, count, color, mode. 4999 */ 5000 static int rangesetInfoMS(WindowInfo *window, DataValue *argList, int nArgs, 5001 DataValue *result, char **errMsg) 5002 { 5003 RangesetTable *rangesetTable = window->buffer->rangesetTable; 5004 Rangeset *rangeset = NULL; 5005 int count, defined; 5006 char *color, *name, *mode; 5007 DataValue element; 5008 int label = 0; 5009 5010 if (nArgs != 1) 5011 return wrongNArgsErr(errMsg); 5012 5013 if (!readIntArg(argList[0], &label, errMsg) 5014 || !RangesetLabelOK(label)) { 5015 M_FAILURE("First parameter is an invalid rangeset label in %s"); 5016 } 5017 5018 if (rangesetTable != NULL) { 5019 rangeset = RangesetFetch(rangesetTable, label); 5020 } 5021 5022 RangesetGetInfo(rangeset, &defined, &label, &count, &color, &name, &mode); 5023 5024 /* set up result */ 5025 result->tag = ARRAY_TAG; 5026 result->val.arrayPtr = ArrayNew(); 5027 5028 element.tag = INT_TAG; 5029 element.val.n = defined; 5030 if (!ArrayInsert(result, PERM_ALLOC_STR("defined"), &element)) 5031 M_FAILURE("Failed to insert array element \"defined\" in %s"); 5032 5033 element.tag = INT_TAG; 5034 element.val.n = count; 5035 if (!ArrayInsert(result, PERM_ALLOC_STR("count"), &element)) 5036 M_FAILURE("Failed to insert array element \"count\" in %s"); 5037 5038 element.tag = STRING_TAG; 5039 if (!AllocNStringCpy(&element.val.str, color)) 5040 M_FAILURE("Failed to allocate array value \"color\" in %s"); 5041 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &element)) 5042 M_FAILURE("Failed to insert array element \"color\" in %s"); 5043 5044 element.tag = STRING_TAG; 5045 if (!AllocNStringCpy(&element.val.str, name)) 5046 M_FAILURE("Failed to allocate array value \"name\" in %s"); 5047 if (!ArrayInsert(result, PERM_ALLOC_STR("name"), &element)) { 5048 M_FAILURE("Failed to insert array element \"name\" in %s"); 5049 } 5050 5051 element.tag = STRING_TAG; 5052 if (!AllocNStringCpy(&element.val.str, mode)) 5053 M_FAILURE("Failed to allocate array value \"mode\" in %s"); 5054 if (!ArrayInsert(result, PERM_ALLOC_STR("mode"), &element)) 5055 M_FAILURE("Failed to insert array element \"mode\" in %s"); 5056 5057 return True; 5058 } 5059 5060 /* 5061 ** Built-in macro subroutine for finding the extent of a range in a set. 5062 ** If only one parameter is supplied, use the spanning range of all 5063 ** ranges, otherwise select the individual range specified. Returns 5064 ** an array with the keys "start" and "end" and values 5065 */ 5066 static int rangesetRangeMS(WindowInfo *window, DataValue *argList, int nArgs, 5067 DataValue *result, char **errMsg) 5068 { 5069 textBuffer *buffer = window->buffer; 5070 RangesetTable *rangesetTable = buffer->rangesetTable; 5071 Rangeset *rangeset; 5072 int start, end, dummy, rangeIndex, ok; 5073 DataValue element; 5074 int label = 0; 5075 5076 if (nArgs < 1 || nArgs > 2) { 5077 return wrongNArgsErr(errMsg); 5078 } 5079 5080 if (!readIntArg(argList[0], &label, errMsg) 5081 || !RangesetLabelOK(label)) { 5082 M_FAILURE("First parameter is an invalid rangeset label in %s"); 5083 } 5084 5085 if (rangesetTable == NULL) { 5086 M_FAILURE("Rangeset does not exist in %s"); 5087 } 5088 5089 ok = False; 5090 rangeset = RangesetFetch(rangesetTable, label); 5091 if (rangeset != NULL) { 5092 if (nArgs == 1) { 5093 rangeIndex = RangesetGetNRanges(rangeset) - 1; 5094 ok = RangesetFindRangeNo(rangeset, 0, &start, &dummy); 5095 ok &= RangesetFindRangeNo(rangeset, rangeIndex, &dummy, &end); 5096 rangeIndex = -1; 5097 } 5098 else if (nArgs == 2) { 5099 if (!readIntArg(argList[1], &rangeIndex, errMsg)) { 5100 return False; 5101 } 5102 ok = RangesetFindRangeNo(rangeset, rangeIndex-1, &start, &end); 5103 } 5104 } 5105 5106 /* set up result */ 5107 result->tag = ARRAY_TAG; 5108 result->val.arrayPtr = ArrayNew(); 5109 5110 if (!ok) 5111 return True; 5112 5113 element.tag = INT_TAG; 5114 element.val.n = start; 5115 if (!ArrayInsert(result, PERM_ALLOC_STR("start"), &element)) 5116 M_FAILURE("Failed to insert array element \"start\" in %s"); 5117 5118 element.tag = INT_TAG; 5119 element.val.n = end; 5120 if (!ArrayInsert(result, PERM_ALLOC_STR("end"), &element)) 5121 M_FAILURE("Failed to insert array element \"end\" in %s"); 5122 5123 return True; 5124 } 5125 5126 /* 5127 ** Built-in macro subroutine for checking a position against a range. If only 5128 ** one parameter is supplied, the current cursor position is used. Returns 5129 ** false (zero) if not in a range, range index (1-based) if in a range; 5130 ** fails if parameters were bad. 5131 */ 5132 static int rangesetIncludesPosMS(WindowInfo *window, DataValue *argList, 5133 int nArgs, DataValue *result, char **errMsg) 5134 { 5135 textBuffer *buffer = window->buffer; 5136 RangesetTable *rangesetTable = buffer->rangesetTable; 5137 Rangeset *rangeset; 5138 int pos, rangeIndex, maxpos; 5139 int label = 0; 5140 5141 if (nArgs < 1 || nArgs > 2) { 5142 return wrongNArgsErr(errMsg); 5143 } 5144 5145 if (!readIntArg(argList[0], &label, errMsg) 5146 || !RangesetLabelOK(label)) { 5147 M_FAILURE("First parameter is an invalid rangeset label in %s"); 5148 } 5149 5150 if (rangesetTable == NULL) { 5151 M_FAILURE("Rangeset does not exist in %s"); 5152 } 5153 5154 rangeset = RangesetFetch(rangesetTable, label); 5155 if (rangeset == NULL) { 5156 M_FAILURE("Rangeset does not exist in %s"); 5157 } 5158 5159 if (nArgs == 1) { 5160 pos = TextGetCursorPos(window->lastFocus); 5161 } 5162 else if (nArgs == 2) { 5163 if (!readIntArg(argList[1], &pos, errMsg)) 5164 return False; 5165 } 5166 5167 maxpos = buffer->length; 5168 if (pos < 0 || pos > maxpos) { 5169 rangeIndex = 0; 5170 } 5171 else { 5172 rangeIndex = RangesetFindRangeOfPos(rangeset, pos, False) + 1; 5173 } 5174 5175 /* set up result */ 5176 result->tag = INT_TAG; 5177 result->val.n = rangeIndex; 5178 return True; 5179 } 5180 5181 /* 5182 ** Set the color of a range set's ranges. it is ignored if the color cannot be 5183 ** found/applied. If no color is applied, any current color is removed. Returns 5184 ** true if the rangeset is valid. 5185 */ 5186 static int rangesetSetColorMS(WindowInfo *window, DataValue *argList, 5187 int nArgs, DataValue *result, char **errMsg) 5188 { 5189 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 5190 textBuffer *buffer = window->buffer; 5191 RangesetTable *rangesetTable = buffer->rangesetTable; 5192 Rangeset *rangeset; 5193 char *color_name; 5194 int label = 0; 5195 5196 if (nArgs != 2) { 5197 return wrongNArgsErr(errMsg); 5198 } 5199 5200 if (!readIntArg(argList[0], &label, errMsg) 5201 || !RangesetLabelOK(label)) { 5202 M_FAILURE("First parameter is an invalid rangeset label in %s"); 5203 } 5204 5205 if (rangesetTable == NULL) { 5206 M_FAILURE("Rangeset does not exist in %s"); 5207 } 5208 5209 rangeset = RangesetFetch(rangesetTable, label); 5210 if (rangeset == NULL) { 5211 M_FAILURE("Rangeset does not exist in %s"); 5212 } 5213 5214 color_name = ""; 5215 if (rangeset != NULL) { 5216 if (!readStringArg(argList[1], &color_name, stringStorage[0], errMsg)) { 5217 M_FAILURE("Second parameter is not a color name string in %s"); 5218 } 5219 } 5220 5221 RangesetAssignColorName(rangeset, color_name); 5222 5223 /* set up result */ 5224 result->tag = NO_TAG; 5225 return True; 5226 } 5227 5228 /* 5229 ** Set the name of a range set's ranges. Returns 5230 ** true if the rangeset is valid. 5231 */ 5232 static int rangesetSetNameMS(WindowInfo *window, DataValue *argList, 5233 int nArgs, DataValue *result, char **errMsg) 5234 { 5235 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 5236 textBuffer *buffer = window->buffer; 5237 RangesetTable *rangesetTable = buffer->rangesetTable; 5238 Rangeset *rangeset; 5239 char *name; 5240 int label = 0; 5241 5242 if (nArgs != 2) { 5243 return wrongNArgsErr(errMsg); 5244 } 5245 5246 if (!readIntArg(argList[0], &label, errMsg) 5247 || !RangesetLabelOK(label)) { 5248 M_FAILURE("First parameter is an invalid rangeset label in %s"); 5249 } 5250 5251 if (rangesetTable == NULL) { 5252 M_FAILURE("Rangeset does not exist in %s"); 5253 } 5254 5255 rangeset = RangesetFetch(rangesetTable, label); 5256 if (rangeset == NULL) { 5257 M_FAILURE("Rangeset does not exist in %s"); 5258 } 5259 5260 name = ""; 5261 if (rangeset != NULL) { 5262 if (!readStringArg(argList[1], &name, stringStorage[0], errMsg)) { 5263 M_FAILURE("Second parameter is not a valid name string in %s"); 5264 } 5265 } 5266 5267 RangesetAssignName(rangeset, name); 5268 5269 /* set up result */ 5270 result->tag = NO_TAG; 5271 return True; 5272 } 5273 5274 /* 5275 ** Change a range's modification response. Returns true if the rangeset is 5276 ** valid and the response type name is valid. 5277 */ 5278 static int rangesetSetModeMS(WindowInfo *window, DataValue *argList, 5279 int nArgs, DataValue *result, char **errMsg) 5280 { 5281 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 5282 textBuffer *buffer = window->buffer; 5283 RangesetTable *rangesetTable = buffer->rangesetTable; 5284 Rangeset *rangeset; 5285 char *update_fn_name; 5286 int ok; 5287 int label = 0; 5288 5289 if (nArgs < 1 || nArgs > 2) { 5290 return wrongNArgsErr(errMsg); 5291 } 5292 5293 if (!readIntArg(argList[0], &label, errMsg) 5294 || !RangesetLabelOK(label)) { 5295 M_FAILURE("First parameter is an invalid rangeset label in %s"); 5296 } 5297 5298 if (rangesetTable == NULL) { 5299 M_FAILURE("Rangeset does not exist in %s"); 5300 } 5301 5302 rangeset = RangesetFetch(rangesetTable, label); 5303 if (rangeset == NULL) { 5304 M_FAILURE("Rangeset does not exist in %s"); 5305 } 5306 5307 update_fn_name = ""; 5308 if (rangeset != NULL) { 5309 if (nArgs == 2) { 5310 if (!readStringArg(argList[1], &update_fn_name, stringStorage[0], errMsg)) { 5311 M_FAILURE("Second parameter is not a string in %s"); 5312 } 5313 } 5314 } 5315 5316 ok = RangesetChangeModifyResponse(rangeset, update_fn_name); 5317 5318 if (!ok) { 5319 M_FAILURE("Second parameter is not a valid mode in %s"); 5320 } 5321 5322 /* set up result */ 5323 result->tag = NO_TAG; 5324 return True; 5325 } 5326 5327 /* -------------------------------------------------------------------------- */ 5328 5329 5330 /* 5331 ** Routines to get details directly from the window. 5332 */ 5333 5334 /* 5335 ** Sets up an array containing information about a style given its name or 5336 ** a buffer position (bufferPos >= 0) and its highlighting pattern code 5337 ** (patCode >= 0). 5338 ** From the name we obtain: 5339 ** ["color"] Foreground color name of style 5340 ** ["background"] Background color name of style if specified 5341 ** ["bold"] '1' if style is bold, '0' otherwise 5342 ** ["italic"] '1' if style is italic, '0' otherwise 5343 ** Given position and pattern code we obtain: 5344 ** ["rgb"] RGB representation of foreground color of style 5345 ** ["back_rgb"] RGB representation of background color of style 5346 ** ["extent"] Forward distance from position over which style applies 5347 ** We only supply the style name if the includeName parameter is set: 5348 ** ["style"] Name of style 5349 ** 5350 */ 5351 static int fillStyleResult(DataValue *result, char **errMsg, 5352 WindowInfo *window, char *styleName, Boolean preallocatedStyleName, 5353 Boolean includeName, int patCode, int bufferPos) 5354 { 5355 DataValue DV; 5356 char colorValue[20]; 5357 int r, g, b; 5358 5359 /* initialize array */ 5360 result->tag = ARRAY_TAG; 5361 result->val.arrayPtr = ArrayNew(); 5362 5363 /* the following array entries will be strings */ 5364 DV.tag = STRING_TAG; 5365 5366 if (includeName) { 5367 /* insert style name */ 5368 if (preallocatedStyleName) { 5369 DV.val.str.rep = styleName; 5370 DV.val.str.len = strlen(styleName); 5371 } 5372 else { 5373 AllocNStringCpy(&DV.val.str, styleName); 5374 } 5375 M_STR_ALLOC_ASSERT(DV); 5376 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) { 5377 M_ARRAY_INSERT_FAILURE(); 5378 } 5379 } 5380 5381 /* insert color name */ 5382 AllocNStringCpy(&DV.val.str, ColorOfNamedStyle(window->colorProfile, styleName)); 5383 M_STR_ALLOC_ASSERT(DV); 5384 if (!ArrayInsert(result, PERM_ALLOC_STR("color"), &DV)) { 5385 M_ARRAY_INSERT_FAILURE(); 5386 } 5387 5388 /* Prepare array element for color value 5389 (only possible if we pass through the dynamic highlight pattern tables 5390 in other words, only if we have a pattern code) */ 5391 if (patCode) { 5392 HighlightColorValueOfCode(window, patCode, &r, &g, &b); 5393 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256); 5394 AllocNStringCpy(&DV.val.str, colorValue); 5395 M_STR_ALLOC_ASSERT(DV); 5396 if (!ArrayInsert(result, PERM_ALLOC_STR("rgb"), &DV)) { 5397 M_ARRAY_INSERT_FAILURE(); 5398 } 5399 } 5400 5401 /* Prepare array element for background color name */ 5402 AllocNStringCpy(&DV.val.str, BgColorOfNamedStyle(window->colorProfile, styleName)); 5403 M_STR_ALLOC_ASSERT(DV); 5404 if (!ArrayInsert(result, PERM_ALLOC_STR("background"), &DV)) { 5405 M_ARRAY_INSERT_FAILURE(); 5406 } 5407 5408 /* Prepare array element for background color value 5409 (only possible if we pass through the dynamic highlight pattern tables 5410 in other words, only if we have a pattern code) */ 5411 if (patCode) { 5412 GetHighlightBGColorOfCode(window, patCode, &r, &g, &b); 5413 sprintf(colorValue, "#%02x%02x%02x", r/256, g/256, b/256); 5414 AllocNStringCpy(&DV.val.str, colorValue); 5415 M_STR_ALLOC_ASSERT(DV); 5416 if (!ArrayInsert(result, PERM_ALLOC_STR("back_rgb"), &DV)) { 5417 M_ARRAY_INSERT_FAILURE(); 5418 } 5419 } 5420 5421 /* the following array entries will be integers */ 5422 DV.tag = INT_TAG; 5423 5424 /* Put boldness value in array */ 5425 DV.val.n = FontOfNamedStyleIsBold(window->colorProfile, styleName); 5426 if (!ArrayInsert(result, PERM_ALLOC_STR("bold"), &DV)) { 5427 M_ARRAY_INSERT_FAILURE(); 5428 } 5429 5430 /* Put italicity value in array */ 5431 DV.val.n = FontOfNamedStyleIsItalic(window->colorProfile, styleName); 5432 if (!ArrayInsert(result, PERM_ALLOC_STR("italic"), &DV)) { 5433 M_ARRAY_INSERT_FAILURE(); 5434 } 5435 5436 if (bufferPos >= 0) { 5437 /* insert extent */ 5438 const char *styleNameNotUsed = NULL; 5439 DV.val.n = StyleLengthOfCodeFromPos(window, bufferPos, &styleNameNotUsed); 5440 if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) { 5441 M_ARRAY_INSERT_FAILURE(); 5442 } 5443 } 5444 return True; 5445 } 5446 5447 /* 5448 ** Returns an array containing information about the style of name $1 5449 ** ["color"] Foreground color name of style 5450 ** ["background"] Background color name of style if specified 5451 ** ["bold"] '1' if style is bold, '0' otherwise 5452 ** ["italic"] '1' if style is italic, '0' otherwise 5453 ** 5454 */ 5455 static int getStyleByNameMS(WindowInfo *window, DataValue *argList, int nArgs, 5456 DataValue *result, char **errMsg) 5457 { 5458 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 5459 char *styleName; 5460 5461 /* Validate number of arguments */ 5462 if (nArgs != 1) { 5463 return wrongNArgsErr(errMsg); 5464 } 5465 5466 /* Prepare result */ 5467 result->tag = ARRAY_TAG; 5468 result->val.arrayPtr = NULL; 5469 5470 if (!readStringArg(argList[0], &styleName, stringStorage[0], errMsg)) { 5471 M_FAILURE("First parameter is not a string in %s"); 5472 } 5473 5474 if (!NamedStyleExists(window->colorProfile, styleName)) { 5475 /* if the given name is invalid we just return an empty array. */ 5476 return True; 5477 } 5478 5479 return fillStyleResult(result, errMsg, window, 5480 styleName, (argList[0].tag == STRING_TAG), False, 0, -1); 5481 } 5482 5483 /* 5484 ** Returns an array containing information about the style of position $1 5485 ** ["style"] Name of style 5486 ** ["color"] Foreground color name of style 5487 ** ["background"] Background color name of style if specified 5488 ** ["bold"] '1' if style is bold, '0' otherwise 5489 ** ["italic"] '1' if style is italic, '0' otherwise 5490 ** ["rgb"] RGB representation of foreground color of style 5491 ** ["back_rgb"] RGB representation of background color of style 5492 ** ["extent"] Forward distance from position over which style applies 5493 ** 5494 */ 5495 static int getStyleAtPosMS(WindowInfo *window, DataValue *argList, int nArgs, 5496 DataValue *result, char **errMsg) 5497 { 5498 int patCode; 5499 int bufferPos; 5500 textBuffer *buf = window->buffer; 5501 5502 /* Validate number of arguments */ 5503 if (nArgs != 1) { 5504 return wrongNArgsErr(errMsg); 5505 } 5506 5507 /* Prepare result */ 5508 result->tag = ARRAY_TAG; 5509 result->val.arrayPtr = NULL; 5510 5511 if (!readIntArg(argList[0], &bufferPos, errMsg)) { 5512 return False; 5513 } 5514 5515 /* Verify sane buffer position */ 5516 if ((bufferPos < 0) || (bufferPos >= buf->length)) { 5517 /* If the position is not legal, we cannot guess anything about 5518 the style, so we return an empty array. */ 5519 return True; 5520 } 5521 5522 /* Determine pattern code */ 5523 patCode = HighlightCodeOfPos(window, bufferPos); 5524 if (patCode == 0) { 5525 /* if there is no pattern we just return an empty array. */ 5526 return True; 5527 } 5528 5529 return fillStyleResult(result, errMsg, window, 5530 HighlightStyleOfCode(window, patCode), False, True, patCode, bufferPos); 5531 } 5532 5533 /* 5534 ** Sets up an array containing information about a pattern given its name or 5535 ** a buffer position (bufferPos >= 0). 5536 ** From the name we obtain: 5537 ** ["style"] Name of style 5538 ** ["extent"] Forward distance from position over which style applies 5539 ** We only supply the pattern name if the includeName parameter is set: 5540 ** ["pattern"] Name of pattern 5541 ** 5542 */ 5543 static int fillPatternResult(DataValue *result, char **errMsg, 5544 WindowInfo *window, char *patternName, Boolean preallocatedPatternName, 5545 Boolean includeName, char* styleName, int bufferPos) 5546 { 5547 DataValue DV; 5548 5549 /* initialize array */ 5550 result->tag = ARRAY_TAG; 5551 result->val.arrayPtr = ArrayNew(); 5552 5553 /* the following array entries will be strings */ 5554 DV.tag = STRING_TAG; 5555 5556 if (includeName) { 5557 /* insert pattern name */ 5558 if (preallocatedPatternName) { 5559 DV.val.str.rep = patternName; 5560 DV.val.str.len = strlen(patternName); 5561 } 5562 else { 5563 AllocNStringCpy(&DV.val.str, patternName); 5564 } 5565 M_STR_ALLOC_ASSERT(DV); 5566 if (!ArrayInsert(result, PERM_ALLOC_STR("pattern"), &DV)) { 5567 M_ARRAY_INSERT_FAILURE(); 5568 } 5569 } 5570 5571 /* insert style name */ 5572 AllocNStringCpy(&DV.val.str, styleName); 5573 M_STR_ALLOC_ASSERT(DV); 5574 if (!ArrayInsert(result, PERM_ALLOC_STR("style"), &DV)) { 5575 M_ARRAY_INSERT_FAILURE(); 5576 } 5577 5578 /* the following array entries will be integers */ 5579 DV.tag = INT_TAG; 5580 5581 if (bufferPos >= 0) { 5582 /* insert extent */ 5583 int checkCode = 0; 5584 DV.val.n = HighlightLengthOfCodeFromPos(window, bufferPos, &checkCode); 5585 if (!ArrayInsert(result, PERM_ALLOC_STR("extent"), &DV)) { 5586 M_ARRAY_INSERT_FAILURE(); 5587 } 5588 } 5589 5590 return True; 5591 } 5592 5593 /* 5594 ** Returns an array containing information about a highlighting pattern. The 5595 ** single parameter contains the pattern name for which this information is 5596 ** requested. 5597 ** The returned array looks like this: 5598 ** ["style"] Name of style 5599 */ 5600 static int getPatternByNameMS(WindowInfo *window, DataValue *argList, int nArgs, 5601 DataValue *result, char **errMsg) 5602 { 5603 char stringStorage[1][TYPE_INT_STR_SIZE(int)]; 5604 char *patternName = NULL; 5605 highlightPattern *pattern; 5606 5607 /* Begin of building the result. */ 5608 result->tag = ARRAY_TAG; 5609 result->val.arrayPtr = NULL; 5610 5611 /* Validate number of arguments */ 5612 if (nArgs != 1) { 5613 return wrongNArgsErr(errMsg); 5614 } 5615 5616 if (!readStringArg(argList[0], &patternName, stringStorage[0], errMsg)) { 5617 M_FAILURE("First parameter is not a string in %s"); 5618 } 5619 5620 pattern = FindPatternOfWindow(window, patternName); 5621 if (pattern == NULL) { 5622 /* The pattern's name is unknown. */ 5623 return True; 5624 } 5625 5626 return fillPatternResult(result, errMsg, window, patternName, 5627 (argList[0].tag == STRING_TAG), False, pattern->style, -1); 5628 } 5629 5630 /* 5631 ** Returns an array containing information about the highlighting pattern 5632 ** applied at a given position, passed as the only parameter. 5633 ** The returned array looks like this: 5634 ** ["pattern"] Name of pattern 5635 ** ["style"] Name of style 5636 ** ["extent"] Distance from position over which this pattern applies 5637 */ 5638 static int getPatternAtPosMS(WindowInfo *window, DataValue *argList, int nArgs, 5639 DataValue *result, char **errMsg) 5640 { 5641 int bufferPos = -1; 5642 textBuffer *buffer = window->buffer; 5643 int patCode = 0; 5644 5645 /* Begin of building the result. */ 5646 result->tag = ARRAY_TAG; 5647 result->val.arrayPtr = NULL; 5648 5649 /* Validate number of arguments */ 5650 if (nArgs != 1) { 5651 return wrongNArgsErr(errMsg); 5652 } 5653 5654 /* The most straightforward case: Get a pattern, style and extent 5655 for a buffer position. */ 5656 if (!readIntArg(argList[0], &bufferPos, errMsg)) { 5657 return False; 5658 } 5659 5660 /* Verify sane buffer position 5661 * You would expect that buffer->length would be among the sane 5662 * positions, but we have n characters and n+1 buffer positions. */ 5663 if ((bufferPos < 0) || (bufferPos >= buffer->length)) { 5664 /* If the position is not legal, we cannot guess anything about 5665 the highlighting pattern, so we return an empty array. */ 5666 return True; 5667 } 5668 5669 /* Determine the highlighting pattern used */ 5670 patCode = HighlightCodeOfPos(window, bufferPos); 5671 if (patCode == 0) { 5672 /* if there is no highlighting pattern we just return an empty array. */ 5673 return True; 5674 } 5675 5676 return fillPatternResult(result, errMsg, window, 5677 HighlightNameOfCode(window, patCode), False, True, 5678 HighlightStyleOfCode(window, patCode), bufferPos); 5679 } 5680 5681 static int wrongNArgsErr(char **errMsg) 5682 { 5683 *errMsg = "Wrong number of arguments to function %s"; 5684 return False; 5685 } 5686 5687 static int tooFewArgsErr(char **errMsg) 5688 { 5689 *errMsg = "Too few arguments to function %s"; 5690 return False; 5691 } 5692 5693 /* 5694 ** Get an integer value from a tagged DataValue structure. Return True 5695 ** if conversion succeeded, and store result in *result, otherwise 5696 ** return False with an error message in *errMsg. 5697 */ 5698 static int readIntArg(DataValue dv, int *result, char **errMsg) 5699 { 5700 char *c; 5701 5702 if (dv.tag == INT_TAG) { 5703 *result = dv.val.n; 5704 return True; 5705 } else if (dv.tag == STRING_TAG) { 5706 for (c=dv.val.str.rep; *c != '\0'; c++) { 5707 if (!(isdigit((unsigned char)*c) || *c == ' ' || *c == '\t')) { 5708 goto typeError; 5709 } 5710 } 5711 sscanf(dv.val.str.rep, "%d", result); 5712 return True; 5713 } 5714 5715 typeError: 5716 *errMsg = "%s called with non-integer argument"; 5717 return False; 5718 } 5719 5720 /* 5721 ** Get an string value from a tagged DataValue structure. Return True 5722 ** if conversion succeeded, and store result in *result, otherwise 5723 ** return False with an error message in *errMsg. If an integer value 5724 ** is converted, write the string in the space provided by "stringStorage", 5725 ** which must be large enough to handle ints of the maximum size. 5726 */ 5727 static int readStringArg(DataValue dv, char **result, char *stringStorage, 5728 char **errMsg) 5729 { 5730 if (dv.tag == STRING_TAG) { 5731 *result = dv.val.str.rep; 5732 return True; 5733 } else if (dv.tag == INT_TAG) { 5734 sprintf(stringStorage, "%d", dv.val.n); 5735 *result = stringStorage; 5736 return True; 5737 } 5738 *errMsg = "%s called with unknown object"; 5739 return False; 5740 } 5741