/*******************************************************************************
*									       *
* smartIndent.c -- Maintain, and allow user to edit, macros for smart indent   *
*									       *
* Copyright (c) 1997 Universities Research Association, Inc.		       *
* All rights reserved.							       *
* 									       *
* This material resulted from work developed under a Government Contract and   *
* is subject to the following license:  The Government retains a paid-up,      *
* nonexclusive, irrevocable worldwide license to reproduce, prepare derivative *
* works, perform publicly and display publicly by or for the Government,       *
* including the right to distribute to other Government contractors.  Neither  *
* the United States nor the United States Department of Energy, nor any of     *
* their employees, makes any warrenty, express or implied, or assumes any      *
* legal liability or responsibility for the accuracy, completeness, or         *
* usefulness of any information, apparatus, product, or process disclosed, or  *
* represents that its use would not infringe privately owned rights.           *
*                                        				       *
* Fermilab Nirvana GUI Library						       *
* July, 1997								       *
*									       *
* Written by Mark Edel							       *
*									       *
*******************************************************************************/
#include <stdio.h>
#include <limits.h>
#include <Xm/Xm.h>
#ifdef VMS
#include "../util/VMSparam.h"
#else
#include <sys/param.h>
#endif /*VMS*/
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Text.h>
#include <Xm/LabelG.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/SeparatoG.h>
#include <Xm/PanedW.h>
#include "../util/DialogF.h"
#include "../util/misc.h"
#include "textBuf.h"
#include "nedit.h"
#include "text.h"
#include "preferences.h"
#include "interpret.h"
#include "macro.h"
#include "window.h"
#include "parse.h"
#include "shift.h"
#include "help.h"
#include "smartIndent.h"

static char MacroEndBoundary[] = "--End-of-Macro--";

typedef struct {
    char *lmName;
    char *initMacro;
    char *newlineMacro;
    char *modMacro;
} smartIndentRec;

typedef struct {
    Program *newlineMacro;
    Program *modMacro;
} windowSmartIndentData;

/* Smart indent macros dialog information */
static struct {
    Widget shell;
    Widget lmOptMenu;
    Widget lmPulldown;
    Widget initMacro;
    Widget newlineMacro;
    Widget modMacro;
    char *langModeName;
} SmartIndentDialog = {NULL};

/* Common smart indent macros dialog information */
static struct {
    Widget shell;
    Widget text;
} CommonDialog = {NULL};

static int NSmartIndentSpecs = 0;
static smartIndentRec *SmartIndentSpecs[MAX_LANGUAGE_MODES];
static char *CommonMacros = NULL;

static void executeNewlineMacro(WindowInfo *window,smartIndentCBStruct *cbInfo);
static void executeModMacro(WindowInfo *window,smartIndentCBStruct *cbInfo);
static void insertShiftedMacro(textBuffer *buf, char *macro);
static int isDefaultIndentSpec(smartIndentRec *indentSpec);
static smartIndentRec *findIndentSpec(char *modeName);
static char *ensureNewline(char *string);
static int loadDefaultIndentSpec(char *lmName);
static int siParseError(char *stringStart, char *stoppedAt, char *message);
static void destroyCB(Widget w, XtPointer clientData, XtPointer callData);
static void langModeCB(Widget w, XtPointer clientData, XtPointer callData);
static void commonDialogCB(Widget w, XtPointer clientData, XtPointer callData);
static void lmDialogCB(Widget w, XtPointer clientData, XtPointer callData);
static void okCB(Widget w, XtPointer clientData, XtPointer callData);
static void applyCB(Widget w, XtPointer clientData, XtPointer callData);
static void checkCB(Widget w, XtPointer clientData, XtPointer callData);
static void restoreCB(Widget w, XtPointer clientData, XtPointer callData);
static void deleteCB(Widget w, XtPointer clientData, XtPointer callData);
static void dismissCB(Widget w, XtPointer clientData, XtPointer callData);
static void helpCB(Widget w, XtPointer clientData, XtPointer callData);
static int checkSmartIndentDialogData(void);
static smartIndentRec *getSmartIndentDialogData(void);
static void setSmartIndentDialogData(smartIndentRec *is);
static void comDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
static void comOKCB(Widget w, XtPointer clientData, XtPointer callData);
static void comApplyCB(Widget w, XtPointer clientData, XtPointer callData);
static void comCheckCB(Widget w, XtPointer clientData, XtPointer callData);
static void comRestoreCB(Widget w, XtPointer clientData, XtPointer callData);
static void comDismissCB(Widget w, XtPointer clientData, XtPointer callData);
static int updateSmartIndentCommonData(void);
static int checkSmartIndentCommonDialogData(void);
static int updateSmartIndentData(void);
static char *readSIMacro(char **inPtr);
static smartIndentRec *copyIndentSpec(smartIndentRec *is);
void freeIndentSpec(smartIndentRec *is);
int indentSpecsDiffer(smartIndentRec *is1, smartIndentRec *is2);

#define N_DEFAULT_INDENT_SPECS 3
static smartIndentRec DefaultIndentSpecs[N_DEFAULT_INDENT_SPECS] = {
{"C",
"# C Macros and tuning parameters are shared with C++, and are declared\n\
# in the common section.  Press Common / Shared Initialization above.\n",
"return cFindSmartIndentDist($1)\n",
"if ($2 == \"}\" || $2 == \"{\" || $2 == \"#\")\n\
    cBraceOrPound($1, $2)\n"},
{"C++",
"# C++ Macros and tuning parameters are shared with C, and are declared\n\
# in the common section.  Press Common / Shared Initialization above.\n",
"return cFindSmartIndentDist($1)\n",
"if ($2 == \"}\" || $2 == \"{\" || $2 == \"#\")\n\
    cBraceOrPound($1, $2)\n"},
{"Python",
"# Number of characters in a normal indent level.  May be a number, or the\n\
# string \"default\", meaning, guess the value from the current tab settings.\n\
$pyIndentDist = \"default\"\n",
"if (get_range($1-1, $1) != \":\")\n\
    return -1\n\
return measureIndent($1) + defaultIndent($pyIndentDist)\n", NULL}
};

