This is dmsyntax.c in view mode; [Download] [Up]
/* dmsyntax.c */
/* Copyright 1995 by Steve Kirkendall */
char id_dmsyntax[] = "$Id: dmsyntax.c,v 2.24 1996/09/21 01:21:36 steve Exp $";
#include "elvis.h"
#ifdef DISPLAY_SYNTAX
#if USE_PROTOTYPES
static CHAR *iskeyword(CHAR *word);
static void addkeyword(CHAR *word, _char_ font, BOOLEAN doesregexp, BOOLEAN doesregsub);
static CHAR **fetchline(void);
static DMINFO *init(WINDOW win);
static void term(DMINFO *info);
static MARK setup(MARK top, long cursor, MARK bottom, DMINFO *info);
static MARK image(WINDOW w, MARK line, DMINFO *info, void (*draw)(CHAR *p, long qty, _char_ font, long offset));
static CHAR *tagatcursor(WINDOW win, MARK cursor);
static MARK tagload(CHAR *tagname, MARK from);
#endif
/* These are the font names, used in the option descriptions below */
char fontnames[] = "normal bold emphasized italic underlined fixed";
/* These are the descriptions and values of some global options */
static OPTDESC globdesc[] =
{
{"commentfont","cfont", opt1string, optisoneof, fontnames },
{"stringfont","sfont", opt1string, optisoneof, fontnames },
{"keywordfont","kfont", opt1string, optisoneof, fontnames },
{"functionfont","ffont",opt1string, optisoneof, fontnames },
{"variablefont","vfont",opt1string, optisoneof, fontnames },
{"prepfont", "pfont", opt1string, optisoneof, fontnames },
{"otherfont", "ofont", opt1string, optisoneof, fontnames },
{"includepath", "inc", optsstring, optisstring }
};
static OPTVAL globval[QTY(globdesc)];
#define o_commentfont globval[0].value.character
#define o_stringfont globval[1].value.character
#define o_keywordfont globval[2].value.character
#define o_functionfont globval[3].value.character
#define o_variablefont globval[4].value.character
#define o_prepfont globval[5].value.character
#define o_otherfont globval[6].value.character
#define o_includepath globval[7].value.string
/* This data type is used to denote a token type. Values of this type will
* be used as indicies into the cfont[] array, below, to determine which
* font each language element should use. The last symbol in the list must
* be PUNCT, because the declaration of cfont[] depends on this.
*/
typedef enum
{
COMMENT, COMMENT2, STRING, CHARACTER, REGEXP, REGSUB, KEYWORD,
FIRSTPUNCT, SECONDPUNCT, FUNCTION, VARIABLE, PREP, PREPWORD, PREPQUOTE,
OTHER, PUNCT
} TOKENTYPE;
/* This structure stores information about the current language's syntax.
* Each window has its own copy of this, so different windows which happen
* to be in "syntax" mode can each display different languages.
*/
typedef struct
{
/* info about the current parsing state */
TOKENTYPE token; /* used during parsing */
/* info from the "elvis.syn" file */
CHAR **keyword[256]; /* hash table of keyword names */
CHAR function; /* character used for function calls */
CHAR strbegin; /* string start-quote character */
CHAR strend; /* string end-quote character */
CHAR charbegin; /* character quote character */
CHAR charend; /* character quote character */
CHAR preprocessor; /* first character of preprocessor directives */
CHAR pqbegin; /* preprocessor start-quote character */
CHAR pqend; /* preprocessor start-quote character */
CHAR comment[2]; /* start of one-line comment */
CHAR combegin[2]; /* start of multi-line comment */
CHAR comend[2]; /* end of multi-line comment */
BOOLEAN allcaps; /* uppercase words are "other" */
BOOLEAN initialcaps; /* mixed case words starting with upper are "other" */
BOOLEAN mixedcaps; /* mixed case words starting with lower are "other" */
BOOLEAN finalt; /* words ending with "_t" are "other" */
BOOLEAN initialpunct; /* words starting with punctuation */
BOOLEAN ignorecase; /* keywords may be uppercase */
char wordbits[256]; /* which chars can appear in a word */
} SINFO;
#define STARTWORD 0x01 /* can start a word */
#define INWORD 0x02 /* can occur within a word */
#define PUNCTWORD 0x04 /* may be first punct of 2-punct word */
#define DELIMREGEXP 0x08 /* can delimit regular expression */
#define USEREGEXP 0x10 /* occurs before possible regexp */
#define USEREGSUB 0x20 /* occurs before possible regsub */
#define isstartword(si,c) ((si)->wordbits[(CHAR)(c)] & STARTWORD)
#define isinword(si,c) ((si)->wordbits[(CHAR)(c)] & INWORD)
#define ispunctword(si,c) ((si)->wordbits[(CHAR)(c)] & PUNCTWORD)
#define isregexp(si,c) ((si)->wordbits[(CHAR)(c)] & DELIMREGEXP)
#define isbeforeregexp(si,c) ((si)->wordbits[(CHAR)(c)] & USEREGEXP)
#define isbeforeregsub(si,c) ((si)->wordbits[(CHAR)(c)] & USEREGSUB)
#define wordbeforeregexp(w) ((w)[-2] & USEREGEXP)
#define wordbeforeregsub(w) ((w)[-2] & USEREGSUB)
#define wordfont(w) ((char)((w)[-1]))
/* This array stores the fonts to be used with the TOKENTYPES, above. It is
* initialized by the setup() function each time the screen is redrawn,
* to reflect the values of the "font" options above.
*/
static char cfont[PUNCT + 1];
static SINFO *sinfo;
/* This macro computes a hash value for a word, which is used for looking
* the word up in the SINFO.keyword[] table. The word is known to be at least
* one character long and terminated with a '\0', so word[1] is guaranteed to
* be valid and consistent.
*/
#define KWHASH(word) (((word)[0] & 0x1f) ^ (((word)[1] & 0x03) << 5))
/* This function returns a pointer to the hashed keyword description if the
* given word is in fact a keyword. Otherwise it returns NULL.
*/
static CHAR *iskeyword(word)
CHAR *word; /* pointer to word */
{
int hash;
int i, j;
/* compute a hash value for this word */
hash = KWHASH(word);
/* if no keywords have that hash value, then return False right away */
if (!sinfo->keyword[hash])
return NULL;
/* try to find the word in the list of keywords */
if (sinfo->ignorecase)
{
/* not case sensitive */
for (i = 0; sinfo->keyword[hash][i]; i++)
{
for (j = 0; toupper(sinfo->keyword[hash][i][j]) == toupper(word[j]); j++)
{
if (!sinfo->keyword[hash][i][j])
return sinfo->keyword[hash][i];
}
}
}
else
{
/* case sensitive */
for (i = 0; sinfo->keyword[hash][i]; i++)
{
if (!CHARcmp(sinfo->keyword[hash][i], word))
{
return sinfo->keyword[hash][i];
}
}
}
/* we didn't find it in the keyword list */
return NULL;
}
static void addkeyword(word, font, doesregexp, doesregsub)
CHAR *word; /* a keyword */
_char_ font; /* font, or '\0' for default */
BOOLEAN doesregexp; /* can keyword be followed by a regexp? */
BOOLEAN doesregsub; /* can keyword be followed by regexp+regsub? */
{
int hash; /* hash value of word */
CHAR *keyword; /* entry describing the current keyword */
CHAR **hashed; /* new list for a hash table slot */
int i;
/* see if the keyword is already in the list */
keyword = iskeyword(word);
if (!keyword)
{
/* no, we need to add it... */
hash = KWHASH(word);
if (sinfo->keyword[hash])
{
/* allocate a new, larger copy of the hash array */
for (i = 0; sinfo->keyword[hash][i]; i++)
{
}
hashed = safealloc(i + 2, sizeof(CHAR *));
for (i = 0; sinfo->keyword[hash][i]; i++)
{
hashed[i] = sinfo->keyword[hash][i];
}
safefree(sinfo->keyword[hash]);
sinfo->keyword[hash] = hashed;
}
else
{
/* create the first hash list */
sinfo->keyword[hash] = safealloc(2, sizeof(CHAR *));
i = 0;
}
/* append the new keyword to the list */
keyword = safealloc(CHARlen(word) + 3, sizeof(CHAR));
*keyword++ = '\0'; /* no special bits yet */
*keyword++ = '\0'; /* no special font yet */
CHARcpy(keyword, word);
sinfo->keyword[hash][i] = keyword;
sinfo->keyword[hash][i + 1] = NULL;
}
/* set the word's attributes */
if (font)
keyword[-1] = font;
if (doesregexp)
keyword[-2] |= USEREGEXP;
if (doesregsub)
keyword[-2] |= USEREGEXP | USEREGSUB;
/* if not a normal word, then mark the character for extra checking */
if (!isstartword(sinfo, keyword[0]) ||
(keyword[1] && !isinword(sinfo, keyword[1])))
{
sinfo->wordbits[keyword[0]] |= PUNCTWORD;
}
}
/* read a line from a file via ioread(), and parse it into words */
static CHAR **fetchline()
{
static CHAR line[500];
static CHAR *word[50];
CHAR ch;
int w, l;
BOOLEAN inword;
int nread;
do
{
for (w = l = 0, inword = False;
(nread = ioread(&ch, 1)) == 1 && ch != '\n';
)
{
if (isspace(ch))
{
if (inword)
{
line[l++] = '\0';
inword = False;
}
}
else
{
if (!inword)
{
word[w++] = &line[l];
inword = True;
}
line[l++] = ch;
}
}
if (inword)
line[l] = '\0';
word[w] = NULL;
} while (nread == 1 && (!word[0] || *word[0] == '#'));
return nread==1 ? word : NULL;
}
/* This function checks whether a given file name's extension is listed in
* the "lib/elvis.syn" file. Returns True if known, or False otherwise.
*/
BOOLEAN dmsknown(filename)
char *filename;
{
CHAR **values;
char *synname;
int len;
int i, j;
values = NULL;
synname = iopath(tochar8(o_elvispath), SYNTAX_FILE, False);
if (synname && ioopen(synname, 'r', False, False, False))
{
/* locate an "extension" line that ends like filename */
len = strlen(filename);
while ((values = fetchline()) != NULL)
{
if (CHARcmp(values[0], toCHAR("extension")))
continue;
for (i = 1;
values[i] &&
((j = CHARlen(values[i])) > len ||
CHARcmp(values[i], toCHAR(filename + len - j)));
i++)
{
}
if (values[i])
break;
}
ioclose();
}
return (BOOLEAN)(values != NULL);
}
/* start the mode, and allocate dminfo */
static DMINFO *init(win)
WINDOW win;
{
char *pathname, *str;
CHAR *cp, **values;
int i, j;
/* if this is the first-ever time a window has been initialized to
* this mode, then we have some extra work to do...
*/
if (!dmsyntax.mark2col)
{
/* Inherit some functions from normal mode. */
dmsyntax.mark2col = dmnormal.mark2col;
dmsyntax.move = dmnormal.move;
dmsyntax.wordmove = dmnormal.wordmove;
dmsyntax.indent = dmnormal.indent; /* !!! really a good idea? */
dmsyntax.tagnext = dmnormal.tagnext;
/* initialize the mode's global options */
optpreset(o_commentfont, 'i', OPT_REDRAW);
optpreset(o_stringfont, 'f', OPT_REDRAW);
optpreset(o_keywordfont, 'b', OPT_REDRAW);
optpreset(o_functionfont, 'n', OPT_REDRAW);
optpreset(o_variablefont, 'n', OPT_REDRAW);
optpreset(o_prepfont, 'e', OPT_REDRAW);
optpreset(o_otherfont, 'b', OPT_REDRAW);
str = getenv("INCLUDE");
#ifdef OSINCLUDEPATH
if (!str)
str = OSINCLUDEPATH;
#endif
o_includepath = toCHAR(str);
/* if no real window, then we're done! */
if (!win)
return NULL;
}
/* allocate a SINFO structure for this window */
sinfo = (SINFO *)safealloc(1, sizeof(SINFO));
/* initialize the wordbits[] array to allow letters and digits */
for (i = 0; i < QTY(sinfo->wordbits); i++)
{
sinfo->wordbits[i] = (isalnum(i) ? (STARTWORD|INWORD) : 0);
}
/* locate the "elvis.syn" file */
pathname = iopath(tochar8(o_elvispath), SYNTAX_FILE, False);
if (pathname && ioopen(pathname, 'r', False, False, False))
{
cp = CHARchr(o_display(win), ' ');
if (cp)
{
/* locate a "language" line containing the name */
cp++;
while ((values = fetchline()) != NULL)
{
if (CHARcmp(values[0], toCHAR("language")))
continue;
for (i = 1; values[i] && CHARcmp(values[i], cp); i++)
{
}
if (values[i])
break;
}
}
else if (o_filename(markbuffer(win->cursor)))
{
/* locate an "extension" line that ends like filename */
cp = o_filename(markbuffer(win->cursor));
j = CHARlen(cp);
while ((values = fetchline()) != NULL)
{
if (CHARcmp(values[0], toCHAR("extension")))
continue;
for (i = 1; values[i] && (CHARlen(values[i]) > (unsigned)j || CHARcmp(values[i], cp + j - CHARlen(values[i]))); i++)
{
}
if (values[i])
break;
}
}
else
{
values = NULL;
}
/* if we found it, then read the attributes */
if (values)
{
while ((values = fetchline()) != NULL
&& CHARcmp(values[0], toCHAR("language")))
{
str = tochar8(values[0]);
if (!strcmp(str, "keyword"))
{
for (i = 1; values[i]; i++)
{
addkeyword(values[i], '\0', False, False);
}
}
else if (!strcmp(str, "font") && values[1])
{
str = tochar8(values[1]);
if (!strcmp(str, "normal")
|| !strcmp(str, "n")
|| !strcmp(str, "fixed")
|| !strcmp(str, "f")
|| !strcmp(str, "bold")
|| !strcmp(str, "b")
|| !strcmp(str, "emphasized")
|| !strcmp(str, "e")
|| !strcmp(str, "italic")
|| !strcmp(str, "i")
|| !strcmp(str, "underlined")
|| !strcmp(str, "u"))
{
for (i = 2; values[i]; i++)
{
addkeyword(values[i], *str, False, False);
}
}
/* else invalid font */
}
else if (!strcmp(str, "comment"))
{
for (i = 1; values[i]; i++)
{
if (values[i + 1])
{
CHARncpy(sinfo->combegin, values[i], 2);
CHARncpy(sinfo->comend, values[++i], 2);
}
else
{
CHARncpy(sinfo->comment, values[1], 2);
}
}
}
else if (!strcmp(str, "string"))
{
if (values[1] && values[2])
{
sinfo->strbegin = *values[1];
sinfo->strend = *values[2];
}
else if (values[1] && values[1][1])
{
sinfo->strbegin = values[1][0];
sinfo->strend = values[1][1];
}
else if (values[1])
{
sinfo->strbegin =
sinfo->strend = *values[1];
}
}
else if (!strcmp(str, "character"))
{
if (values[1] && values[2])
{
sinfo->charbegin = *values[1];
sinfo->charend = *values[2];
}
else if (values[1] && values[1][1])
{
sinfo->charbegin = values[1][0];
sinfo->charend = values[1][1];
}
else if (values[1])
{
sinfo->charbegin =
sinfo->charend = *values[1];
}
}
else if (!strcmp(str, "regexp"))
{
for (i = 1; values[i]; i++)
{
for (j = 0; values[i][j]; j++)
{
sinfo->wordbits[values[i][j]] |= DELIMREGEXP;
}
}
}
else if (!strcmp(str, "useregexp"))
{
for (i = 1; values[i]; i++)
{
cp = iskeyword(values[i]);
if (cp)
{
cp[-2] |= USEREGEXP;
}
else if (isalpha(values[i][0]))
{
addkeyword(values[i], '\0', True, False);
}
else /* character list */
{
for (j = 0; values[i][j]; j++)
{
sinfo->wordbits[values[i][j]] |= USEREGEXP;
}
}
}
}
else if (!strcmp(str, "useregsub"))
{
for (i = 1; values[i]; i++)
{
cp = iskeyword(values[i]);
if (cp)
{
cp[-2] |= USEREGEXP | USEREGSUB;
}
else if (isalpha(values[i][0]))
{
addkeyword(values[i], '\0', True, True);
}
else /* character list */
{
for (j = 0; values[i][j]; j++)
{
sinfo->wordbits[values[i][j]] |= USEREGEXP | USEREGSUB;
}
}
}
}
else if (!strcmp(str, "preprocessor"))
{
if (values[1])
sinfo->preprocessor = *values[1];
}
else if (!strcmp(str, "prepquote"))
{
if (values[1] && values[2])
{
sinfo->pqbegin = *values[1];
sinfo->pqend = *values[2];
}
else if (values[1] && values[1][1])
{
sinfo->pqbegin = values[1][0];
sinfo->pqend = values[1][1];
}
else if (values[1])
{
sinfo->pqbegin =
sinfo->pqend = *values[1];
}
}
else if (!strcmp(str, "function"))
{
if (values[1])
sinfo->function = *values[1];
}
else if (!strcmp(str, "other"))
{
for (i = 1; values[i]; i++)
{
str = tochar8(values[i]);
if (!strcmp(str, "allcaps"))
sinfo->allcaps = (BOOLEAN)!sinfo->allcaps;
else if (!strcmp(str, "initialcaps"))
sinfo->initialcaps = (BOOLEAN)!sinfo->initialcaps;
else if (!strcmp(str, "mixedcaps"))
sinfo->mixedcaps = (BOOLEAN)!sinfo->mixedcaps;
else if (!strcmp(str, "final_t"))
sinfo->finalt = (BOOLEAN)!sinfo->finalt;
else if (!strcmp(str, "initialpunct"))
sinfo->initialpunct = (BOOLEAN)!sinfo->initialpunct;
/* else unknown type */
}
}
else if (!strcmp(str, "startword"))
{
for (i = 1; values[i]; i++)
{
for (j = 0; values[i][j]; j++)
{
sinfo->wordbits[values[i][j]] |= STARTWORD;
}
}
}
else if (!strcmp(str, "inword"))
{
for (i = 1; values[i]; i++)
{
for (j = 0; values[i][j]; j++)
{
sinfo->wordbits[values[i][j]] |= INWORD;
}
}
}
else if (!strcmp(str, "ignorecase"))
{
if (values[1])
sinfo->ignorecase = calctrue(values[1]);
else
sinfo->ignorecase = True;
}
/* else unknown attribute */
}
/* close the "elvis.syn" file */
(void)ioclose();
}
}
/* return the window's SINFO structure */
return (DMINFO *)sinfo;
}
/* end the mode, and free the modeinfo */
static void term(info)
DMINFO *info; /* window-specific information about mode */
{
int hash, i;
SINFO *si = (SINFO *)info;
for (hash = 0; hash < 256; hash++)
{
/* skip empty hash lists */
if (!si->keyword[hash]) continue;
/* free all words in this hash list, and then free the list */
for (i = 0; si->keyword[hash][i]; i++)
{
safefree(si->keyword[hash][i] - 2);
/* "- 2" because we sneak in two extra chars
* to store attributes of the keyword.
*/
}
safefree(si->keyword[hash]);
}
safefree(info);
}
/* Choose a line to appear at the top of the screen, and return its mark.
* Also, initialize the info for the next line.
*/
static MARK setup(top, cursor, bottom, info)
MARK top; /* where the image drawing began last time */
long cursor; /* cursor's offset into buffer */
MARK bottom; /* where the image drawing ended last time */
DMINFO *info; /* window-specific information about mode */
{
MARK newtop;
BOOLEAN oddquotes;
CHAR *cp;
CHAR following;
BOOLEAN knowstr, knowcom;
sinfo = (SINFO *)info;
/* copy the values of the "font" options into the cfont[] array */
cfont[COMMENT] = cfont[COMMENT2] = o_commentfont;
cfont[STRING] = cfont[CHARACTER] = cfont[REGEXP] = cfont[REGSUB] = cfont[PREPQUOTE] = o_stringfont;
cfont[KEYWORD] = cfont[FIRSTPUNCT] = cfont[SECONDPUNCT] = o_keywordfont;
cfont[FUNCTION] = o_functionfont;
cfont[VARIABLE] = o_variablefont;
cfont[PREP] = cfont[PREPWORD] = o_prepfont;
cfont[OTHER] = o_otherfont;
cfont[PUNCT] = 'n';
/* use the normal mode's setup function to choose the screen top */
newtop = (*dmnormal.setup)(top, cursor, bottom, info);
if (!newtop || markoffset(newtop) >= o_bufchars(markbuffer(newtop)))
return newtop;
/* The top line could be a continuation of a COMMENT or STRING.
* (Other tokens can't span a newline, so we can ignore them.)
* Scan backward for clues about comments or strings.
*
* This isn't perfect. To do the job perfectly, we'd need to start
* at the top of the buffer, and scan *forward* to the top of the
* screen, but that could take far too long.
*/
following = *scanalloc(&cp, newtop);
oddquotes = False;
knowstr = (BOOLEAN)(sinfo->strbegin == '\0' || (cp && *cp == sinfo->strbegin));
knowcom = (BOOLEAN)!sinfo->combegin[0];
sinfo->token = PUNCT;
for (; scanprev(&cp) && (!knowstr || !knowcom); following = *cp)
{
if (sinfo->strend && *cp != '\\' && following == sinfo->strend && !knowstr)
{
/* a " which isn't preceded by a \ toggles the quote state */
oddquotes = (BOOLEAN)!oddquotes;
}
else if (sinfo->strend && *cp != '\\' && following == '\n')
{
/* strings can't span a newline unless preceded by a backslash */
knowstr = True;
}
else if (sinfo->combegin[0]
&& *cp == sinfo->combegin[0]
&& (!sinfo->combegin[1] || following == sinfo->combegin[1]))
{
/* We'll assume that slash-asterisk always starts a
* comment (i.e., that it never occurs inside a string).
* However, some C++ programmers like to begin comments
* with slash-slash and a bunch of asterisks; we need
* to watch out for that.
*/
knowstr = knowcom = True;
if (*cp != sinfo->comment[1] || !scanprev(&cp) || *cp != sinfo->comment[0])
{
sinfo->token = COMMENT;
break;
}
}
else if (sinfo->comend[0]
&& *cp == sinfo->comend[0]
&& (!sinfo->comend[1] || following == sinfo->comend[1]))
{
/* We'll assume that asterisk-slash always ends a comment.
* (I.e., that it never occurs inside a string.)
*/
knowstr = knowcom = True;
}
else if (sinfo->comment[0]
&& *cp == sinfo->comment[0]
&& (!sinfo->comment[1] || following == sinfo->comment[1]))
{
/* We'll assume that slash-slash always indicates a single-
* line comment. (I.e., that it never occurs in a string or
* slash-asterisk type comment.
*/
knowstr = knowcom = True;
oddquotes = False;
}
}
scanfree(&cp);
/* If it isn't a comment, then it might be a string... check oddquotes */
if (sinfo->token == PUNCT && oddquotes)
{
*(TOKENTYPE *)info = STRING;
}
return newtop;
}
/* generate the image of a line, and return the mark of the next line */
static MARK image(w, line, info, draw)
WINDOW w; /* window where drawing will take place */
MARK line; /* line to be drawn */
DMINFO *info; /* window-specific information amount mode */
void (*draw)P_((CHAR *p, long qty, _char_ font, long offset));
/* function for drawing a single character */
{
int col;
CHAR *cp;
CHAR tmpchar;
CHAR regexpdelim; /* delimiter for current regexp */
long offset;
static MARKBUF tmp;
CHAR undec[40]; /* characters of undecided font */
CHAR *up; /* pointer used for scanning chars */
CHAR prev, prev2; /* the preceding two characters */
BOOLEAN quote; /* True after a backslash in STRING or CHARACTER */
int upper, lower; /* counts letters of each case */
BOOLEAN indent;
CHAR *kp; /* pointer to a keyword */
BOOLEAN expectregexp; /* allow a regular expression to start next */
BOOLEAN expectregsub; /* allow substitution text to follow regexp */
BOOLEAN expectprepq; /* allow preprocessor quotes */
int i;
/* initially, we'll assume we continue the font of the previous line */
sinfo = (SINFO *)info;
quote = False;
indent = True;
expectregexp = (BOOLEAN)(sinfo->token == PUNCT);
expectregsub = expectprepq = False;
/* this is just to silence a compiler warning */
regexpdelim = '/';
/* for each character in the line... */
for (prev = ' ', col = 0, offset = markoffset(line), scanalloc(&cp, line);
cp && *cp != '\n' && (*cp != '\f' || markoffset(w->cursor) < o_bufchars(markbuffer(w->cursor)));
prev = *cp, offset++, scannext(&cp))
{
/* some characters are handled specially */
if (*cp == '\t' && !o_list(w))
{
/* tab ends any symbol */
if (sinfo->token == KEYWORD || sinfo->token == FUNCTION
|| sinfo->token == VARIABLE || sinfo->token == OTHER
|| sinfo->token == PREPWORD)
{
sinfo->token = PUNCT;
}
/* display the tab character as a bunch of spaces */
tmpchar = ' ';
i = o_tabstop(markbuffer(w->cursor));
i -= col % i;
(*draw)(&tmpchar, -i, indent ? 'n' : cfont[sinfo->token], offset);
col += i;
}
else if (*cp < ' ' || *cp == 127)
{
/* any control character ends any symbol */
if (sinfo->token == KEYWORD || sinfo->token == FUNCTION
|| sinfo->token == VARIABLE || sinfo->token == OTHER
|| sinfo->token == PREPWORD)
{
sinfo->token = PUNCT;
}
/* also ends indentation */
indent = False;
/* control characters */
tmpchar = '^';
(*draw)(&tmpchar, -1, cfont[sinfo->token], offset);
tmpchar = *cp ^ 0x40;
(*draw)(&tmpchar, -1, cfont[sinfo->token], offset);
col += 2;
}
else if (sinfo->preprocessor && indent
&& *cp == sinfo->preprocessor && sinfo->token == PUNCT)
{
/* output the '#' in prepfont */
indent = False;
sinfo->token = PREP;
(*draw)(cp, 1, cfont[PREP], offset);
col++;
}
else /* normal printable character */
{
/* ending a keyword/function/variable? */
if (!isinword(sinfo, *cp)
&& (sinfo->token == FUNCTION
|| sinfo->token == VARIABLE
|| sinfo->token == KEYWORD
|| sinfo->token == OTHER))
{
sinfo->token = PUNCT;
}
/* starting a keyword/function/variable? */
if (sinfo->token == PUNCT && isstartword(sinfo, *cp))
{
/* this isn't regexp */
expectregexp = expectregsub = False;
/* store first letter of possible keyword */
lower = upper = 0;
undec[0] = *cp;
if (islower(*cp))
lower++;
else if (isupper(*cp))
upper++;
/* collect more letters of possible keyword */
for (i = 1, prev2 = prev = '\0',
scanalloc(&up, marktmp(tmp, markbuffer(line), offset + 1));
i < QTY(undec) - 1 && up && isinword(sinfo, *up);
prev2 = prev, prev = *up, i++, scannext(&up))
{
undec[i] = *up;
if (islower(*up))
lower++;
else if (isupper(*up))
upper++;
}
undec[i] = '\0';
/* did we find a keyword? */
kp = iskeyword(undec);
if (kp)
{
sinfo->token = KEYWORD;
cfont[KEYWORD] = wordfont(kp);
if (!cfont[KEYWORD])
cfont[KEYWORD] = o_keywordfont;
expectregexp = (BOOLEAN)wordbeforeregexp(kp);
expectregsub = (BOOLEAN)wordbeforeregsub(kp);
}
else /* must be function, variable, or other */
{
/* continue on to the end of the word */
for (;
up && isinword(sinfo, *up);
prev2 = prev, prev = *up, scannext(&up))
{
if (islower(*up))
lower++;
else if (isupper(*up))
upper++;
}
/* skip any following whitespace */
for (; up && *up == ' '; scannext(&up))
{
}
/* is the word followed by a '(' ? */
if (up && sinfo->function && *up == sinfo->function)
{
sinfo->token = FUNCTION;
}
else if (sinfo->finalt && prev2 == '_' && prev == 't')
{
sinfo->token = OTHER;
}
else if (sinfo->initialpunct && !isalnum(undec[0]))
{
sinfo->token = OTHER;
}
else if (sinfo->allcaps && upper >= 2 && lower == 0)
{
sinfo->token = OTHER;
}
else if (sinfo->initialcaps && lower > 0 && isupper(undec[0]))
{
sinfo->token = OTHER;
}
else if (sinfo->mixedcaps && lower > 0 && upper > 0)
{
sinfo->token = OTHER;
}
else if (i > 1 || isalnum(*cp))
{
sinfo->token = VARIABLE;
}
/* else leave it set to PUNCT so we can
* recognize two-punctuation keywords.
*/
}
scanfree(&up);
}
else if (sinfo->token == PREP && !isspace(*cp))
{
sinfo->token = PREPWORD;
}
else if (sinfo->token == PREPWORD && !isalnum(*cp))
{
sinfo->token = PUNCT;
expectprepq = True;
}
/* start of preprocessor quote? */
if (sinfo->token == PUNCT && expectprepq && *cp == sinfo->pqbegin)
{
sinfo->token = PREPQUOTE;
expectprepq = False;
}
/* start of a two-punctuation keyword? */
if (sinfo->token == PUNCT && ispunctword(sinfo, *cp))
{
/* maybe... check the other punct character */
undec[0] = *cp;
undec[1] = '\0';
kp = iskeyword(undec);
if (!kp)
{
tmp = *scanmark(&cp);
markaddoffset(&tmp, 1);
undec[1] = scanchar(&tmp);
undec[2] = '\0';
kp = iskeyword(undec);
}
if (kp)
{
sinfo->token = kp[1] ? FIRSTPUNCT : SECONDPUNCT;
cfont[FIRSTPUNCT] = cfont[SECONDPUNCT] = wordfont(kp);
if (!cfont[FIRSTPUNCT])
cfont[FIRSTPUNCT] = cfont[SECONDPUNCT] = o_keywordfont;
expectregexp = (BOOLEAN)wordbeforeregexp(kp);
expectregsub = (BOOLEAN)wordbeforeregsub(kp);
}
}
/* start of a string? */
if (sinfo->strbegin && sinfo->token == PUNCT && *cp == sinfo->strbegin)
{
sinfo->token = STRING;
expectregexp = expectregsub = False;
/* make sure the initial quote character
* isn't going to be mistaken for the
* terminating quote character.
*/
quote = True;
}
/* start of a character literal? */
if (sinfo->charbegin && sinfo->token == PUNCT && *cp == sinfo->charbegin)
{
sinfo->token = CHARACTER;
expectregexp = expectregsub = False;
/* make sure the initial quote character
* isn't going to be mistaken for the
* terminating quote character.
*/
quote = True;
}
/* start of a C comment? */
if (sinfo->token == PUNCT
&& sinfo->combegin[0]
&& *cp == sinfo->combegin[0]
&& (!sinfo->combegin[1]
|| scanchar(marktmp(tmp, markbuffer(line), offset + 1)) == sinfo->combegin[1]))
{
sinfo->token = COMMENT;
expectregexp = expectregsub = False;
}
/* start of a one-line comment? */
if (sinfo->token == PUNCT
&& sinfo->comment[0]
&& *cp == sinfo->comment[0]
&& (indent || !expectregexp)
&& (!sinfo->comment[1]
|| scanchar(marktmp(tmp, markbuffer(line), offset + 1)) == sinfo->comment[1]))
{
sinfo->token = COMMENT2;
expectregexp = expectregsub = False;
}
/* start of a regular expression? */
if (sinfo->token == PUNCT && expectregexp && isregexp(sinfo, *cp))
{
sinfo->token = REGEXP;
expectregexp = False;
regexpdelim = *cp;
/* make sure the initial quote character
* isn't going to be mistaken for the
* terminating quote character.
*/
quote = True;
}
/* would a regexp be allowed after this char? */
if (sinfo->token == PUNCT && !isspace(*cp))
{
expectregexp = (BOOLEAN)isbeforeregexp(sinfo, *cp);
expectregsub = (BOOLEAN)isbeforeregsub(sinfo, *cp);
}
/* any non-whitespace ends indent and disabled prepquote */
if (!isspace(*cp))
{
indent = expectprepq = False;
}
/* draw the character */
(*draw)(cp, 1, indent ? 'n' : cfont[sinfo->token], offset);
col++;
/* end of a string? */
if (sinfo->token == STRING && *cp == sinfo->strend && !quote)
{
sinfo->token = PUNCT;
}
/* end of a character? */
if (sinfo->token == CHARACTER && *cp == sinfo->charend && !quote)
{
sinfo->token = PUNCT;
}
/* end of a comment? */
if (sinfo->token == COMMENT
&& (sinfo->comend[1]
? (prev == sinfo->comend[0] && *cp == sinfo->comend[1])
: *cp == sinfo->comend[0]))
{
sinfo->token = PUNCT;
}
/* end of a regexp or substitution text? */
if ((sinfo->token == REGEXP || sinfo->token == REGSUB)
&& *cp == regexpdelim && !quote)
{
if (expectregsub)
{
sinfo->token = REGSUB;
quote = True;
expectregsub = False;
}
else
{
sinfo->token = PUNCT;
}
}
/* middle/end of a two-punctuation character keyword? */
if (sinfo->token == FIRSTPUNCT)
{
sinfo->token = SECONDPUNCT;
}
else if (sinfo->token == SECONDPUNCT)
{
sinfo->token = PUNCT;
}
/* end of a prepquote? */
if (sinfo->token == PREPQUOTE && *cp == sinfo->pqend)
{
sinfo->token = PUNCT;
}
/* in a STRING, CHARACTER, REGEXP, or REGSUB constant,
* backslash is used to quote the following character.
*/
if ((sinfo->token==STRING || sinfo->token==CHARACTER
|| sinfo->token == REGEXP || sinfo->token == REGSUB)
&& *cp == '\\' && !quote)
{
quote = True;
}
else
{
quote = False;
}
}
}
/* end the line */
if (o_list(w) && (!cp || *cp == '\n'))
{
tmpchar = '$';
(*draw)(&tmpchar, -1, 'n', -1);
}
tmpchar = (cp ? *cp : '\n');
(*draw)(&tmpchar, 1, 'n', offset);
if (cp)
{
offset++;
}
else
{
offset = o_bufchars(markbuffer(w->cursor));
}
/* Strings can span a newline if the newline is preceded by a
* backslash. Old-style C comments can span a newline. Everything
* else ends here.
*/
if ((sinfo->token != STRING || prev != '\\') && sinfo->token != COMMENT)
{
sinfo->token = PUNCT;
}
/* clean up & return the MARK of the next line */
scanfree(&cp);
return marktmp(tmp, markbuffer(w->cursor), offset);
}
/* This function considers the possibility that the cursor may be on a quoted
* filename. If so, it returns the name, with the quotes. Otherwise it calls
* dmnormal.tagatcursor() for the traditional tags.
*
* The return value is a dynamically-allocated string; the calling function
* is responsible for freeing it when it is no longer required.
*/
static CHAR *tagatcursor(win, cursor)
WINDOW win; /* window, used for finding mode-dependent info */
MARK cursor; /* where the desired tag name can be found */
{
CHAR *ret; /* return value */
CHAR *cp; /* used for scanning */
/* initialization */
sinfo = (SINFO *)win->mi;
ret = NULL;
/* search backward for first quote or whitespace */
for (scanalloc(&cp, cursor);
cp && !isspace(*cp) && *cp != sinfo->pqbegin && *cp != sinfo->strbegin;
scanprev(&cp))
{
}
/* did we find some kind of quote? */
if (cp && *cp == sinfo->strbegin)
{
/* string - search forward for closing quote, collecting chars
* along the way. Beware of whitespace.
*/
do
{
buildCHAR(&ret, *cp);
} while (scannext(&cp) && !isspace(*cp) && *cp != sinfo->strend);
if (cp && *cp == sinfo->strend)
{
buildCHAR(&ret, *cp);
scanfree(&cp);
return ret;
}
}
else if (cp && *cp == sinfo->pqbegin)
{
/* prepquote - search forward for closing quote, collecting
* chars along the way. Beware of whitespace.
*/
do
{
buildCHAR(&ret, *cp);
} while (scannext(&cp) && !isspace(*cp) && *cp != sinfo->pqend);
if (cp && *cp == sinfo->pqend)
{
buildCHAR(&ret, *cp);
scanfree(&cp);
return ret;
}
}
/* cleanup */
scanfree(&cp);
if (ret)
safefree(ret);
/* use dmnormal's tagatcursor() function */
return (*dmnormal.tagatcursor)(win, cursor);
}
/* This function checks the tagname. If it is a quoted filename, then it
* attempts to load the file. Otherwise it calls dmnormal.tagload() to try
* and load a normal tag.
*/
static MARK tagload(tagname, from)
CHAR *tagname; /* name of tag to move to */
MARK from; /* where we're coming from */
{
char *name;
char *tmp;
unsigned len;
DIRPERM perms;
BUFFER buf;
static MARKBUF retb;
/* is it a quoted filename? */
len = CHARlen(tagname);
if (len >= 3 && (*tagname == '"' || (sinfo && sinfo->pqbegin && *tagname == sinfo->pqbegin)))
{
/* make an unquoted, 8-bit version of the name */
name = strdup(tochar8(tagname + 1));
if ((*tagname == '"' && tagname[len - 1] == '"')
|| (sinfo && sinfo->pqbegin && *tagname == sinfo->pqbegin && tagname[len - 1] == sinfo->pqend))
{
name[len - 2] = '\0';
}
/* if plain old quote character, then look for it first in
* the current directory.
*/
if (*tagname == '"')
{
perms = dirperm(name);
if (perms == DIR_READONLY || perms == DIR_READWRITE)
{
buf = bufload(NULL, name, False);
assert(buf != NULL);
safefree(name);
return marktmp(retb, buf, buf->changepos);
}
}
/* search through the include path */
if (o_includepath)
{
tmp = iopath(tochar8(o_includepath), name, False);
if (tmp)
{
buf = bufload(NULL, tmp, False);
assert(buf != NULL);
safefree(name);
return marktmp(retb, buf, buf->changepos);
}
}
/* Failed! Complain, clean up, and return NULL */
msg(MSG_ERROR, "[s]header file $1 not found", name);
safefree(name);
return NULL;
}
/* use dmnormal's tagload() function */
return (*dmnormal.tagload)(tagname, from);
}
DISPMODE dmsyntax =
{
"syntax",
"generic syntax coloring",
True, /* can optimize */
True, /* can use normal wordwrap */
0, /* no window options */
NULL,
QTY(globdesc),
globdesc,
globval,
init,
term,
NULL, /* init() sets this to be identical to dmnormal's mark2col() */
NULL, /* init() sets this to be identical to dmnormal's move() */
NULL, /* init() sets this to be identical to dmnormal's moveword() */
setup,
image,
NULL, /* doesn't need a header */
NULL, /* init() sets this to be identical to dmnormal's indent() */
tagatcursor,
tagload,
NULL /* init() sets this to be identical to dmnormal's tagnext() */
};
#endif /* DISPLAY_SYNTAX */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.