This is dmnormal.c in view mode; [Download] [Up]
/* dmnormal.c */
/* Copyright 1995 by Steve Kirkendall */
char id_dmnormal[] = "$Id: dmnormal.c,v 2.29 1996/09/21 01:21:36 steve Exp $";
#include "elvis.h"
#if USE_PROTOTYPES
static DMINFO *init(WINDOW win);
static void term(DMINFO *info);
static MARK move(WINDOW w, MARK from, long linedelta, long column, BOOLEAN cmd);
static MARK wordmove(MARK cursor, long count, BOOLEAN backward, BOOLEAN whitespace);
static long mark2col(WINDOW w, MARK mark, BOOLEAN cmd);
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 void indent(WINDOW w, MARK line, long linedelta);
static CHAR *tagatcursor(WINDOW win, MARK cursor);
static MARK tagload(CHAR *tagname, MARK from);
#endif
/* start the mode, and allocate modeinfo */
static DMINFO *init(win)
WINDOW win;
{
return NULL;
}
/* end the mode, and free the modeinfo */
static void term(info)
DMINFO *info; /* window-specific info about mode */
{
}
/* Move vertically, and to a given column (or as close to column as possible) */
static MARK move(w, from, linedelta, column, cmd)
WINDOW w; /* window where buffer is shown */
MARK from; /* old position */
long linedelta; /* change in line number */
long column; /* desired column */
BOOLEAN cmd; /* if True, we're in command mode; else input mode */
{
static MARKBUF tmp;
long col, lnum;
long offset;
CHAR *cp;
assert(w != NULL || column == 0);
/* move forward/back to the start of the line + linedelta */
lnum = markline(from) + linedelta;
if (lnum < 1)
lnum = 1;
else if (lnum > o_buflines(markbuffer(from)))
lnum = o_buflines(markbuffer(from));
offset = lowline(bufbufinfo(markbuffer(from)), lnum);
/* now move to the left far enough to find the desired column */
(void)scanalloc(&cp, marktmp(tmp, markbuffer(from), offset));
for (col = 0; w && cp && *cp != '\n' && col <= column; offset++, scannext(&cp))
{
/* add the width of this character */
if (*cp == '\t' && (!o_list(w) || w->state->acton))
{
col = col + o_tabstop(markbuffer(w->cursor)) - (col % o_tabstop(markbuffer(w->cursor)));
}
else if (*cp < ' ' || *cp == 127)
{
col += 2;
}
else
{
col++;
}
}
/* The above loop normally exits when we've PASSED the desired column,
* so we normally want to back up one character. Two important
* exceptions: In input mode, if we break out of that loop because we
* hit '\n' then we want to leave the cursor on the '\n' character.
* If we hit the end of the buffer, then we want to leave the cursor
* on the last character of the buffer but we also need to be careful
* about empty buffers.
*/
if (col > 0 && !(!cmd && (!cp || *cp == '\n') && col <= column))
{
offset--;
}
/* return the mark */
scanfree(&cp);
return marktmp(tmp, markbuffer(from), offset);
}
/* Convert a mark to a column number */
static long mark2col(w, mark, cmd)
WINDOW w; /* window where buffer is shown */
MARK mark; /* mark to be converted */
BOOLEAN cmd; /* if True, we're in command mode; else input mode */
{
long col;
CHAR *cp;
MARK front;
long nchars;
/* if the buffer is empty, the column must be 0 */
if (o_bufchars(markbuffer(mark)) == 0)
{
return 0;
}
/* find the front of the line */
front = move(w, mark, 0, 0, cmd);
nchars = markoffset(mark) - markoffset(front);
/* in command mode, we leave the cursor on the last cell of any
* wide characters such as tabs. To accomplish this, we'll find
* the column of the following character, and then subtract 1.
*/
if (cmd)
{
nchars++;
}
/* count character widths until we find the requested mark */
for (scanalloc(&cp, front), col = 0; cp && nchars > 0; nchars--, scannext(&cp))
{
if (*cp == '\t' && (!o_list(w) || w->state->acton))
{
col = col + o_tabstop(markbuffer(w->cursor)) - (col % o_tabstop(markbuffer(w->cursor)));
}
else if (*cp == '\n')
{
#if 1
col++;
#else
/* no change to col */
#endif
}
else if (*cp < ' ' || *cp == 127)
{
col += 2;
}
else
{
col++;
}
}
scanfree(&cp);
/* the other half of the "cmd" hack */
if (cmd && col > 0)
{
col--;
}
return col;
}
/* This function implements most of the logic for the visual <b>, <e>, and
* <w> commands. If it succedes, it adjusts the starting mark and returns
* it; if it fails, it returns NULL and leaves the starting mark unchanged.
*/
static MARK wordmove(cursor, count, backward, whitespace)
MARK cursor; /* starting position */
long count; /* number of words to move by */
BOOLEAN backward; /* if True, move backward; else forward */
BOOLEAN whitespace; /* if True, trailing whitespace is included */
{
BOOLEAN inword, inpunct;
CHAR *cp;
long offset;
long end;
/* start the scan */
scanalloc(&cp, cursor);
offset = markoffset(cursor);
assert(cp != NULL);
end = o_bufchars(markbuffer(cursor));
/* figure out if we're in the middle of a word */
if (backward || !whitespace || isspace(*cp))
{
inword = inpunct = False;
}
else if (isalnum(*cp) || *cp == '_')
{
inword = True;
inpunct = False;
}
else
{
inword = False;
inpunct = True;
}
/* continue... */
if (backward)
{
/* move backward until we hit the top of the buffer, or
* the start of the desired word.
*/
while (count > 0 && offset > 0)
{
scanprev(&cp);
assert(cp != NULL);
if (isspace(*cp))
{
if (inword || inpunct)
{
count--;
}
inpunct = inword = False;
}
else if (isalnum(*cp) || *cp == '_')
{
if (inpunct)
{
count--;
}
inword = True;
inpunct = False;
}
else
{
if (inword)
{
count--;
}
inword = False;
inpunct = True;
}
if (count > 0)
{
offset--;
}
}
/* if we hit offset==0 and were in a word, we found the start
* of the first word. Count it here.
*/
if (offset == 0 && (inword || inpunct))
{
count--;
}
}
else
{
/* move forward until we hit the end of the buffer, or
* the start of the desired word.
*/
while (count > 0 && offset < end - 1)
{
scannext(&cp);
assert(cp != NULL);
if (isspace(*cp))
{
if ((inword || inpunct) && !whitespace)
{
count--;
}
inword = inpunct = False;
if (count > 0)
{
offset++;
}
}
else if (isalnum(*cp) || *cp == '_')
{
if ((!inword && whitespace) || inpunct)
{
count--;
}
inword = True;
inpunct = False;
if (count > 0 || whitespace)
{
offset++;
}
}
else
{
if ((!inpunct && whitespace) || inword)
{
count--;
}
inword = False;
inpunct = True;
if (count > 0 || whitespace)
{
offset++;
}
}
}
}
/* cleanup */
scanfree(&cp);
/* If we were moving forward and had only one more word to go, then
* move the cursor to the last character in the buffer -- usually a
* newline character. Pretend the count was decremented to 0.
*/
if (count == 1 && !backward && whitespace && end > 0)
{
offset = end - 1;
count = 0;
}
/* if the count didn't reach 0, we failed */
if (count > 0)
{
return NULL;
}
/* else set the cursor's offset */
assert(offset < end && offset >= 0);
marksetoffset(cursor, offset);
return cursor;
}
/* 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 previous image started */
long cursor; /* offset of cursor position */
MARK bottom; /* where previous image ended */
DMINFO *info; /* window-specific info about mode */
{
static MARKBUF tmp;
long topoff;
long bottomoff;
long other;
long i;
/* if the cursor is still on the screen (or very near the bottom)
* then use the same top.
*/
topoff = markoffset(move((WINDOW)0, top, 0, 0, True));
bottomoff = markoffset(move((WINDOW)0, bottom, o_nearscroll, 0, True));
if (cursor >= topoff && (cursor < bottomoff || bottomoff < markoffset(bottom) + 1))
{
return marktmp(tmp, markbuffer(top), topoff);
}
/* if the cursor is on the line before the top, then scroll back */
if (topoff > 0)
{
for (i = 1; i < o_nearscroll; i++)
{
other = markoffset(move((WINDOW)0, top, -i, 0, True));
if (cursor >= other && cursor < topoff)
{
return marktmp(tmp, markbuffer(top), other);
}
}
}
/* else try to center the line in the window */
other = cursor - (bottomoff - topoff) / 2;
if (other < 0)
{
other = 0;
}
other = markoffset(move((WINDOW)0, marktmp(tmp, markbuffer(top), other), 0, 0, True));
return marktmp(tmp, markbuffer(top), other);
}
static MARK image(w, line, info, draw)
WINDOW w; /* window where drawing will go */
MARK line; /* start of line to draw next */
DMINFO *info; /* window-specific info about mode */
void (*draw)P_((CHAR *p, long qty, _char_ font, long offset));
/* function for drawing a single character */
{
int col;
CHAR *cp;
CHAR tmpchar;
long offset;
static MARKBUF tmp;
long startoffset; /* offset of first contiguous normal char */
int qty; /* number of contiguous normal chars */
CHAR buf[100]; /* buffer, holds the contiguous normal chars */
int i;
/* initialize startoffset just to silence a compiler warning */
startoffset = 0;
/* for each character in the line... */
qty = 0;
for (col = 0, offset = markoffset(line), scanalloc(&cp, line); cp && *cp != '\n'; offset++, scannext(&cp))
{
/* some characters are handled specially */
if (*cp == '\f' && markoffset(w->cursor) == o_bufchars(markbuffer(w->cursor)))
{
/* when printing, a formfeed ends the line (and page) */
break;
}
else if (*cp == '\t' && (!o_list(w) || w->state->acton))
{
/* output any preceding normal characters */
if (qty > 0)
{
(*draw)(buf, qty, 'n', startoffset);
qty = 0;
}
/* display the tab character as a bunch of spaces */
i = o_tabstop(markbuffer(w->cursor));
i -= (col % i);
tmpchar = ' ';
(*draw)(&tmpchar, -i, 'n', offset);
col += i;
}
else if (*cp < ' ' || *cp == 127)
{
/* output any preceding normal characters */
if (qty > 0)
{
(*draw)(buf, qty, 'n', startoffset);
qty = 0;
}
/* control characters */
tmpchar = '^';
(*draw)(&tmpchar, 1, 'n', offset);
tmpchar = *cp ^ 0x40;
(*draw)(&tmpchar, 1, 'n', offset);
col += 2;
}
else
{
/* starting a new string of contiguous normal chars? */
if (qty == 0)
{
startoffset = offset;
}
/* add this char to the string */
buf[qty++] = *cp;
col++;
/* if buf[] is full, flush it now */
if (qty == QTY(buf))
{
(*draw)(buf, qty, 'n', startoffset);
qty = 0;
}
}
}
/* output any normal chars from the end of the line */
if (qty > 0)
{
(*draw)(buf, qty, 'n', startoffset);
qty = 0;
}
/* end the line */
if (o_list(w) && !w->state->acton && *cp == '\n')
{
(*draw)(toCHAR("$"), 1, 'n', offset);
}
(*draw)(cp ? cp : toCHAR("\n"), 1, 'n', offset);
if (cp)
{
offset++;
}
else
{
offset = o_bufchars(markbuffer(w->cursor));
}
scanfree(&cp);
return marktmp(tmp, markbuffer(w->cursor), offset);
}
/* This function implements autoindent. Given the MARK of a newly created
* line, insert a copy of the indentation from another line. The line whose
* indentation is to be copied is specified as a line delta. Usually, this
* will be -1 so the new line has the same indentation as a previous line.
* The <Shift-O> command uses a linedelta of 1 so the new line will have the
* same indentation as the following line.
*/
static void indent(w, line, linedelta)
WINDOW w; /* windows whose options are used */
MARK line; /* new line to adjust */
long linedelta; /* -1 to copy from previous line, etc. */
{
MARKBUF from, to; /* bounds of whitespace in source line */
MARKBUF bline; /* copy of the "line" argument */
CHAR *cp; /* used for scanning whitespace */
BOOLEAN srcblank; /* is source indentation from a blank line? */
assert(o_autoindent(markbuffer(line)));
assert(markbuffer(w->cursor) == markbuffer(line));
/*assert(scanchar(w->cursor) == '\n');*/
/* find the start of the source line */
bline = *line;
from = *dispmove(w, linedelta, 0);
if (markoffset(&from) == markoffset(&bline))
{
/* can't find source line -- at edge of buffer, maybe? */
return;
}
/* find the end of the source line's whitespace */
for (scanalloc(&cp, &from); cp && (*cp == ' ' || *cp == '\t'); scannext(&cp))
{
}
if (!cp)
{
/* hit end of buffer without finding end of line -- do nothing */
scanfree(&cp);
return;
}
to = *scanmark(&cp);
srcblank = (BOOLEAN)(*cp == '\n');
scanfree(&cp);
if (markoffset(&from) != markoffset(&to))
{
/* copy the source whitespace into the new line */
bufpaste(&bline, &from, &to);
/* if the source line was blank, then delete its whitespace */
if (srcblank)
{
if (linedelta > 0L)
{
/* tweak from & to, due to bufpaste() */
markaddoffset(&to, markoffset(&to) - markoffset(&from));
marksetoffset(&to, markoffset(&from));
}
else
{
/* tweak bline for the following bufreplace() */
markaddoffset(&bline, markoffset(&from) - markoffset(&to));
}
bufreplace(&from, &to, NULL, 0L);
}
/* tweak the argument mark, to leave cursor after whitespace */
marksetoffset(line, markoffset(&bline) + markoffset(&to) - markoffset(&from));
bline = *line;
}
/* if the line had some other indentation before, then delete that */
for (scanalloc(&cp, line); cp && (*cp == ' ' || *cp == '\t'); scannext(&cp))
{
}
if (cp)
{
bufreplace(&bline, scanmark(&cp), NULL, 0);
}
scanfree(&cp);
}
/* Return a dynamically-allocated string containing the name of the tag at the
* cursor, or NULL if the cursor isn't on a tag.
*/
static CHAR *tagatcursor(win, cursor)
WINDOW win;
MARK cursor;
{
MARKBUF curscopy; /* a copy of the cursor */
MARK word; /* mark for the front of the word */
/* find the ends of the word */
curscopy = *cursor;
word = wordatcursor(&curscopy);
/* if not on a word, then return NULL */
if (!word)
return NULL;
/* copy the word into RAM, and return it */
return bufmemory(word, &curscopy);
}
/* Lookup a tag name, load the file where that tag was defined, and return
* the MARK of its position within that buffer. If the tag can't be found
* or loaded for any reason, then issue an error message and return NULL.
*/
static MARK tagload(tagname, from)
CHAR *tagname; /* name of tag to lookup */
MARK from; /* initial position of the cursor */
{
static MARKBUF retmark; /* the return value */
BUFFER buf; /* the buffer containing the tag */
CHAR tagline[1000]; /* a line from the tags file */
int bytes; /* number of bytes in I/O buffer */
char *pathname; /* full pathname of current "tags" file */
int len; /* significant length of tagname */
BOOLEAN fulllen; /* len is the full length of the tag */
BOOLEAN allnext; /* is the whole next line in the buffer? */
char *loadname; /* name of file to load */
EXINFO xinfb; /* dummy ex command, for parsing tag address */
BOOLEAN wasmagic; /* stores the normal value of o_magic */
CHAR *src, *dst;
/* find the significant length of tagname */
len = CHARlen(tagname);
fulllen = True;
if (len > o_taglength && o_taglength > 0)
{
len = o_taglength;
fulllen = False;
}
/* for each tags file named in the "tags" option... */
for (pathname = iopath(tochar8(o_tags), "tags", True);
pathname;
pathname = iopath(NULL, "tags", True))
{
/* open the tags file */
if (!ioopen(pathname, 'r', False, False, False))
{
return NULL;
}
/* Compare the tag of each line against the tagname */
bytes = ioread(tagline, QTY(tagline) - 1);
while (bytes > len
&& (CHARncmp(tagname, tagline, (size_t)len)
|| (fulllen && !isspace(tagline[len]))))
{
/* delete this line from tagline[] */
for (src = tagline; src < &tagline[bytes] && *src != '\n'; src++)
{
}
for (dst = tagline, src++, allnext = False; src < &tagline[bytes]; )
{
if (*src == '\n')
allnext = True;
*dst++ = *src++;
}
bytes = (int)(dst - tagline);
/* if the next line is incomplete, read some more text
* from the tags file.
*/
if (!allnext || bytes <= len)
{
bytes += ioread(dst, (int)QTY(tagline) - bytes - 1);
}
}
(void)ioclose();
/* did we find the tag? */
if (bytes > len)
{
goto Found;
}
}
msg(MSG_ERROR, "[S]tag $1 not found", tagname);
return NULL;
Found:
/* skip past the tagname to find the start of the filename */
for (src = tagline; src < &tagline[bytes] && !isspace(*src); src++)
{
}
for ( ; src < &tagline[bytes] && isspace(*src); src++)
{
}
/* locate the end of the filename, and mark it with a NUL byte */
for (dst = src; dst < &tagline[bytes] && !isspace(*dst); dst++)
{
}
*dst++ = '\0';
/* if the filename isn't a full pathname, then assume it is in the
* same directory as the "tags" file.
*/
if (!isalnum(*src))
{
loadname = tochar8(src);
}
else
{
loadname = dirpath(dirdir(pathname), tochar8(src));
}
/* find a buffer containing the file, or load the file into a buffer */
buf = buffind(toCHAR(loadname));
if (!buf)
{
if (dirperm(loadname) == DIR_NEW)
{
msg(MSG_ERROR, "[s]$1 doesn't exist", loadname);
return NULL;
}
buf = bufload(NULL, loadname, False);
if (!buf)
{
/* bufload() already gave error message */
return NULL;
}
}
if (o_bufchars(buf) == 0)
{
goto NotFound;
}
/* locate the tag address field */
for (src = dst; src < &tagline[bytes] && isspace(*src); src++)
{
}
for (dst = src; dst < &tagline[bytes] && *dst != '\n'; dst++)
{
}
*dst = '\0';
/* convert the tag address into a line number */
scanstring(&dst, src);
memset((char *)&xinfb, 0, sizeof xinfb);
(void)marktmp(xinfb.defaddr, buf, 0);
wasmagic = o_magic;
o_magic = False;
if (!exparseaddress(&dst, &xinfb))
{
scanfree(&dst);
o_magic = wasmagic;
goto NotFound;
}
scanfree(&dst);
o_magic = wasmagic;
(void)marktmp(retmark, buf, lowline(bufbufinfo(buf), xinfb.to));
exfree(&xinfb);
return &retmark;
NotFound:
msg(MSG_WARNING, "tag address out of date");
return marktmp(retmark, buf, 0L);
}
DISPMODE dmnormal =
{
"normal",
"Standard vi",
True, /* display generating can be optimized */
True, /* should use normal wordwrap */
0, /* no window options */
NULL, /* no descriptions of window options */
0, /* no global options */
NULL, /* no descriptions of global options */
NULL, /* no values of global options */
init,
term,
mark2col,
move,
wordmove,
setup,
image,
NULL, /* doesn't need a header */
indent,
tagatcursor,
tagload,
NULL /* no tagnext() function */
};
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.