static char DefaultCommonMacros[] = "#\n\
# C/C++ Style/tuning parameters\n\
#\n\
\n\
# Number of characters in a normal indent level.  May be a number, or the\n\
# string \"default\", meaning, guess the value from the current tab settings.\n\
$cIndentDist = \"default\"\n\
\n\
# Number of characters in a line continuation.  May be a number or the\n\
# string \"default\", meaning, guess the value from the current tab settings.\n\
$cContinuationIndent = \"default\"\n\
\n\
# How far back from the current position to search for an anchoring position\n\
# on which to base indent.  When no reliable indicators of proper indent level\n\
# can be found within the requested distance, reverts to plain auto indent.\n\
$cMaxSearchBackLines = 10\n\
\n\
#\n\
# Find the start of the line containing position $1\n\
#\n\
define startOfLine {\n\
\n\
    for (i=$1-1; ; i--) {\n\
	if (i <= 0)\n\
	    return 0\n\
	if (get_character(i) == \"\\n\")\n\
	    return i + 1\n\
    }\n\
}\n\
\n\
#\n\
# Find the indent level of the line containing character position $1\n\
#\n\
define measureIndent {\n\
    \n\
    # measure the indentation to the first non-white character on the line\n\
    indent = 0\n\
    for (i=startOfLine($1); i < $text_length; i++) {\n\
	c = get_character(i)\n\
	if (c != \" \" && c != \"\\t\")\n\
	    break\n\
	if (c == \"\\t\")\n\
	    indent += $tab_dist - (indent % $tab_dist)\n\
	else\n\
	    indent++\n\
    }\n\
    return indent\n\
}\n\
\n\
#\n\
# Make a string to produce an indent of $1 characters\n\
#\n\
define makeIndentString {\n\
\n\
    if ($use_tabs) {\n\
	nTabs = $1 / $tab_dist\n\
	nSpaces = $1 % $tab_dist\n\
    } else {\n\
	nTabs = 0\n\
	nSpaces = $1\n\
    }\n\
    indentString = \"\"\n\
    for (i=0; i<nTabs; i++)\n\
	indentString = indentString \"\\t\"\n\
    for (i=0; i<nSpaces; i++)\n\
	indentString = indentString \" \"\n\
    return indentString\n\
}\n\
\n\
#\n\
# If $1 is a number, just pass it on.  If it is the string \"default\",\n\
# figure out a reasonable indent distance for a structured languages\n\
# like C, based on how tabs are set.\n\
#\n\
define defaultIndent {\n\
\n\
    if ($1 != \"default\")\n\
    	return $1\n\
    if ($em_tab_dist != -1)\n\
    	return $em_tab_dist\n\
    if ($tab_dist <= 8)\n\
    	return $tab_dist\n\
    return 4\n\
}\n\
   \n\
#\n\
# If $1 is a number, just pass it on.  If it is the string \"default\",\n\
# figure out a reasonable amount of indentation for continued lines\n\
# based on how tabs are set.\n\
#\n\
define defaultContIndent {\n\
\n\
    if ($1 != \"default\")\n\
    	return $1\n\
    if ($em_tab_dist != -1)\n\
    	return $em_tab_dist * 2\n\
    if ($tab_dist <= 8)\n\
    	return $tab_dist * 2\n\
    return 8\n\
}\n\
\n\
#\n\
# Find the end of the conditional part of if/while/for, by looking for balanced\n\
# parenthesis between $1 and $2.  returns -1 if parens don't balance before\n\
# $2, or if no parens are found\n\
#\n\
define findBalancingParen {\n\
\n\
    openParens = 0\n\
    parensFound = 0\n\
    for (i=$1; i<$2; i++) {\n\
	c = get_character(i)\n\
	if (c == \"(\") {\n\
	    openParens++\n\
	    parensFound = 1\n\
	} else if (c == \")\")\n\
	    openParens--\n\
	else if (!parensFound && c != \" \" && c != \"\\t\")\n\
	    return -1\n\
	if (parensFound && openParens <=0)\n\
	    return i+1\n\
    }\n\
    return -1\n\
}\n\
\n\
#\n\
# Skip over blank space and comments and preprocessor directives from position\n\
# $1 to a maximum of $2.\n\
# if $3 is non-zero, newlines are considered blank space as well.  Return -1\n\
# if the maximum position ($2) is hit mid-comment or mid-directive\n\
#\n\
define cSkipBlankSpace {\n\
    \n\
    for (i=$1; i<$2; i++) {\n\
	c = get_character(i)\n\
	if (c == \"/\") {\n\
	    if (i+1 >= $2)\n\
		return i\n\
	    if (get_character(i+1) == \"*\") {\n\
		for (i=i+1; ; i++) {\n\
		    if (i+1 >= $2)\n\
			return -1\n\
		    if (get_character(i) == \"*\" && get_character(i+1) == \"/\") {\n\
			i++\n\
			break\n\
		    }\n\
		}\n\
	    } else if (get_character(i+1) == \"/\") {\n\
		for (i=i+1; i<$2; i++) {\n\
		    if (get_character(i) == \"\\n\") {\n\
			if (!$3)\n\
			    return i\n\
			break\n\
		    }\n\
		}\n\
	    }\n\
	} else if (c == \"#\" && $3) {\n\
	    for (i=i+1; ; i++) {\n\
		if (i >= $2) {\n\
		    if (get_character(i-1) == \"\\\\\")\n\
			return -1\n\
		    else\n\
			break\n\
		}\n\
		if (get_character(i) == \"\\n\" && get_character(i-1) != \"\\\\\")\n\
		    break\n\
	    }\n\
	} else if (!(c == \" \" || c == \"\\t\" || ($3 && c==\"\\n\")))\n\
	    return i\n\
    }\n\
    return $2\n\
}\n\
\n\
#\n\
# Search backward for an anchor point: a line ending brace, or semicolon\n\
# or case statement, followed (ignoring blank lines and comments) by what we\n\
# assume is a properly indented line, a brace on a line by itself, or a case\n\
# statement.  Returns the position of the first non-white, non comment\n\
# character on the line.  returns -1 if an anchor position can't be found\n\
# before $cMaxSearchBackLines.\n\
#\n\
define cFindIndentAnchorPoint {\n\
\n\
    nLines = 0\n\
    anchorPos = $1\n\
    for (i=$1-1; i>0; i--) {\n\
	c = get_character(i)\n\
	if (c == \";\" || c == \"{\" || c == \"}\" || c == \":\") {\n\
\n\
	    # Verify that it's line ending\n\
	    lineEnd = cSkipBlankSpace(i+1, $1, 0)\n\
	    if (lineEnd == -1 || \\\n\
	    	    (lineEnd != $text_length && get_character(lineEnd) != \"\\n\"))\n\
   		continue\n\
\n\
	    # if it's a colon, it's only meaningful if \"case\" begins the line\n\
	    if (c == \":\") {\n\
	    	lineStart = startOfLine(i)\n\
		caseStart = cSkipBlankSpace(lineStart, lineEnd, 0)\n\
		if (get_range(caseStart, caseStart+4) != \"case\")\n\
		    continue\n\
		delim = get_character(caseStart+4)\n\
		if (delim!=\" \" && delim!=\"\\t\" && delim!=\"(\" && delim!=\":\")\n\
		    continue\n\
		isCase = 1\n\
	    } else\n\
	    	isCase = 0\n\
\n\
	    # Move forward past blank lines and comment lines to find\n\
	    #    non-blank, non-comment line-start\n\
	    anchorPos = cSkipBlankSpace(lineEnd, $1, 1)\n\
\n\
	    # Accept if it's before the requested position, otherwise\n\
	    #    continue further back in the file and try again\n\
	    if (anchorPos != -1 && anchorPos < $1)\n\
		break\n\
\n\
	    # A case statement by itself is an acceptable anchor\n\
	    if (isCase)\n\
	    	return caseStart\n\
\n\
	    # A brace on a line by itself is an acceptable anchor, even\n\
	    #    if it doesn't follow a semicolon or another brace\n\
	    if (c == \"{\" || c == \"}\") {\n\
		for (j = i-1; ; j--) {\n\
		    if (j == 0)\n\
			return i\n\
		    ch = get_character(j)\n\
		    if (ch == \"\\n\")\n\
		       return i\n\
		    if (ch != \"\\t\" && ch != \" \")\n\
		       break\n\
		}\n\
	    }\n\
\n\
	} else if (c == \"\\n\")\n\
	    if (++nLines > $cMaxSearchBackLines)\n\
		return -1\n\
    }\n\
    if (i <= 0)\n\
	return -1\n\
    return anchorPos\n\
}\n\
\n\
#\n\
# adjust the indent on a line about to recive either a right or left brace\n\
# or pound (#) character ($2) following position $1\n\
#\n\
define cBraceOrPound {\n\
\n\
    # Find start of the line, and make sure there's nothing but white-space\n\
    #   before the character.  If there's anything before it, do nothing\n\
    for (i=$1-1; ; i--) {\n\
	if (i < 0) {\n\
	    lineStart = 0\n\
	    break\n\
	}\n\
	c = get_character(i)\n\
	if (c == \"\\n\") {\n\
	    lineStart = i + 1\n\
	    break\n\
	}\n\
	if (c != \" \" && c != \"\\t\")\n\
	    return\n\
    }\n\
\n\
    # If the character was a pound, drag it all the way to the left margin\n\
    if ($2 == \"#\") {\n\
	replace_range(lineStart, $1, \"\")\n\
	return\n\
    }\n\
\n\
    # Find the position on which to base the indent\n\
    indent = cFindSmartIndentDist($1 - 1, \"noContinue\")\n\
    if (indent == -1)\n\
	return\n\
    \n\
    # Adjust the indent if it's a right brace (left needs no adjustment)\n\
    if ($2 == \"}\") {\n\
	indent -= defaultIndent($cIndentDist)\n\
        if (indent < 0)\n\
	    indent = 0\n\
    }\n\
\n\
    # Replace the current indent with the new indent string\n\
    insertStr = makeIndentString(indent)\n\
    replace_range(lineStart, $1, insertStr)\n\
}\n\
\n\
#\n\
# Find Smart Indent Distance for a newline character inserted at $1,\n\
# or return -1 to give up.  Adding the optional argument \"noContinue\"\n\
# will stop the routine from inserting line continuation indents\n\
#\n\
define cFindSmartIndentDist {\n\
\n\
    # Find a known good indent to base the new indent upon\n\
    anchorPos = cFindIndentAnchorPoint($1)\n\
    if (anchorPos == -1)\n\
	return -1\n\
\n\
    # Find the indentation of that line\n\
    anchorIndent = measureIndent(anchorPos)\n\
\n\
    # Look for special keywords which affect indent (for, if, else while, do)\n\
    #    and modify the continuation indent distance to the normal indent\n\
    #    distance when a completed statement of this type occupies the line.\n\
    if ($n_args >= 2 && $2 == \"noContinue\") {\n\
	continueIndent = 0\n\
	$allowSemi = 0\n\
    } else\n\
	continueIndent = cCalcContinueIndent(anchorPos, $1)\n\
\n\
    # Move forward from anchor point, ignoring comments and blank lines,\n\
    #   remembering the last non-white, non-comment character.  If $1 is\n\
    #   in the middle of a comment, give up\n\
    lastChar = get_character(anchorPos)\n\
    if (anchorPos < $1) {\n\
	for (i=anchorPos;;) {\n\
   	    i = cSkipBlankSpace(i, $1, 1)\n\
	    if (i == -1)\n\
		return -1\n\
 	    if (i >= $1)\n\
 		break\n\
 	    lastChar = get_character(i++)\n\
	}\n\
    }\n\
\n\
    # Return the new indent based on the type of the last character.\n\
    #   In a for stmt, however, last character may be a semicolon and not\n\
    #   signal the end of the statement\n\
    if (lastChar == \"{\")\n\
	return anchorIndent + defaultIndent($cIndentDist)\n\
    else if (lastChar == \"}\")\n\
	return anchorIndent\n\
    else if (lastChar == \";\") {\n\
	if ($allowSemi)\n\
	    return anchorIndent + continueIndent\n\
	else\n\
	    return anchorIndent\n\
    } else if (lastChar == \":\" && get_range(anchorPos, anchorPos+4) == \"case\")\n\
    	return anchorIndent + defaultIndent($cIndentDist)\n\
    return anchorIndent + continueIndent\n\
}\n\
\n\
#\n\
# Calculate the continuation indent distance for statements not ending in\n\
# semicolons or braces.  This is not necessarily $continueIndent.  It may\n\
# be adjusted if the statement contains if, while, for, or else.\n\
#\n\
# As a side effect, also return $allowSemi to help distinguish statements\n\
# which might contain an embedded semicolon, which should not be interpreted\n\
# as an end of statement character.\n\
#\n\
define cCalcContinueIndent {\n\
\n\
    anchorPos = $1\n\
    maxPos = $2\n\
\n\
    # Figure out if the anchor is on a keyword which changes indent.  A special\n\
    #   case is made for elses nested in after braces\n\
    anchorIsFor = 0\n\
    $allowSemi = 0\n\
    if (get_character(anchorPos) == \"}\") {\n\
	for (i=anchorPos+1; i<maxPos; i++) {\n\
	    c = get_character(i)\n\
	    if (c != \" \" && c != \"\\t\")\n\
		break\n\
	}\n\
	if (get_range(i, i+4) == \"else\") {\n\
	    keywordEnd = i + 4\n\
	    needsBalancedParens = 0\n\
	} else\n\
	    return defaultContIndent($cContinuationIndent)\n\
    } else if (get_range(anchorPos, anchorPos + 4) == \"else\") {\n\
	keywordEnd = anchorPos + 4\n\
	needsBalancedParens = 0\n\
    } else if (get_range(anchorPos, anchorPos + 2) == \"do\") {\n\
	keywordEnd = anchorPos + 2\n\
	needsBalancedParens = 0\n\
    } else if (get_range(anchorPos, anchorPos + 3) == \"for\") {\n\
	keywordEnd = anchorPos + 3\n\
	anchorIsFor = 1\n\
	needsBalancedParens = 1\n\
    } else if (get_range(anchorPos, anchorPos + 2) == \"if\") {\n\
	keywordEnd = anchorPos + 2\n\
	needsBalancedParens = 1\n\
    } else if (get_range(anchorPos, anchorPos + 5) == \"while\") {\n\
	keywordEnd = anchorPos + 5\n\
	needsBalancedParens = 1\n\
    } else\n\
	return defaultContIndent($cContinuationIndent)\n\
\n\
    # If the keyword must be followed balanced parenthesis, find the end of\n\
    # the statement by following balanced parens.  If the parens aren't\n\
    # balanced by maxPos, continue the condition.  In the special case of\n\
    # the for keyword, a semicolon can end the line and the caller should be\n\
    # signaled to allow that\n\
    if (needsBalancedParens) {\n\
	stmtEnd = findBalancingParen(keywordEnd, maxPos)\n\
	if (stmtEnd == -1) {\n\
	    $allowSemi = anchorIsFor\n\
	    return defaultContIndent($cContinuationIndent)\n\
	}\n\
    } else\n\
	stmtEnd = keywordEnd\n\
\n\
    # check if the statement ends the line\n\
    lineEnd = cSkipBlankSpace(stmtEnd, maxPos, 0)\n\
    if (lineEnd == -1)		    	    # ends in comment or preproc\n\
	return -1\n\
    if (lineEnd == maxPos)  	    	    # maxPos happens at stmt end\n\
	return defaultIndent($cIndentDist)\n\
    c = get_character(lineEnd)\n\
    if (c != \"\\n\")   		    	    # something past last paren on line,\n\
	return defaultIndent($cIndentDist)  #   probably quoted or extra braces\n\
\n\
    # stmt contintinues beyond matching paren && newline, we're in\n\
    #   the conditional part, calculate the continue indent distance\n\
    #   recursively, based on the anchor point of the new line\n\
    newAnchor = cSkipBlankSpace(lineEnd+1, maxPos, 1)\n\
    if (newAnchor == -1)\n\
	return -1\n\
    if (newAnchor == maxPos)\n\
	return defaultIndent($cIndentDist)\n\
    return cCalcContinueIndent(newAnchor, maxPos) + defaultIndent($cIndentDist)\n\
}\n\
";

/*
** Turn on smart-indent (well almost).  Unfortunately, this doesn't do
** everything.  It requires that the smart indent callback (SmartIndentCB)
** is already attached to all of the text widgets in the window, and that the
** smartIndent resource must be turned on in the widget.  These are done
** separately, because they are required per-text widget, and therefore must
** be repeated whenever a new text widget is created within this window
** (a split-window command).
*/
void BeginSmartIndent(WindowInfo *window, int warn)
{
    windowSmartIndentData *winData;
    smartIndentRec *indentMacros;
    char *modeName, *stoppedAt, *errMsg;
    static int initialized;

    /* Find the window's language mode.  If none is set, warn the user */
    modeName = LanguageModeName(window->languageMode);
    if (modeName == NULL) {
    	if (warn)
	    DialogF(DF_WARN, window->shell, 1,
"No language-specific mode has been set for this file.\n\
\n\
To use smart indent in this window, please select a\n\
language from the Preferences -> Language Modes menu.", "Dismiss");
    	return;
    }
    
    /* Look up the appropriate smart-indent macros for the language */
    indentMacros = findIndentSpec(modeName);
    if (indentMacros == NULL) {
    	if (warn)
	    DialogF(DF_WARN, window->shell, 1,
"Smart indent is not available in languagemode\n\
%s.\n\
\n\
You can create new smart indent macros in the\n\
Preferences -> Default Settings -> Smart Indent\n\
dialog, or choose a different language mode from:\n\
Preferences -> Language Mode.", "Dismiss", modeName);
    	return;
    }
    
    /* Compile and run the common and language-specific initialization macros
       (Note that when these return, the immediate commands in the file have not
       necessarily been executed yet.  They are only SCHEDULED for execution) */
    if (!initialized) {
    	if (!ReadMacroString(window, CommonMacros,
	    	"smart indent common initialization macros"))
    	    return;
	initialized = True;
    }
    if (!ReadMacroString(window, indentMacros->initMacro,
	    "smart indent initialization macro"))
    	return;
    
    /* Compile the newline and modify macros and attach them to the window */
    winData = (windowSmartIndentData *)XtMalloc(sizeof(windowSmartIndentData));
    winData->newlineMacro = ParseMacro(indentMacros->newlineMacro, &errMsg,
    	    &stoppedAt);
    if (winData->newlineMacro == NULL) {
    	ParseError(window->shell, indentMacros->newlineMacro, stoppedAt,
    	    	"newline macro", errMsg);
    	return;
    }
    if (indentMacros->modMacro == NULL)
    	winData->modMacro = NULL;
    else {
    	winData->modMacro = ParseMacro(indentMacros->modMacro, &errMsg,
    	    	&stoppedAt);
    	if (winData->modMacro == NULL) {
    	    ParseError(window->shell, indentMacros->modMacro, stoppedAt,
    	    	    "smart indent modify macro", errMsg);
    	    return;
    	}
    }
    window->smartIndentData = (void *)winData;
}

void EndSmartIndent(WindowInfo *window)
{
    windowSmartIndentData *winData =
    	    (windowSmartIndentData *)window->smartIndentData;
    
    if (winData == NULL)
    	return;

    /* Free programs and allocated data */
    if (winData->modMacro != NULL)
    	FreeProgram(winData->modMacro);
    FreeProgram(winData->newlineMacro);
    XtFree((char *)winData);
    window->smartIndentData = NULL;
}

/*
** Returns true if there are smart indent macros for a named language
*/
int SmartIndentMacrosAvailable(char *languageModeName)
{
    return findIndentSpec(languageModeName) != NULL;
}

/*
** Attaches to the text widget's smart-indent callback to invoke a user
** defined macro when the text widget requires an indent (not just when the
** user types a newline, but also when the widget does an auto-wrap with
** auto-indent on), or the user types some other character.
*/
void SmartIndentCB(Widget w, XtPointer clientData, XtPointer callData) 
{
    WindowInfo *window = (WindowInfo *)clientData;
    smartIndentCBStruct *cbInfo = (smartIndentCBStruct *)callData;
    
    if (window->smartIndentData == NULL)
    	return;
    if (cbInfo->reason == CHAR_TYPED)
	executeModMacro(window, cbInfo);
    else if (cbInfo->reason == NEWLINE_INDENT_NEEDED)
	executeNewlineMacro(window, cbInfo);
}

/*
** Run the newline macro with information from the smart-indent callback
** structure passed by the widget
*/
static void executeNewlineMacro(WindowInfo *window, smartIndentCBStruct *cbInfo)
{
    windowSmartIndentData *winData =
    	    (windowSmartIndentData *)window->smartIndentData;
    static DataValue posValue = {INT_TAG, {0}};
    DataValue result;
    RestartData *continuation;
    char *errMsg;
    int stat;
   
    /* Call newline macro with the position at which to add newline/indent */
    posValue.val.n = cbInfo->pos;
    stat = ExecuteMacro(window, winData->newlineMacro, 1, &posValue, &result,
    	    &continuation, &errMsg);
    
    /* Don't allow preemption or time limit.  Must get return value */
    while (stat == MACRO_TIME_LIMIT)
    	stat = ContinueMacro(continuation, &result, &errMsg);
    
    /* Collect Garbage.  Note that the mod macro does not collect garbage,
       (because collecting per-line is more efficient than per-character)
       but GC now depends on the newline macro being mandatory */
    SafeGC();
    
    /* Process errors in macro execution */
    if (stat == MACRO_PREEMPT || stat == MACRO_ERROR) {
    	DialogF(DF_ERR, window->shell, 1, "Error in smart indent macro:\n%s",
    	    	"Dismiss", stat == MACRO_ERROR ? errMsg :
    	    	"dialogs and shell commands not permitted");
    	EndSmartIndent(window);
    	return;
    }
    	
    /* Validate and return the result */
    if (result.tag != INT_TAG || result.val.n < -1 || result.val.n > 1000) {
    	DialogF(DF_ERR, window->shell, 1,
    	    	"Smart indent macros must return\ninteger indent distance",
    	    	"Dismiss");
    	EndSmartIndent(window);
    	return;
    }
    cbInfo->indentRequest = result.val.n;
}

/*
** Run the modification macro with information from the smart-indent callback
** structure passed by the widget
*/
static void executeModMacro(WindowInfo *window,smartIndentCBStruct *cbInfo)
{
    windowSmartIndentData *winData =
    	    (windowSmartIndentData *)window->smartIndentData;
    static DataValue args[2] = {{INT_TAG, {0}}, {STRING_TAG, {0}}};
    static int inModCB = False;
    DataValue result;
    RestartData *continuation;
    char *errMsg;
    int stat;
    
    /* Check for inappropriate calls and prevent re-entering if the macro
       makes a buffer modification */
    if (winData == NULL || winData->modMacro == NULL || inModCB)
    	return;
	
    /* Call modification macro with the position of the modification,
       and the character(s) inserted.  Don't allow
       preemption or time limit.  Execution must not overlap or re-enter */
    args[0].val.n = cbInfo->pos;
    args[1].val.str = AllocString(strlen(cbInfo->charsTyped) + 1);
    strcpy(args[1].val.str, cbInfo->charsTyped);
    inModCB = True;
	stat = ExecuteMacro(window, winData->modMacro, 3, args, &result,
    		&continuation, &errMsg);
	while (stat == MACRO_TIME_LIMIT)
    	    stat = ContinueMacro(continuation, &result, &errMsg);
    inModCB = False;
    
    /* Process errors in macro execution */
    if (stat == MACRO_PREEMPT || stat == MACRO_ERROR) {
    	DialogF(DF_ERR, window->shell, 1,
    	    	"Error in smart indent modification macro:\n%s", "Dismiss",
    	    	stat == MACRO_ERROR ? errMsg :
    	    	"dialogs and shell commands not permitted");
    	EndSmartIndent(window);
    	return;
    }
}

void EditSmartIndentMacros(WindowInfo *window)
{
#define BORDER 4
    Widget form, lmOptMenu, lmLbl, lmForm, lmBtn;
    Widget okBtn, applyBtn, checkBtn, deleteBtn, commonBtn;
    Widget dismissBtn, helpBtn, restoreBtn, pane;
    Widget initForm, newlineForm, modifyForm;
    Widget initLbl, newlineLbl, modifyLbl;
    XmString s1;
    char *lmName;
    Arg args[20];
    int n;

    /* if the dialog is already displayed, just pop it to the top and return */
    if (SmartIndentDialog.shell != NULL) {
    	RaiseShellWindow(SmartIndentDialog.shell);
    	return;
    }
    
    if (LanguageModeName(0) == NULL) {
    	DialogF(DF_WARN, window->shell, 1, "No Language Modes defined",
		"Dismiss");
    	return;
    }
    
    /* Decide on an initial language mode */
    lmName = LanguageModeName(window->languageMode == PLAIN_LANGUAGE_MODE ? 0 :
    	    window->languageMode);
    SmartIndentDialog.langModeName = CopyAllocatedString(lmName);

    /* Create a form widget in an application shell */
    n = 0;
    XtSetArg(args[n], XmNdeleteResponse, XmDO_NOTHING); n++;
    XtSetArg(args[n], XmNiconName, "Smart Indent Macros"); n++;
    XtSetArg(args[n], XmNtitle, "Smart Indent Macros"); n++;
    SmartIndentDialog.shell = XtAppCreateShell(APP_NAME, APP_CLASS,
	    applicationShellWidgetClass, TheDisplay, args, n);
    AddSmallIcon(SmartIndentDialog.shell);
    form = XtVaCreateManagedWidget("editSmartIndentMacros", xmFormWidgetClass,
	    SmartIndentDialog.shell, XmNautoUnmanage, False,
	    XmNresizePolicy, XmRESIZE_NONE, 0);
    XtAddCallback(form, XmNdestroyCallback, destroyCB, NULL);
    AddMotifCloseCallback(SmartIndentDialog.shell, dismissCB, NULL);
       
    lmForm = XtVaCreateManagedWidget("lmForm", xmFormWidgetClass,
    	    form,
	    XmNleftAttachment, XmATTACH_POSITION,
	    XmNleftPosition, 1,
	    XmNtopAttachment, XmATTACH_POSITION,
	    XmNtopPosition, 1,
	    XmNrightAttachment, XmATTACH_POSITION,
	    XmNrightPosition, 99, 0);
 
    SmartIndentDialog.lmPulldown = CreateLanguageModeMenu(lmForm, langModeCB,
    	    NULL);
    n = 0;
    XtSetArg(args[n], XmNspacing, 0); n++;
    XtSetArg(args[n], XmNmarginWidth, 0); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNleftPosition, 50); n++;
    XtSetArg(args[n], XmNsubMenuId, SmartIndentDialog.lmPulldown); n++;
    lmOptMenu = XmCreateOptionMenu(lmForm, "langModeOptMenu", args, n);
    XtManageChild(lmOptMenu);
    SmartIndentDialog.lmOptMenu = lmOptMenu;
    
    lmLbl = XtVaCreateManagedWidget("lmLbl", xmLabelGadgetClass, lmForm,
    	    XmNlabelString, s1=XmStringCreateSimple("Language Mode:"),
    	    XmNmnemonic, 'L',
    	    XmNuserData, XtParent(SmartIndentDialog.lmOptMenu),
    	    XmNalignment, XmALIGNMENT_END,
	    XmNrightAttachment, XmATTACH_POSITION,
	    XmNrightPosition, 50,
	    XmNtopAttachment, XmATTACH_FORM,
	    XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET,
	    XmNbottomWidget, lmOptMenu, 0);
    XmStringFree(s1);
    
    lmBtn = XtVaCreateManagedWidget("lmBtn", xmPushButtonWidgetClass, lmForm,
    	    XmNlabelString, s1=MKSTRING("Add / Modify\nLanguage Mode..."),
    	    XmNmnemonic, 'A',
    	    XmNrightAttachment, XmATTACH_FORM,
    	    XmNtopAttachment, XmATTACH_FORM, 0);
    XtAddCallback(lmBtn, XmNactivateCallback, lmDialogCB, NULL);
    XmStringFree(s1);
    
    commonBtn = XtVaCreateManagedWidget("commonBtn", xmPushButtonWidgetClass,
    	    lmForm,
    	    XmNlabelString, s1=MKSTRING("Common / Shared\nInitialization..."),
    	    XmNmnemonic, 'C',
    	    XmNleftAttachment, XmATTACH_FORM,
    	    XmNtopAttachment, XmATTACH_FORM, 0);
    XtAddCallback(commonBtn, XmNactivateCallback, commonDialogCB, NULL);
    XmStringFree(s1);
    
    okBtn = XtVaCreateManagedWidget("ok", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("OK"),
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 1,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 13,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(okBtn, XmNactivateCallback, okCB, NULL);
    XmStringFree(s1);
    
    applyBtn = XtVaCreateManagedWidget("apply", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("Apply"),
    	    XmNmnemonic, 'y',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 13,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 26,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(applyBtn, XmNactivateCallback, applyCB, NULL);
    XmStringFree(s1);
    
    checkBtn = XtVaCreateManagedWidget("check", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("Check"),
    	    XmNmnemonic, 'k',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 26,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 39,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(checkBtn, XmNactivateCallback, checkCB, NULL);
    XmStringFree(s1);
    
    deleteBtn = XtVaCreateManagedWidget("delete", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("Delete"),
    	    XmNmnemonic, 'D',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 39,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 52,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(deleteBtn, XmNactivateCallback, deleteCB, NULL);
    XmStringFree(s1);
    
    restoreBtn = XtVaCreateManagedWidget("restore", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("Restore Defaults"),
    	    XmNmnemonic, 'f',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 52,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 73,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(restoreBtn, XmNactivateCallback, restoreCB, NULL);
    XmStringFree(s1);
    
    dismissBtn = XtVaCreateManagedWidget("dismiss", xmPushButtonWidgetClass,
    	    form,
    	    XmNlabelString, s1=XmStringCreateSimple("Dismiss"),
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 73,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 86,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(dismissBtn, XmNactivateCallback, dismissCB, NULL);
    XmStringFree(s1);
    
    helpBtn = XtVaCreateManagedWidget("help", xmPushButtonWidgetClass,
    	    form,
    	    XmNlabelString, s1=XmStringCreateSimple("Help"),
    	    XmNmnemonic, 'H',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 86,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 99,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, BORDER, 0);
    XtAddCallback(helpBtn, XmNactivateCallback, helpCB, NULL);
    XmStringFree(s1);
    
    pane = XtVaCreateManagedWidget("pane", xmPanedWindowWidgetClass,  form,
   	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 1,
   	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 99,
	    XmNtopAttachment, XmATTACH_WIDGET,
	    XmNtopWidget, lmForm,
	    XmNbottomAttachment, XmATTACH_WIDGET,
	    XmNbottomWidget, okBtn, 0);
     	    /* XmNmarginWidth, 0, XmNmarginHeight, 0, XmNseparatorOn, False,
    	    XmNspacing, 3, XmNsashIndent, -2, */

    initForm = XtVaCreateManagedWidget("initForm", xmFormWidgetClass,
	    pane, 0);
    initLbl = XtVaCreateManagedWidget("initLbl", xmLabelGadgetClass, initForm,
    	    XmNlabelString, s1=XmStringCreateSimple(
    	     "Language Specific Initialization Macro Commands and Definitions"),
    	    XmNmnemonic, 'I', 0);
    XmStringFree(s1);
    n = 0;
    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
    XtSetArg(args[n], XmNrows, 5); n++;
    XtSetArg(args[n], XmNcolumns, 80); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, initLbl); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    SmartIndentDialog.initMacro = XmCreateScrolledText(initForm,
    	    "initMacro", args, n);
    XtManageChild(SmartIndentDialog.initMacro);
    RemapDeleteKey(SmartIndentDialog.initMacro);
    XtVaSetValues(initLbl, XmNuserData, SmartIndentDialog.initMacro, 0);

    newlineForm = XtVaCreateManagedWidget("newlineForm", xmFormWidgetClass,
	    pane, 0);
    newlineLbl = XtVaCreateManagedWidget("newlineLbl", xmLabelGadgetClass,
    	    newlineForm,
    	    XmNlabelString, s1=XmStringCreateSimple("Newline Macro"),
    	    XmNmnemonic, 'N', 0);
    XmStringFree(s1);
    XtVaCreateManagedWidget("newlineArgsLbl", xmLabelGadgetClass,
    	    newlineForm, XmNalignment, XmALIGNMENT_END,
    	    XmNlabelString, s1=XmStringCreateSimple(
	       "($1 is insert position, return indent request or -1)"),
	    XmNrightAttachment, XmATTACH_FORM, 0);
    XmStringFree(s1);
    n = 0;
    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
    XtSetArg(args[n], XmNrows, 5); n++;
    XtSetArg(args[n], XmNcolumns, 80); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, newlineLbl); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    SmartIndentDialog.newlineMacro = XmCreateScrolledText(newlineForm,
    	    "newlineMacro", args, n);
    XtManageChild(SmartIndentDialog.newlineMacro);
    RemapDeleteKey(SmartIndentDialog.newlineMacro);
    XtVaSetValues(newlineLbl, XmNuserData, SmartIndentDialog.newlineMacro, 0);

    modifyForm = XtVaCreateManagedWidget("modifyForm", xmFormWidgetClass,
	    pane, 0);
    modifyLbl = XtVaCreateManagedWidget("modifyLbl", xmLabelGadgetClass,
    	    modifyForm, XmNlabelString,s1=XmStringCreateSimple("Type-in Macro"),
    	    XmNmnemonic, 'M', 0);
    XmStringFree(s1);
    XtVaCreateManagedWidget("modifyArgsLbl", xmLabelGadgetClass,
    	    modifyForm, XmNalignment, XmALIGNMENT_END,
    	    XmNlabelString, s1=XmStringCreateSimple(
	        "($1 is position, $2 is character just inserted)"),
	    XmNrightAttachment, XmATTACH_FORM, 0);
    XmStringFree(s1);
    n = 0;
    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
    XtSetArg(args[n], XmNrows, 5); n++;
    XtSetArg(args[n], XmNcolumns, 80); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, modifyLbl); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    SmartIndentDialog.modMacro = XmCreateScrolledText(modifyForm,
    	    "modifyMacro", args, n);
    XtManageChild(SmartIndentDialog.modMacro);
    RemapDeleteKey(SmartIndentDialog.modMacro);
    XtVaSetValues(modifyLbl, XmNuserData, SmartIndentDialog.modMacro, 0);

    /* Set initial default button */
    XtVaSetValues(form, XmNdefaultButton, okBtn, 0);
    XtVaSetValues(form, XmNcancelButton, dismissBtn, 0);
    
    /* Handle mnemonic selection of buttons and focus to dialog */
    AddDialogMnemonicHandler(form);
    
    /* Fill in the dialog information for the selected language mode */
    setSmartIndentDialogData(findIndentSpec(lmName));
    SetLangModeMenu(SmartIndentDialog.lmOptMenu,SmartIndentDialog.langModeName);
    
    /* Realize all of the widgets in the new dialog */
    XtRealizeWidget(SmartIndentDialog.shell);
}

static void destroyCB(Widget w, XtPointer clientData, XtPointer callData)
{
    XtFree(SmartIndentDialog.langModeName);
    SmartIndentDialog.shell = NULL;
}

static void langModeCB(Widget w, XtPointer clientData, XtPointer callData)
{
    char *modeName;
    int i, resp;
    static smartIndentRec emptyIndentSpec = {NULL, NULL, NULL, NULL};
    smartIndentRec *oldMacros, *newMacros;
	    
    /* Get the newly selected mode name.  If it's the same, do nothing */
    XtVaGetValues(w, XmNuserData, &modeName, 0);
    if (!strcmp(modeName, SmartIndentDialog.langModeName))
    	return;

    /* Find the original macros */
    for (i=0; i<NSmartIndentSpecs; i++)
    	if (!strcmp(SmartIndentDialog.langModeName,SmartIndentSpecs[i]->lmName))
	    break;
    oldMacros = i == NSmartIndentSpecs ? &emptyIndentSpec : SmartIndentSpecs[i];
    
    /* Check if the macros have changed, if so allow user to apply, discard,
       or cancel */
    newMacros = getSmartIndentDialogData();
    if (indentSpecsDiffer(oldMacros, newMacros)) {
	resp = DialogF(DF_QUES, SmartIndentDialog.shell, 3,
      "Smart indent macros for language mode\n%s were changed.  Apply changes?",
		"Apply", "Discard", "Cancel", oldMacros->lmName);
	if (resp == 3) {
	    SetLangModeMenu(SmartIndentDialog.lmOptMenu,
		    SmartIndentDialog.langModeName);
	    return;
    	} else if (resp == 1) {
	    if (checkSmartIndentDialogData()) {
		if (oldMacros == &emptyIndentSpec) {
		    SmartIndentSpecs[NSmartIndentSpecs++] =
		    	    copyIndentSpec(newMacros);
	    	} else {
		    freeIndentSpec(oldMacros);
		    SmartIndentSpecs[i] = copyIndentSpec(newMacros);
		}
	    } else {
     	    	SetLangModeMenu(SmartIndentDialog.lmOptMenu,
		    	SmartIndentDialog.langModeName);
		return;
	    }
     	}
    }
    freeIndentSpec(newMacros);
    
    /* Fill the dialog with the new language mode information */
    SmartIndentDialog.langModeName = CopyAllocatedString(modeName);
    setSmartIndentDialogData(findIndentSpec(modeName));
}

static void lmDialogCB(Widget w, XtPointer clientData, XtPointer callData)
{
    EditLanguageModes(SmartIndentDialog.shell);
}

static void commonDialogCB(Widget w, XtPointer clientData, XtPointer callData)
{
    EditCommonSmartIndentMacro();
}

static void okCB(Widget w, XtPointer clientData, XtPointer callData)
{
    /* change the macro */
    if (!updateSmartIndentData())
    	return;
    
    /* pop down and destroy the dialog */
    XtDestroyWidget(SmartIndentDialog.shell);
}

static void applyCB(Widget w, XtPointer clientData, XtPointer callData)
{
    /* change the patterns */
    updateSmartIndentData();
}
	
static void checkCB(Widget w, XtPointer clientData, XtPointer callData)
{
    if (checkSmartIndentDialogData())
	DialogF(DF_INF, SmartIndentDialog.shell, 1,
    		"Macros compiled without error", "Dismiss");
}
	
static void restoreCB(Widget w, XtPointer clientData, XtPointer callData)
{
   int i;
   smartIndentRec *defaultIS;
    
    /* Find the default indent spec */
    for (i=0; i<N_DEFAULT_INDENT_SPECS; i++)
    	if (!strcmp(SmartIndentDialog.langModeName,
		DefaultIndentSpecs[i].lmName))
	    break;
    if (i == N_DEFAULT_INDENT_SPECS) {
    	DialogF(DF_WARN, SmartIndentDialog.shell, 1,
 		"There are no default indent macros\nfor language mode %s",
 		"Dismiss", SmartIndentDialog.langModeName);
    	return;
    }
    defaultIS = &DefaultIndentSpecs[i];
    
    if (DialogF(DF_WARN, SmartIndentDialog.shell, 2,
"Are you sure you want to discard\n\
all changes to smart indent macros\n\
for language mode %s?", "Discard", "Cancel",
	    SmartIndentDialog.langModeName) == 2)
    	return;
    
    /* if a stored version of the indent macros exist, replace them, if not,
       add a new one */
    for (i=0; i<NSmartIndentSpecs; i++)
    	if (!strcmp(SmartIndentDialog.langModeName,SmartIndentSpecs[i]->lmName))
	    break;
    if (i < NSmartIndentSpecs) {
     	freeIndentSpec(SmartIndentSpecs[i]);
   	SmartIndentSpecs[i] = copyIndentSpec(defaultIS);
    } else
    	SmartIndentSpecs[NSmartIndentSpecs++] = copyIndentSpec(defaultIS);
   
    /* Update the dialog */
    setSmartIndentDialogData(defaultIS);
}
	
static void deleteCB(Widget w, XtPointer clientData, XtPointer callData)
{
    int i;
    
    if (DialogF(DF_WARN, SmartIndentDialog.shell, 2,
"Are you sure you want to delete smart indent\n\
macros for language mode %s?", "Yes, Delete", "Cancel",
    	    SmartIndentDialog.langModeName) == 2)
    	return;
    /* if a stored version of the pattern set exists, delete it from the list */
    for (i=0; i<NSmartIndentSpecs; i++)
    	if (!strcmp(SmartIndentDialog.langModeName,SmartIndentSpecs[i]->lmName))
	    break;
    if (i < NSmartIndentSpecs) {
     	freeIndentSpec(SmartIndentSpecs[i]);
   	memmove(&SmartIndentSpecs[i], &SmartIndentSpecs[i+1],
   	    	(NSmartIndentSpecs-1 - i) * sizeof(smartIndentRec *));
    	NSmartIndentSpecs--;
    }
    
    /* Clear out the dialog */
    setSmartIndentDialogData(NULL);
}

static void dismissCB(Widget w, XtPointer clientData, XtPointer callData)
{
    /* pop down and destroy the dialog */
    XtDestroyWidget(SmartIndentDialog.shell);
}

static void helpCB(Widget w, XtPointer clientData, XtPointer callData)
{
    Help(SmartIndentDialog.shell, HELP_SMART_INDENT);
}

static int checkSmartIndentDialogData(void)
{
    char *widgetText, *errMsg, *stoppedAt;
    Program *prog;
    
    /* Check the initialization macro */
    if (!TextWidgetIsBlank(SmartIndentDialog.initMacro)) {
	widgetText =ensureNewline(XmTextGetString(SmartIndentDialog.initMacro));
	if (!CheckMacroString(SmartIndentDialog.shell, widgetText,
		"initialization macro", &stoppedAt)) {
    	    XmTextSetInsertionPosition(SmartIndentDialog.initMacro,
		    stoppedAt - widgetText);
	    XmProcessTraversal(SmartIndentDialog.initMacro, XmTRAVERSE_CURRENT);
	    XtFree(widgetText);
	    return False;
	}
	XtFree(widgetText);
    }
    
    /* Test compile the newline macro */
    if (TextWidgetIsBlank(SmartIndentDialog.newlineMacro)) {
    	DialogF(DF_WARN, SmartIndentDialog.shell, 1, "Newline macro required",
    	    	"Dismiss");
    	return False;
    }
    widgetText = ensureNewline(XmTextGetString(SmartIndentDialog.newlineMacro));
    prog = ParseMacro(widgetText, &errMsg, &stoppedAt);
    if (prog == NULL) {
 	ParseError(SmartIndentDialog.shell, widgetText, stoppedAt,
    	    	"newline macro", errMsg);
     	XmTextSetInsertionPosition(SmartIndentDialog.newlineMacro,
		stoppedAt - widgetText);
	XmProcessTraversal(SmartIndentDialog.newlineMacro, XmTRAVERSE_CURRENT);
  	XtFree(widgetText);
    	return False;
    }
    XtFree(widgetText);
    FreeProgram(prog);
    
    /* Test compile the modify macro */
    if (!TextWidgetIsBlank(SmartIndentDialog.modMacro)) {
    	widgetText = ensureNewline(XmTextGetString(SmartIndentDialog.modMacro));
    	prog = ParseMacro(widgetText, &errMsg, &stoppedAt);
	if (prog == NULL) {
    	    ParseError(SmartIndentDialog.shell, widgetText, stoppedAt,
    	    	    "modify macro", errMsg);
     	    XmTextSetInsertionPosition(SmartIndentDialog.modMacro,
		    stoppedAt - widgetText);
	    XmProcessTraversal(SmartIndentDialog.modMacro, XmTRAVERSE_CURRENT);
    	    XtFree(widgetText);
    	    return False;
    	}
    	XtFree(widgetText);
	FreeProgram(prog);
    }
    return True;
}

static smartIndentRec *getSmartIndentDialogData(void)
{
    smartIndentRec *is;
    
    is = (smartIndentRec *)XtMalloc(sizeof(smartIndentRec));
    is->lmName = CopyAllocatedString(SmartIndentDialog.langModeName);
    is->initMacro = TextWidgetIsBlank(SmartIndentDialog.initMacro) ? NULL :
	    ensureNewline(XmTextGetString(SmartIndentDialog.initMacro));
    is->newlineMacro = TextWidgetIsBlank(SmartIndentDialog.newlineMacro) ? NULL:
	    ensureNewline(XmTextGetString(SmartIndentDialog.newlineMacro));
    is->modMacro = TextWidgetIsBlank(SmartIndentDialog.modMacro) ? NULL :
	    ensureNewline(XmTextGetString(SmartIndentDialog.modMacro));
    return is;
}

static void setSmartIndentDialogData(smartIndentRec *is)
{
    if (is == NULL) {
	XmTextSetString(SmartIndentDialog.initMacro, "");
	XmTextSetString(SmartIndentDialog.newlineMacro, "");
	XmTextSetString(SmartIndentDialog.modMacro, "");
    } else {
	if (is->initMacro == NULL)
	    XmTextSetString(SmartIndentDialog.initMacro, "");
	else
	    XmTextSetString(SmartIndentDialog.initMacro, is->initMacro);
	XmTextSetString(SmartIndentDialog.newlineMacro, is->newlineMacro);
	if (is->modMacro == NULL)
	    XmTextSetString(SmartIndentDialog.modMacro, "");
	else
	    XmTextSetString(SmartIndentDialog.modMacro, is->modMacro);
    }
}

void EditCommonSmartIndentMacro(void)
{
#define VERT_BORDER 4
    Widget form, topLbl;
    Widget okBtn, applyBtn, checkBtn;
    Widget dismissBtn, restoreBtn;
    XmString s1;
    Arg args[20];
    int n;

    /* if the dialog is already displayed, just pop it to the top and return */
    if (CommonDialog.shell != NULL) {
    	RaiseShellWindow(CommonDialog.shell);
    	return;
    }

    /* Create a form widget in an application shell */
    n = 0;
    XtSetArg(args[n], XmNdeleteResponse, XmDO_NOTHING); n++;
    XtSetArg(args[n], XmNiconName, "Common Smart Indent Macros"); n++;
    XtSetArg(args[n], XmNtitle, "Common Smart Indent Macros"); n++;
    CommonDialog.shell = XtAppCreateShell(APP_NAME, APP_CLASS,
	    applicationShellWidgetClass, TheDisplay, args, n);
    AddSmallIcon(CommonDialog.shell);
    form = XtVaCreateManagedWidget("editCommonSIMacros", xmFormWidgetClass,
	    CommonDialog.shell, XmNautoUnmanage, False,
	    XmNresizePolicy, XmRESIZE_NONE, 0);
    XtAddCallback(form, XmNdestroyCallback, comDestroyCB, NULL);
    AddMotifCloseCallback(CommonDialog.shell, comDismissCB, NULL);
    
    topLbl = XtVaCreateManagedWidget("topLbl", xmLabelGadgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple(
	        "Common Definitions for Smart Indent Macros"),
    	    XmNmnemonic, 'C',
	    XmNtopAttachment, XmATTACH_FORM,
	    XmNtopOffset, VERT_BORDER,
	    XmNleftAttachment, XmATTACH_POSITION,
	    XmNleftPosition, 1, 0);

    okBtn = XtVaCreateManagedWidget("ok", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("OK"),
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 6,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 18,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, VERT_BORDER, 0);
    XtAddCallback(okBtn, XmNactivateCallback, comOKCB, NULL);
    XmStringFree(s1);
    
    applyBtn = XtVaCreateManagedWidget("apply", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("Apply"),
    	    XmNmnemonic, 'y',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 22,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 35,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, VERT_BORDER, 0);
    XtAddCallback(applyBtn, XmNactivateCallback, comApplyCB, NULL);
    XmStringFree(s1);
    
    checkBtn = XtVaCreateManagedWidget("check", xmPushButtonWidgetClass, form,
    	    XmNlabelString, s1=XmStringCreateSimple("Check"),
    	    XmNmnemonic, 'k',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 39,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 52,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, VERT_BORDER, 0);
    XtAddCallback(checkBtn, XmNactivateCallback, comCheckCB, NULL);
    XmStringFree(s1);
    
    restoreBtn = XtVaCreateManagedWidget("restore", xmPushButtonWidgetClass,
    form,
    	    XmNlabelString, s1=XmStringCreateSimple("Restore Default"),
    	    XmNmnemonic, 'f',
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 56,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 77,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, VERT_BORDER, 0);
    XtAddCallback(restoreBtn, XmNactivateCallback, comRestoreCB, NULL);
    XmStringFree(s1);
    
    dismissBtn = XtVaCreateManagedWidget("dismiss", xmPushButtonWidgetClass,
    	    form,
    	    XmNlabelString, s1=XmStringCreateSimple("Dismiss"),
    	    XmNleftAttachment, XmATTACH_POSITION,
    	    XmNleftPosition, 81,
    	    XmNrightAttachment, XmATTACH_POSITION,
    	    XmNrightPosition, 94,
    	    XmNbottomAttachment, XmATTACH_FORM,
    	    XmNbottomOffset, VERT_BORDER, 0);
    XtAddCallback(dismissBtn, XmNactivateCallback, comDismissCB, NULL);
    XmStringFree(s1);
    
    n = 0;
    XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
    XtSetArg(args[n], XmNrows, 24); n++;
    XtSetArg(args[n], XmNcolumns, 80); n++;
    XtSetArg(args[n], XmNvalue, CommonMacros); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, topLbl); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNleftPosition, 1); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition, 99); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNbottomWidget, okBtn); n++;
    XtSetArg(args[n], XmNbottomOffset, VERT_BORDER); n++;
    CommonDialog.text = XmCreateScrolledText(form, "commonText", args, n);
    XtManageChild(CommonDialog.text);
    RemapDeleteKey(CommonDialog.text);
    XtVaSetValues(topLbl, XmNuserData, CommonDialog.text, 0);

    /* Set initial default button */
    XtVaSetValues(form, XmNdefaultButton, okBtn, 0);
    XtVaSetValues(form, XmNcancelButton, dismissBtn, 0);
    
    /* Handle mnemonic selection of buttons and focus to dialog */
    AddDialogMnemonicHandler(form);
    
    /* Realize all of the widgets in the new dialog */
    XtRealizeWidget(CommonDialog.shell);
}

static void comDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
{
    CommonDialog.shell = NULL;
}

static void comOKCB(Widget w, XtPointer clientData, XtPointer callData)
{
    /* change the macro */
    if (!updateSmartIndentCommonData())
    	return;
    
    /* pop down and destroy the dialog */
    XtDestroyWidget(CommonDialog.shell);
}

static void comApplyCB(Widget w, XtPointer clientData, XtPointer callData)
{
    /* change the macro */
    updateSmartIndentCommonData();
}
	
static void comCheckCB(Widget w, XtPointer clientData, XtPointer callData)
{
    if (checkSmartIndentCommonDialogData())
	DialogF(DF_INF, CommonDialog.shell, 1,
    		"Macros compiled without error", "Dismiss");
}
	
static void comRestoreCB(Widget w, XtPointer clientData, XtPointer callData)
{
    if (DialogF(DF_WARN, CommonDialog.shell, 2,
"Are you sure you want to discard all\n\
changes to common smart indent macros", "Discard", "Cancel") == 2)
    	return;
    
    /* replace common macros with default */
    if (CommonMacros != NULL)
    	XtFree(CommonMacros);
    CommonMacros = XtNewString(DefaultCommonMacros);
   
    /* Update the dialog */
    XmTextSetString(CommonDialog.text, CommonMacros);
}

static void comDismissCB(Widget w, XtPointer clientData, XtPointer callData)
{
    /* pop down and destroy the dialog */
    XtDestroyWidget(CommonDialog.shell);
}

/*
** Update the smart indent macros being edited in the dialog
** with the information that the dialog is currently displaying, and
** apply changes to any window which is currently using the macros.
*/
static int updateSmartIndentCommonData(void)
{
    WindowInfo *window;
    	
    /* Make sure the patterns are valid and compile */
    if (!checkSmartIndentCommonDialogData())
    	return False;
    
    /* Get the current data */
    CommonMacros = ensureNewline(XmTextGetString(CommonDialog.text));
    
    /* Re-execute initialization macros (macros require a window to function,
       since user could theoretically execute an action routine, but it
       probably won't be referenced in a smart indent initialization) */
    if (!ReadMacroString(WindowList, CommonMacros, "common macros"))
    	return False;

    /* Find windows that are currently using smart indent and
       re-initialize the smart indent macros (in case they have initialization
       data which depends on common data) */
    for (window=WindowList; window!=NULL; window=window->next) {
    	if (window->indentStyle == SMART_INDENT &&
    		window->languageMode != PLAIN_LANGUAGE_MODE) {
    	    EndSmartIndent(window);
    	    BeginSmartIndent(window, False);
    	}
    }
    
    /* Note that preferences have been changed */
    MarkPrefsChanged();

    return True;
}

static int checkSmartIndentCommonDialogData(void)
{
    char *widgetText, *stoppedAt;
    
    if (!TextWidgetIsBlank(CommonDialog.text)) {
	widgetText = ensureNewline(XmTextGetString(CommonDialog.text));
	if (!CheckMacroString(CommonDialog.shell, widgetText,
		"macros", &stoppedAt)) {
    	    XmTextSetInsertionPosition(CommonDialog.text, stoppedAt-widgetText);
	    XmProcessTraversal(CommonDialog.text, XmTRAVERSE_CURRENT);
	    XtFree(widgetText);
	    return False;
	}
	XtFree(widgetText);
    }
    return True;
}

/*
** Update the smart indent macros being edited in the dialog
** with the information that the dialog is currently displaying, and
** apply changes to any window which is currently using the macros.
*/
static int updateSmartIndentData(void)
{
    smartIndentRec *newMacros;
    WindowInfo *window;
    char *lmName;
    int i;
    	
    /* Make sure the patterns are valid and compile */
    if (!checkSmartIndentDialogData())
    	return False;
    
    /* Get the current data */
    newMacros = getSmartIndentDialogData();
    
    /* Find the original macros */
    for (i=0; i<NSmartIndentSpecs; i++)
    	if (!strcmp(SmartIndentDialog.langModeName,SmartIndentSpecs[i]->lmName))
	    break;
    
    /* If it's a new language, add it at the end, otherwise free the
       existing macros and replace it */
    if (i == NSmartIndentSpecs) {
    	SmartIndentSpecs[NSmartIndentSpecs++] = newMacros;
    } else {
	freeIndentSpec(SmartIndentSpecs[i]);
	SmartIndentSpecs[i] = newMacros;
    }
    
    /* Find windows that are currently using this indent specification and
       re-do the smart indent macros */
    for (window=WindowList; window!=NULL; window=window->next) {
    	lmName = LanguageModeName(window->languageMode);
	if (lmName != NULL && !strcmp(lmName, newMacros->lmName)) {
	    XtSetSensitive(window->smartIndentItem, True);
    	    if (window->indentStyle == SMART_INDENT &&
    		    window->languageMode != PLAIN_LANGUAGE_MODE) {
    	    	EndSmartIndent(window);
    	    	BeginSmartIndent(window, False);
    	    }
    	}
    }
    
    /* Note that preferences have been changed */
    MarkPrefsChanged();

    return True;
}

static int loadDefaultIndentSpec(char *lmName)
{
    int i;
    
    for (i=0; i<N_DEFAULT_INDENT_SPECS; i++) {
    	if (!strcmp(lmName, DefaultIndentSpecs[i].lmName)) {
    	    SmartIndentSpecs[NSmartIndentSpecs++] =
		    copyIndentSpec(&DefaultIndentSpecs[i]);
    	    return True;
    	}
    }
    return False;
}

int LoadSmartIndentString(char *inString)
{
   char *errMsg, *macroStart, *inPtr = inString;
   smartIndentRec is, *isCopy;
   int i;

   for (;;) {
   	
	/* skip over blank space */
	inPtr += strspn(inPtr, " \t\n");
	
	/* finished */
	if (*inPtr == '\0')
	    return True;

	/* read language mode name */
	is.lmName = ReadSymbolicField(&inPtr);
	if (is.lmName == NULL)
    	    return siParseError(inString, inPtr, "language mode name required");
	if (!SkipDelimiter(&inPtr, &errMsg)) {
    	    XtFree(is.lmName);
    	    return siParseError(inString, inPtr, errMsg);
    	}
    	
	/* look for "Default" keyword, and if it's there, return the default
	   smart indent macros */
	if (!strncmp(inPtr, "Default", 7)) {
    	    inPtr += 7;
    	    if (!loadDefaultIndentSpec(is.lmName)) {
    		XtFree(is.lmName);
    		return siParseError(inString, inPtr,
    	    		"no default smart indent macros");
    	    }
    	    XtFree(is.lmName);
    	    continue;
	}

	/* read the initialization macro (arbitrary text terminated by the
	   macro end boundary string) */
	is.initMacro = readSIMacro(&inPtr);
	if (is.initMacro == NULL) {
    	    XtFree(is.lmName);
    	    return siParseError(inString, inPtr,
    	    	    "no end boundary to initialization macro");
	}
	
	/* read the newline macro */
	is.newlineMacro = readSIMacro(&inPtr);
	if (is.newlineMacro == NULL) {
    	    XtFree(is.lmName);
    	    XtFree(is.initMacro);
    	    return siParseError(inString, inPtr,
    	    	    "no end boundary to newline macro");
	}
	
	/* read the modify macro */
	macroStart = inPtr + strspn(inPtr, " \t\n");
	is.modMacro = readSIMacro(&inPtr);
	if (is.modMacro == NULL) {
    	    XtFree(is.lmName);
    	    XtFree(is.initMacro);
    	    XtFree(is.newlineMacro);
    	    return siParseError(inString, inPtr,
    	    	    "no end boundary to modify macro");
	}
	
	/* if there's no mod macro, make it null so it won't be executed */
	if (is.modMacro[0] == '\0') {
	    XtFree(is.modMacro);
            is.modMacro = NULL;
    	}
    	
    	/* create a new data structure and add/change it in the list */
	isCopy = (smartIndentRec *)XtMalloc(sizeof(smartIndentRec));
	*isCopy = is;
	for (i=0; i<NSmartIndentSpecs; i++) {
	    if (!strcmp(SmartIndentSpecs[i]->lmName, is.lmName)) {
		freeIndentSpec(SmartIndentSpecs[i]);
		SmartIndentSpecs[i] = isCopy;
		break;
	    }
	}
	if (i == NSmartIndentSpecs)
	    SmartIndentSpecs[NSmartIndentSpecs++] = isCopy;
    }
}

int LoadSmartIndentCommonString(char *inString)
{
    int shiftedLen;
    char *inPtr = inString;
    
    /* If called from -import, can replace existing ones */
    if (CommonMacros != NULL)
	XtFree(CommonMacros);
    
    /* skip over blank space */
    inPtr += strspn(inPtr, " \t\n");

    /* look for "Default" keyword, and if it's there, return the default
       smart common macro */
    if (!strncmp(inPtr, "Default", 7)) {
    	CommonMacros = XtNewString(DefaultCommonMacros);
	return True;
    }
        
    /* Remove leading tabs added by writer routine */
    CommonMacros = ShiftText(inPtr, SHIFT_LEFT, True, 8, 8, &shiftedLen);
    return True;
}

/*
** Read a macro (arbitrary text terminated by the macro end boundary string)
** from the position pointed to by *inPtr, trim off added tabs and return an
** allocated copy of the string, and advance *inPtr to the end of the macro.
** Returns NULL if the macro end boundary string is not found.
*/
static char *readSIMacro(char **inPtr)
{
    char *retStr, *macroStr, *macroEnd;
    int shiftedLen;
    
    /* Strip leading newline */
    if (**inPtr == '\n')
    	(*inPtr)++;
    
    /* Find the end of the macro */
    macroEnd = strstr(*inPtr, MacroEndBoundary);
    if (macroEnd == NULL)
	return NULL;
    
    /* Copy the macro */
    macroStr = XtMalloc(macroEnd - *inPtr + 1);
    strncpy(macroStr, *inPtr, macroEnd - *inPtr);
    macroStr[macroEnd - *inPtr] = '\0';
    
    /* Remove leading tabs added by writer routine */
    *inPtr = macroEnd + strlen(MacroEndBoundary);
    retStr = ShiftText(macroStr, SHIFT_LEFT, True, 8, 8, &shiftedLen);
    XtFree(macroStr);
    return retStr;
}

static smartIndentRec *copyIndentSpec(smartIndentRec *is)
{
    smartIndentRec *ris = (smartIndentRec *)XtMalloc(sizeof(smartIndentRec));
    ris->lmName = CopyAllocatedString(is->lmName);
    ris->initMacro = CopyAllocatedString(is->initMacro);
    ris->newlineMacro = CopyAllocatedString(is->newlineMacro);
    ris->modMacro = CopyAllocatedString(is->modMacro);
    return ris;
}

void freeIndentSpec(smartIndentRec *is)
{
    XtFree(is->lmName);
    if (is->initMacro != NULL) XtFree(is->initMacro);
    XtFree(is->newlineMacro);
    if (is->modMacro != NULL)XtFree(is->modMacro);
}

int indentSpecsDiffer(smartIndentRec *is1, smartIndentRec *is2)
{
    return AllocatedStringsDiffer(is1->initMacro, is2->initMacro) ||
	    AllocatedStringsDiffer(is1->newlineMacro, is2->newlineMacro) ||
	    AllocatedStringsDiffer(is1->modMacro, is2->modMacro);
}

static int siParseError(char *stringStart, char *stoppedAt, char *message)
{
    return ParseError(NULL, stringStart, stoppedAt,
    	    "smart indent specification", message);
}

char *WriteSmartIndentString(void)
{
    int i;
    smartIndentRec *sis;
    textBuffer *outBuf;
    char *outStr, *escapedStr;
    
    outBuf = BufCreate();
    for (i=0; i<NSmartIndentSpecs; i++) {
    	sis = SmartIndentSpecs[i];
    	BufInsert(outBuf, outBuf->length, "\t");
    	BufInsert(outBuf, outBuf->length, sis->lmName);
    	BufInsert(outBuf, outBuf->length, ":");
    	if (isDefaultIndentSpec(sis))
    	    BufInsert(outBuf, outBuf->length, "Default\n");
    	else {
    	    insertShiftedMacro(outBuf, sis->initMacro);
    	    insertShiftedMacro(outBuf, sis->newlineMacro);
    	    insertShiftedMacro(outBuf, sis->modMacro);
    	}
    }
    
    /* Get the output string, and lop off the trailing newline */
    outStr = BufGetRange(outBuf, 0, outBuf->length > 0 ? outBuf->length-1 : 0);
    BufFree(outBuf);
    
    /* Protect newlines and backslashes from translation by the resource
       reader */
    escapedStr = EscapeSensitiveChars(outStr);
    XtFree(outStr);
    return escapedStr;
}

char *WriteSmartIndentCommonString(void)
{
    int len;
    char *outStr, *escapedStr;
    
    if (!strcmp(CommonMacros, DefaultCommonMacros))
    	return XtNewString("Default");
    if (CommonMacros == NULL)
    	return XtNewString("");
    
    /* Shift the macro over by a tab to keep .nedit file bright and clean */
    outStr = ShiftText(CommonMacros, SHIFT_RIGHT, True, 8, 8, &len);
	
    /* Protect newlines and backslashes from translation by the resource
       reader */
    escapedStr = EscapeSensitiveChars(outStr);
    XtFree(outStr);
    
    /* If there's a trailing escaped newline, remove it */
    len = strlen(escapedStr);
    if (len > 1 && escapedStr[len-1] == '\n' && escapedStr[len-2] == '\\')
    	escapedStr[len-2] = '\0';
    return escapedStr;
}

/*
** Insert macro text "macro" into buffer "buf" shifted right by 8 characters
** (so it looks nice in the .nedit file), and terminated with a macro-end-
** boundary string.
*/
static void insertShiftedMacro(textBuffer *buf, char  *macro)
{
    char *shiftedMacro;
    int shiftedLen;
    
    if (macro != NULL) {
	shiftedMacro = ShiftText(macro, SHIFT_RIGHT, True, 8, 8, &shiftedLen);
	BufInsert(buf, buf->length, shiftedMacro);
	XtFree(shiftedMacro);
    }
    BufInsert(buf, buf->length, "\t");
    BufInsert(buf, buf->length, MacroEndBoundary);
    BufInsert(buf, buf->length, "\n");
}

static int isDefaultIndentSpec(smartIndentRec *indentSpec)
{
    int i;
   
    for (i=0; i<N_DEFAULT_INDENT_SPECS; i++)
    	if (!strcmp(indentSpec->lmName, DefaultIndentSpecs[i].lmName))
    	    return !indentSpecsDiffer(indentSpec, &DefaultIndentSpecs[i]);
    return False;
}
    
static smartIndentRec *findIndentSpec(char *modeName)
{
    int i;

    if (modeName == NULL)
    	return NULL;
    
    for (i=0; i<NSmartIndentSpecs; i++)
    	if (!strcmp(modeName, SmartIndentSpecs[i]->lmName))
    	    return SmartIndentSpecs[i];
    return NULL;
}

/*
** If "string" is not terminated with a newline character,  return a
** reallocated string which does end in a newline (otherwise, just pass on
** string as function value).  (The macro language requires newline terminators
** for statements, but the text widget doesn't force it like the NEdit text
** buffer does, so this might avoid some confusion.)
*/
static char *ensureNewline(char *string)
{
    char *newString;
    int length;
    
    if (string == NULL)
	return NULL;
    length = strlen(string);
    if (length == 0 || string[length-1] == '\n')
	return string;
    newString = XtMalloc(length + 2);
    strcpy(newString, string);
    newString[length] = '\n';
    newString[length+1] = '\0';
    XtFree(string);
    return newString;
}
