This is move.c in view mode; [Download] [Up]
/* move.c */ /* Copyright 1995 by Steve Kirkendall */ char id_move[] = "$Id: move.c,v 2.39 1996/09/10 16:51:38 steve Exp $"; #include "elvis.h" /* This function implements the "h" command */ RESULT m_left(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARK tmp; DEFAULT(1); /* find the start of this line */ tmp = dispmove(win, 0L, 0L); /* if already at the start of the line, then fail */ if (markoffset(tmp) == markoffset(win->state->cursor)) { return RESULT_ERROR; } /* move either the requested number of characters left, or to the * start of the line, whichever is closer */ if (markoffset(win->state->cursor) - vinf->count > markoffset(tmp)) { markaddoffset(win->state->cursor, -vinf->count); } else { marksetoffset(win->state->cursor, markoffset(tmp)); } return RESULT_COMPLETE; } /* This function implements the "l" command */ RESULT m_right(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARK tmp; DEFAULT(1); /* find the end of this line. This is complicated by the fact that * when used as the target of an operator, the l command can move * past the end of the line. */ if (vinf->oper && !win->state->acton) tmp = (*win->md->move)(win, win->cursor, 0L, INFINITY, False); else tmp = dispmove(win, 0L, INFINITY); /* if already at the end of the line, then fail */ if (markoffset(tmp) == markoffset(win->state->cursor)) { return RESULT_ERROR; } /* move either the requested number of characters right, or to the * end of the line, whichever is closer */ if (markoffset(win->state->cursor) + vinf->count < markoffset(tmp)) { markaddoffset(win->state->cursor, vinf->count); } else { marksetoffset(win->state->cursor, markoffset(tmp)); } return RESULT_COMPLETE; } /* This function implements the "j" and "k" functions */ RESULT m_updown(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARK tmp = NULL; DEFAULT(1); /* do the move */ switch (vinf->command) { case '_': /* decremement count & then treat like <Enter>... */ vinf->count--; /* fall through... */ case ELVCTRL('J'): case ELVCTRL('M'): case ELVCTRL('N'): case '+': case 'j': tmp = dispmove(win, vinf->count, win->wantcol); break; case ELVCTRL('P'): case '-': case 'k': tmp = dispmove(win, -vinf->count, win->wantcol); break; #ifndef NDEBUG default: abort(); #endif } /* check for goofy return values */ if (markoffset(tmp) < 0 || markoffset(tmp) >= o_bufchars(markbuffer(win->state->cursor)) || (markoffset(tmp) == markoffset(win->state->cursor) && vinf->count != 0)) { return RESULT_ERROR; } /* It's good! */ marksetoffset(win->state->cursor, markoffset(tmp)); return RESULT_COMPLETE; } /* This function implements the "^" function */ RESULT m_front(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { CHAR *cp; /* scan from the start of the line, to the first non-space */ scanalloc(&cp, dispmove(win, 0, 0)); if (cp && (*cp == '\t' || *cp == ' ')) { do { scannext(&cp); } while (cp && (*cp == ' ' || *cp == '\t')); if (*cp == '\n') { scanprev(&cp); } } if (!cp) { return RESULT_ERROR; } marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); scanfree(&cp); return RESULT_COMPLETE; } /* This function implements the <Shift-G>, <Control-G>, and <%> commands, which * move the cursor to a specific line, character, or percentage of the buffer. * The number is given in the "count" field; if no number is given, then either * move to the last line, show buffer statistics, or move to matching bracket, * respectively. */ RESULT m_absolute(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BUFFER buf = markbuffer(win->state->cursor); CHAR match; /* the parenthesis we want (for <%> command) */ CHAR nest = 0;/* the starting parenthesis */ CHAR *cp; /* used for scanning through text */ long count; /* nesting depth */ EXINFO xinfb; /* an ex command */ assert(vinf->command == 'G' || vinf->command == ELVCTRL('G') || vinf->command == '%'); switch (vinf->command) { case 'G': DEFAULT(o_buflines(buf)); /* Try to go to the requestedc line. Catch errors, including a * numberless <G> in an empty buffer. */ if (!marksetline(win->state->cursor, vinf->count)) { msg(MSG_ERROR, "[d]only $1 lines", o_buflines(buf)); return RESULT_ERROR; } break; case ELVCTRL('G'): if (!vinf->count) { /* no count, just show buffer status */ memset((char *)&xinfb, 0, sizeof xinfb); xinfb.window = win; xinfb.defaddr = *win->state->cursor; xinfb.command = EX_FILE; ex_file(&xinfb); } else if (vinf->count < 0 || vinf->count > o_bufchars(buf)) { /* request offset is out of range */ return RESULT_ERROR; } else { /* set the cursor to the requested offset */ marksetoffset(win->state->cursor, vinf->count - 1); } break; case '%': if (!vinf->count) { /* search forward within line for one of "[](){}" */ for (match = '\0', scanalloc(&cp, win->state->cursor); !match;) { /* if hit end-of-line or end-of-buffer without * finding a parenthesis, then fail. */ if (!cp || *cp == '\n') { scanfree(&cp); return RESULT_ERROR; } /* if parenthesis, great! else keep looking */ switch (*cp) { case '[': match = ']'; break; case ']': match = '['; break; case '(': match = ')'; break; case ')': match = '('; break; case '{': match = '}'; break; case '}': match = '{'; break; default: scannext(&cp); } } assert(cp != NULL); nest = *cp; /* search forward or backward for match */ if (match == '(' || match == '[' || match == '{')/*)]}*/ { /* search backward */ for (count = 1; count > 0; ) { /* back up 1 char; give up at top of buffer */ if (!scanprev(&cp)) { break; } /* check the char */ if (*cp == match) count--; else if (*cp == nest) count++; } } else { /* search forward */ for (count = 1; count > 0; ) { /* advance 1 char; give up at end of buffer */ if (!scannext(&cp)) { break; } /* check the char */ if (*cp == match) count--; else if (*cp == nest) count++; } } /* if we hit the end of the buffer, then fail */ if (!cp) { scanfree(&cp); return RESULT_ERROR; } /* move the cursor to the match */ marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); scanfree(&cp); } else if (vinf->count < 1 || vinf->count > 100) { msg(MSG_ERROR, "bad percentage"); return RESULT_ERROR; } else { /* Compute the character offset, given the percentage. * I'm slightly careful here to avoid overflowing * the long int which stores the offset. */ if (o_bufchars(buf) > 1000000L) { marksetoffset(win->state->cursor, (o_bufchars(buf) / 100) * vinf->count); } else { marksetoffset(win->state->cursor, (o_bufchars(buf) * vinf->count) / 100); } vinf->tweak |= TWEAK_FRONT; } return RESULT_COMPLETE; } return RESULT_COMPLETE; } /* Move to a mark. This function implements the <'> and <`> commands. */ RESULT m_mark(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARK newmark; /* check for <'><'> or <`><`> */ if (vinf->command == vinf->key2) { if (win->prevcursor) { assert(markbuffer(win->state->cursor) == markbuffer(win->prevcursor)); marksetoffset(win->state->cursor, markoffset(win->prevcursor)); return RESULT_COMPLETE; } return RESULT_ERROR; } /* else key2 had better be a lowercase ASCII letter */ if (vinf->key2 < 'a' || vinf->key2 > 'z') { return RESULT_ERROR; } /* look up the named mark */ newmark = namedmark[vinf->key2 - 'a']; if (!newmark) { msg(MSG_ERROR, "[C]'$1 unset", vinf->key2); return RESULT_ERROR; } /* if the named mark is in a different buffer, fail. */ /* (A later version of elvis may be able to do this!) */ if (markbuffer(newmark) != markbuffer(win->state->cursor)) { msg(MSG_ERROR, "[C]'$1 in other buffer", vinf->key2); return RESULT_ERROR; } /* move to the named mark */ marksetoffset(win->state->cursor, markoffset(newmark)); return RESULT_COMPLETE; } /* This function implements the whitespace-delimited word movement commands: * <Shift-W>, <Shift-B>, and <Shift-E>. */ RESULT m_bigword(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BOOLEAN whitespace; /* do we include following whitespace? */ BOOLEAN backward; /* are we searching backwards? */ long offset; /* offset of *cp character */ long count; /* number of words to skip */ long end; /* offset of the end of the buffer */ BOOLEAN inword; /* are we currently in a word? */ CHAR *cp; /* used for scanning chars of buffer */ DEFAULT(1); /* start the scan */ offset = markoffset(win->state->cursor); scanalloc(&cp, win->state->cursor); assert(cp != NULL); count = vinf->count; end = o_bufchars(markbuffer(win->state->cursor)); /* figure out which type of movement we'll be doing */ switch (vinf->command) { case 'B': backward = True; whitespace = False; inword = False; break; case 'E': backward = False; whitespace = False; inword = False; break; default: backward = False; inword = (BOOLEAN)!isspace(*cp); if (vinf->oper == 'c') { /* "cW" acts like "cE", pretty much */ whitespace = False; vinf->tweak |= TWEAK_INCL; /* starting on whitespace? */ if (!inword) { /* When "cW" starts on whitespace, it changes * one less word than normal. If it would * normally change just one word, then it * should change a single whitespace character. */ vinf->count--; if (vinf->count == 0) { scanfree(&cp); return RESULT_COMPLETE; } } else if (markoffset(win->state->cursor) > 0) { /* When "cW" starts on the last character of a * word, then it should change just that last * character. By temporarily moving the * cursor back one char, we can achieve this * effect without affecting the results of any * other movement. */ scanprev(&cp); offset--; } } else { whitespace = True; } break; } /* 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) { count--; } inword = False; } else { inword = True; } if (count > 0) { offset--; if (offset == 0 && count == 1) { count = 0; } } } } 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 (vinf->oper && *cp == '\n' && count == 1) { count = 0; if (!(vinf->tweak & TWEAK_INCL)) offset++; } else if (inword && !whitespace) { count--; } inword = False; if (count > 0) { offset++; } } else { if (!inword && whitespace) { count--; } inword = True; offset++; } } } /* cleanup */ scanfree(&cp); /* if the count didn't reach 0, we failed */ if (count > 0) { return RESULT_ERROR; } /* else set the cursor's offset */ assert(offset < end && offset >= 0); marksetoffset(win->state->cursor, offset); return RESULT_COMPLETE; } /* This function implements the <b>, <e>, and <w> word movement commands * by calling the mode-dependent wordmove function. */ RESULT m_word(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BOOLEAN whitespace; /* include trailing whitespace? */ BOOLEAN backward; /* are we moving backward? */ long span; /* offset of starting position */ long newline; /* offset of newline */ CHAR *cp; /* used for scanning backward for newline */ CHAR atcursor; /* character at the cursor position */ DEFAULT(1); /* figure out which type of movement we'll be doing */ switch (vinf->command) { case 'b': backward = True; whitespace = False; break; case 'e': backward = False; whitespace = False; break; default: backward = False; if (vinf->oper == 'c') { /* "cw" acts like "ce", pretty much */ whitespace = False; vinf->tweak |= TWEAK_INCL; /* starting on whitespace? */ atcursor = scanchar(win->state->cursor); if (atcursor == ' ' || atcursor == '\t') { /* When "cw" starts on whitespace, it changes * one less word than normal. If it would * normally change just one word, then it * should change a single whitespace character. */ vinf->count--; if (vinf->count == 0) { return RESULT_COMPLETE; } } else if (markoffset(win->state->cursor) > 0) { /* When "cw" starts on the last character of a * word, then it should change just that last * character. By temporarily moving the * cursor back one char, we can achieve this * effect without affecting the results of any * other movement. */ markaddoffset(win->state->cursor, -1); } } else { whitespace = True; } break; } /* remember the starting position */ span = markoffset(win->state->cursor); /* Call the mode-dependent function. If we're editing a history buffer * then always use dmnormal's version; else (for the window's main * buffer) use the window's mode's function. */ if (!(*(win->state->acton ? dmnormal.wordmove : win->md->wordmove)) (win->state->cursor, vinf->count, backward, whitespace)) { /* movement failed */ return RESULT_ERROR; } /* NOTE: If we get here, then the word movement succeeded and the * cursor has been moved. */ /* We need to avoid newlines for <w> movements that are used as the * target of an operator (except for <c><w> which doesn't include * whitespace). */ if (whitespace && vinf->oper && vinf->oper != 'c') { newline = markoffset(win->state->cursor); span = newline - span; scanalloc(&cp, win->state->cursor); while (scanprev(&cp) && span-- > 0) { if (*cp == '\n') newline = markoffset(scanmark(&cp)); } scanfree(&cp); marksetoffset(win->state->cursor, newline); } else if (whitespace && !vinf->oper && scanchar(win->state->cursor) == '\n') { /* tried a plain old "w" command at end of file -- * move cursor back to starting point and fail. */ marksetoffset(win->state->cursor, span); return RESULT_ERROR; } return RESULT_COMPLETE; } /* This function scrolls the screen, implementing the <Control-E>, <Control-Y>, * <Control-F>, <Control-B>, <Control-D>, and <Control-U> commands. */ RESULT m_scroll(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARKBUF tmp; assert(!win->state->acton); assert(!(win->state->flags & ELVIS_BOTTOM) || vinf->command == ELVCTRL('D')); /* adjust the count */ if (vinf->command == ELVCTRL('U') || vinf->command == ELVCTRL('D')) { if (vinf->count == 0) { vinf->count = o_scroll(win); } else { if (vinf->count > o_lines(win) - 1) { vinf->count = o_lines(win) - 1; } o_scroll(win) = vinf->count; } } else { DEFAULT(1); } /* Do the scroll */ switch (vinf->command) { case ELVCTRL('U'): win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), -vinf->count, 0, True)); win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), -vinf->count, 0, True)); marksetoffset(win->cursor, markoffset(dispmove(win, -vinf->count, 0))); break; case ELVCTRL('D'): win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), vinf->count, 0, True)); win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), vinf->count, 0, True)); marksetoffset(win->cursor, markoffset(dispmove(win, vinf->count, 0))); break; case ELVCTRL('Y'): win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), -vinf->count, 0, True)); win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), -vinf->count, 0, True)); marksetoffset(win->cursor, markoffset(dispmove(win, 0, win->wantcol))); if (markoffset(win->cursor) >= win->di->bottomline) { marksetoffset(win->cursor, win->di->bottomline); marksetoffset(win->cursor, markoffset(dispmove(win, -1, win->wantcol))); } break; case ELVCTRL('E'): win->di->topline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), vinf->count, 0, True)); win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->bottomline), vinf->count, 0, True)); if (markoffset(win->cursor) < win->di->topline) { marksetoffset(win->cursor, win->di->topline); marksetoffset(win->cursor, markoffset(dispmove(win, 0, win->wantcol))); } break; case ELVCTRL('F'): marksetoffset(win->cursor, win->di->bottomline); win->di->topline = markoffset(dispmove(win, -1, 0)); marksetoffset(win->cursor, win->di->topline); win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), o_lines(win), 0, True)); break; case ELVCTRL('B'): /* note: this adjustment of topline can be sloppy, because * the drawimage() function will perform slop scrolling, if * necessary, to keep the cursor in the screen. */ marksetoffset(win->cursor, win->di->topline); win->di->topline = markoffset(dispmove(win, -o_lines(win), 0)); win->di->bottomline = markoffset((*win->md->move)(win, marktmp(tmp, markbuffer(win->cursor), win->di->topline), o_lines(win), 0, True)); break; } /* partially disable optimization for the next redraw - it doesn't * automatically realize that scrolling is a type of change. */ win->di->logic = DRAW_CHANGED; return RESULT_COMPLETE; } /* This function moves the cursor to a given column. The window's desired * column ("wantcol") is set to the requested column. This implements the * <|>, <0>, <Control-X>, and <$> commands. */ RESULT m_column(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARK dest; /* choose a column number */ switch (vinf->command) { case '|': case '0': DEFAULT(1); break; case ELVCTRL('X'): DEFAULT(o_columns(win)); vinf->count += win->di->skipped; break; case '$': vinf->count = INFINITY; break; } /* move to the requested column (or as close as possible). */ dest = dispmove(win, 0L, vinf->count - 1); marksetoffset(win->state->cursor, markoffset(dest)); /* if the window is editing the main buffer, set wantcol... * even if the cursor didn't quite make it to the requested column. */ win->wantcol = vinf->count - 1; return RESULT_COMPLETE; } /* This function implements the character-search commands: f, t, F, T, comma, * and semicolon. */ RESULT m_csearch(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { static CHAR prevcmd; /* previous command, for <,> and <;> */ static CHAR prevtarget; /* previous target character */ CHAR *cp; /* used for scanning text */ DEFAULT(1); assert(strchr("fFtT,;", vinf->command)); /* comma and semicolon recall the previous character search */ if (vinf->command == ';' || vinf->command == ',') { /* fail if there was no previous command */ if (!prevcmd) { msg(MSG_ERROR, "no previous char search"); return RESULT_ERROR; } /* use the previous command, or its opposite */ if (vinf->command == ';') { vinf->command = prevcmd; } else if (isupper(prevcmd)) { vinf->command = tolower(prevcmd); } else { vinf->command = toupper(prevcmd); } /* use the previous target character, too */ vinf->key2 = prevtarget; } else /* not comma or semicolon */ { /* remember this command so it can be repeated later */ prevcmd = vinf->command; prevtarget = vinf->key2; } /* Which way should we scan? Forward or backward? */ if (islower(vinf->command)) { /* scan forward */ for (scanalloc(&cp, win->state->cursor); vinf->count > 0 && scannext(&cp) && *cp != '\n'; ) { if (*cp == vinf->key2) { vinf->count--; } } } else { /* scan backward */ for (scanalloc(&cp, win->state->cursor); vinf->count > 0 && scanprev(&cp) && *cp != '\n'; ) { if (*cp == vinf->key2) { vinf->count--; } } } /* if hit EOF or newline, then fail */ if (!cp || *cp == '\n') { scanfree(&cp); return RESULT_ERROR; } /* if <t> or <T> then back off one character */ if (vinf->command == 't') { scanprev(&cp); } else if (vinf->command == 'T') { scannext(&cp); } /* move the cursor */ marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); scanfree(&cp); return RESULT_COMPLETE; } /* This funtion moves the cursor to the next tag in the current buffer */ RESULT m_tag(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { MARK next; /* where the next tag is located */ /* This only works when editing the main stratum */ if (win->state->acton) return RESULT_ERROR; /* If the display mode has no "next tag" function, then fail */ if (!win->md->tagnext) return RESULT_ERROR; /* else call the "next tag" function */ next = (*win->md->tagnext)(win->cursor); if (!next) return RESULT_ERROR; /* move the cursor to the next tag */ assert(markbuffer(next) == markbuffer(win->state->cursor)); marksetoffset(win->state->cursor, markoffset(next)); return RESULT_COMPLETE; } /* This function implements [[ and { movement commands */ RESULT m_bsection(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BUFFER buf; /* buffer being addressed */ CHAR *cp; CHAR nroff[3]; /* characters after current position */ CHAR *codes; BOOLEAN sect; /* are we looking through section? */ DEFAULT(1); assert(vinf->command == '[' || vinf->command == '{'); /* if this is the start of a "learn" mode, do that! */ if (vinf->command == '[' && vinf->key2 != '[') { return maplearn(vinf->key2, True); } /* search backward for a section or paragraph */ buf = markbuffer(win->state->cursor); scanalloc(&cp, win->state->cursor); memset(nroff, 0, sizeof nroff); nroff[0] = (*cp == '{' ? ' ' : *cp); nroff[1] = '\n'; do { /* move back one character */ if (!scanprev(&cp)) break; /* if this is a newline, look for special stuff... */ if (*cp == '\n') { if (nroff[0] == '{') vinf->count--; else if (nroff[1] != '\n' && nroff[0] == '\n' && vinf->command == '{') vinf->count--; else if (nroff[0] == '.') { for (codes = o_sections(buf), sect = (BOOLEAN)(vinf->command == '{'); codes && *codes; ) { if (codes[0] == nroff[1] && (codes[1] == nroff[2] || (!isalnum(nroff[2]) && (!codes[1] || codes[1] == ' ' ) ) ) ) { vinf->count--; break; } /* after section, chain to paragraph */ if ((!codes[1] || !codes[2]) && sect) { codes = o_paragraphs(buf); sect = False; } else if (!codes[1]) codes++; else codes += 2; } } } /* shift this character into "nroff" string */ nroff[2] = nroff[1]; nroff[1] = nroff[0]; nroff[0] = *cp; } while (vinf->count > 0); /* At this point, cp either points to the newline before a section, * or it is NULL because we hit the top of the buffer. If it is NULL * and we were only looking to go back one more section/paragraph, and * the cursor wasn't already at the top of the buffer, then we should * move the cursor to the top of the buffer. Otherwise a NULL cp * indicates an error. A non-NULL cp should cause the cursor to be * left after the newline that it points to. */ if (!cp && vinf->count == 1 && markoffset(win->state->cursor) != 0) { marksetoffset(win->state->cursor, 0); } else if (!cp) { scanfree(&cp); return RESULT_ERROR; } else { marksetoffset(win->state->cursor, markoffset(scanmark(&cp)) + 1); } scanfree(&cp); return RESULT_COMPLETE; } /* This function implements ]] and } movement commands */ RESULT m_fsection(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BUFFER buf; /* buffer being addressed */ CHAR *cp; CHAR nroff[3]; /* characters after current position */ CHAR *codes; long offset; /* offset of potential destination */ BOOLEAN sect; /* are we looking through section? */ DEFAULT(1); assert(vinf->command == ']' || vinf->command == '}'); /* Initialize "offset" just to silence a compiler warning */ offset = 0; /* if this is the end of a "learn" mode, do that! */ if (vinf->command == ']' && vinf->key2 != ']') { return maplearn(vinf->key2, False); } /* search forward for a section or paragraph */ buf = markbuffer(win->state->cursor); scanalloc(&cp, win->state->cursor); memset(nroff, 0, sizeof nroff); nroff[2] = *cp; nroff[1] = (*cp == '.' ? '\0' : '\n'); do { /* move ahead one character */ if (!scannext(&cp)) break; /* look for special stuff... */ if (nroff[2] == '\n' && *cp == '{') { offset = markoffset(scanmark(&cp)); vinf->count--; } else if (nroff[1] != '\n' && nroff[2] == '\n' && *cp == '\n' && vinf->command == '}') { offset = markoffset(scanmark(&cp)); vinf->count--; } else if (nroff[0] == '\n' && nroff[1] == '.') { for (codes = o_sections(buf), sect = (BOOLEAN)(vinf->command == '}'); codes && *codes; ) { if (codes[0] == nroff[2] && (codes[1] == *cp || (!isalnum(*cp) && (!codes[1] || codes[1] == ' ') ) ) ) { offset = markoffset(scanmark(&cp)) - 2; vinf->count--; break; } /* after section, chain to paragraph */ if ((!codes[1] || !codes[2]) && sect) { codes = o_paragraphs(buf); sect = False; } else if (!codes[1]) codes++; else codes += 2; } } /* shift this character into "nroff" string */ nroff[0] = nroff[1]; nroff[1] = nroff[2]; nroff[2] = *cp; } while (vinf->count > 0); /* At this point, cp either points to the last character of a section * header (and "offset" is the start of that header), or cp is NULL * because we hit the end of the buffer before finding a section. * If it is NULL and we were only looking to go forward 1 more section, * then move the cursor to the end of the buffer. Otherwise a NULL * cp indicates an error. A non-NULL cp should cause the cursor to be * left at the "offset" value. */ if (!cp && vinf->count == 1 && markoffset(win->state->cursor) < o_bufchars(buf) - 2) { /* leave cursor on the character before the final newline, * unless the final line consists of only a newline character; * then leave it on that newline. */ marksetoffset(win->state->cursor, o_bufchars(buf) - 2); if (scanchar(win->state->cursor) == '\n') { markaddoffset(win->state->cursor, 1); } /* doing a line-mode operator? */ if (vinf->oper) { /* include the final line */ vinf->tweak |= TWEAK_INCL; } } else if (!cp) { scanfree(&cp); return RESULT_ERROR; } else { marksetoffset(win->state->cursor, offset); } scanfree(&cp); return RESULT_COMPLETE; } /* This implements the screen-relative movement commands: H, M, and L */ RESULT m_scrnrel(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { long delta; /* number of lines to move forward */ long srcoff; /* offset of source line */ MARKBUF srcbuf; /* mark of line that we're moving relative to */ MARK tmp; /* value returned by display mode's move() function */ int rows; /* number of rows showing something other than "~" */ assert(vinf->command == 'H' || vinf->command == 'M' || vinf->command == 'L'); assert(win->di && win->di->rows > 1); DEFAULT(1); /* see how many rows are visible */ for (rows = win->di->rows - 2; rows > 1 && win->di->newrow[rows].lineoffset >= o_bufchars(markbuffer(win->state->cursor)); rows--) { } /* choose a source offset and line delta, depending on command */ switch (vinf->command) { case 'H': delta = vinf->count - 1; srcoff = win->di->newrow[0].lineoffset; break; case 'M': delta = 0; srcoff = win->di->newrow[rows / 2].lineoffset; break; default: /* 'L' */ delta = 1 - vinf->count; srcoff = win->di->newrow[rows].lineoffset; break; } /* if bad offset, then fail */ if (srcoff < 0 || srcoff >= o_bufchars(markbuffer(win->state->cursor))) return RESULT_ERROR; /* maybe move forward or backward from that line */ if (delta != 0) { (void)marktmp(srcbuf, markbuffer(win->state->cursor), srcoff); tmp = (*win->md->move)(win, &srcbuf, delta, 0, False); if (!tmp) return RESULT_ERROR; srcoff = markoffset(tmp); } /* move the cursor to that line */ marksetoffset(win->state->cursor, srcoff); return RESULT_COMPLETE; } RESULT m_z(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { /* This only works on the window's primary buffer */ if (win->state->cursor != win->cursor) return RESULT_ERROR; /* If a count is given, then move the cursor to that line */ if (vinf->count > 0 && vinf->count < o_buflines(markbuffer(win->cursor))) { marksetoffset(win->cursor, lowline(bufbufinfo(markbuffer(win->cursor)), vinf->count)); } /* tweak the window's top & bottom to force the current line to * appear in a given location on the screen. */ switch (vinf->key2) { case '\n': case '\r': case '+': /* The current line should appear at the top of the screen. * We'll tweak the top & bottom so they both refer to this * line. When the window is redrawn, the redrawing logic * will cause this line to be output first, and then it'll * just continue showing lines until the bottom of the * screen. */ win->di->topline = markoffset(dispmove(win, 0L, 0)); win->di->bottomline = o_bufchars(markbuffer(win->cursor)); win->di->logic = DRAW_CHANGED; break; case '.': case 'z': /* The current line should appear in the middle of the screen. * To do this, we'll set the top half a screenful's number of * lines back, and the bottom some point after the cursor. * We'll also set the drawing logic to perform slop-scrolling * until the cursor is in the top half of the screen, just in * case the lines at the top of the screen fill more than one * row. */ win->di->topline = markoffset(dispmove(win, -(o_lines(win) / 2), 0)); win->di->bottomline = o_bufchars(markbuffer(win->cursor)); win->di->logic = DRAW_CENTER; break; case '-': /* The current line should appear at the bottom of the screen. * To do this, we'll set the top back a whole screenload's * number of lines before the cursor line, and the bottom * to some point after the current line. When the window is * redrawn, the drawing logic will start drawing from the * computed top, and then scroll the window if necessary to * bring the current line onto the screen. */ win->di->topline = markoffset(dispmove(win, -o_lines(win), 0)); win->di->bottomline = markoffset(win->cursor) + 1; win->di->logic = DRAW_CHANGED; break; } return RESULT_COMPLETE; } RESULT m_fsentence(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BOOLEAN ending; /* have we seen at least one sentence ender? */ BOOLEAN didpara;/* between paragraph and first sentence in paragraph */ int spaces; /* number of spaces seen so far */ CHAR *cp; /* used for scanning through text */ CHAR *end; /* characters that end a sentence */ CHAR *quote; /* quote/parenthesis character that may appear at end */ CHAR oper; /* operator command, or '\0' */ long newline;/* offset of first newline in trailing whitespace */ long para; long offset; long count; DEFAULT(1); /* If sentenceend and sentencequote are unset, use default values */ end = o_sentenceend ? o_sentenceend : toCHAR(".?!"); quote = o_sentencequote ? o_sentencequote : toCHAR("\")]"); count = vinf->count; oper = vinf->oper; /* detect whether we're at the start of a paragraph */ offset = markoffset(win->state->cursor); scanalloc(&cp, win->state->cursor); if (*cp != '\n') scanprev(&cp); else while (cp && *cp == '\n') { scanprev(&cp); } if (cp) { marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); vinf->count = 1; vinf->command = '}'; m_fsection(win, vinf); para = markoffset(win->state->cursor); } else { para = 0; } marksetoffset(win->state->cursor, offset); if (para == offset) count++; else if (para < offset) { /* find the next paragraph */ marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); vinf->count = 1; vinf->command = '}'; m_fsection(win, vinf); para = markoffset(win->state->cursor); } /* for each count... */ newline = -1; scanseek(&cp, win->state->cursor); for (; cp && count > 0; count--) { /* for each character in the sentence... */ for (ending = didpara = False, spaces = 0; cp && (!ending || spaces < o_sentencegap || isspace(*cp)); scannext(&cp), offset++) { /* if paragraph, then... */ if (offset == para) { /* if still more sentences to skip... */ if (count > 1) { /* count this as a sentence, and * arrange for next line to also be a * sentence. */ count--; newline = -1; ending = True; if (*cp == '\n') { didpara = False; spaces = o_sentencegap; } else { didpara = True; spaces = 0; } /* oh, and we need to find the next * paragraph, too. */ marksetoffset(win->state->cursor, offset); vinf->count = 1; vinf->command = '}';/*{*/ if (m_fsection(win, vinf) == RESULT_COMPLETE) para = markoffset(win->state->cursor); else para = -1; continue; } else { /* we're here! */ break; } } /* check the character */ if (*cp == '\n') { spaces = o_sentencegap; didpara = False; if (newline < 0) newline = markoffset(scanmark(&cp)); } else if (didpara) /* skip characters in a ".P" line */; else if (isspace(*cp)) spaces++; else if (CHARchr(end, *cp)) newline = -1, ending = True, spaces = 0; else if (!CHARchr(quote, *cp)) newline = -1, ending = False, spaces = 0; else /* quote character */ newline = -1; } } /* did we find it? */ if (cp) { /* if target of operator, and a newline was encountered in the * trailing whitespace, then move the cursor to that newline; * else move the cursor to the start of the following sentence. */ if (oper && newline >= 0) marksetoffset(win->state->cursor, newline); else marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); } scanfree(&cp); return cp ? RESULT_COMPLETE : RESULT_ERROR; } RESULT m_bsentence(win, vinf) WINDOW win; /* window where command was typed */ VIINFO *vinf; /* information about the command */ { BOOLEAN first; /* True until we pass some mid-sentence stuff */ BOOLEAN ending; /* have we seen at least one sentence ender? */ BOOLEAN anynext;/* any text seen on following line */ BOOLEAN anythis;/* any text seen on this line */ int spaces; /* number of spaces seen so far */ CHAR *cp; /* used for scanning through text */ CHAR *end; /* characters that end a sentence */ CHAR *quote; /* quote/parenthesis character that may appear at end */ long count; /* sentences to move over */ long para; /* top of current paragraph */ long offset; RESULT result; DEFAULT(1); /* If sentenceend and sentencequote are unset, use default values */ end = o_sentenceend ? o_sentenceend : toCHAR(".?!"); quote = o_sentencequote ? o_sentencequote : toCHAR("\")]"); /* misc initialization */ anynext = anythis = False; offset = markoffset(win->state->cursor); /* NOTE: The "first" variable is used to handle the situation where * we start at the beginning of one sentence. From here, we want * to go back one extra sentence-end; otherwise <(> would just move * us to the start of the same sentence. */ first = True; /* Start scanning at the cursor location */ scanalloc(&cp, win->state->cursor); count = vinf->count; /* Find the start of this paragraph. That counts as a "sentence" */ vinf->command = '{'; vinf->count = 1; para = (m_bsection(win, vinf) == RESULT_COMPLETE) ? markoffset(win->state->cursor) : -1; /* For each count... */ for (; cp && count > 0; count--) { /* for each character in the sentence... */ for (ending = True, anythis = anynext = False, spaces = 0, scanprev(&cp), offset = markoffset(scanmark(&cp)); cp && offset != para && (!ending || spaces<o_sentencegap || !CHARchr(end,*cp)); scanprev(&cp), offset--) { if (*cp == '\n') { spaces = o_sentencegap; ending = True; anynext = anythis; anythis = False; } else if (isspace(*cp)) { spaces++, ending = True; } else if (!CHARchr(quote, *cp)) { first = ending = False; anythis = True; spaces = 0; } else { anythis = True; } } /* If this sentence is actually a paragraph start, and we * still have more sentences to move over, then find the * next higher paragraph. */ if (offset == para && count > 1) { vinf->count = 1; para = (m_bsection(win, vinf) == RESULT_COMPLETE) ? markoffset(win->state->cursor) : -1; } /* If this sentence ender was encountered before any * mid-sentence text (i.e., if we started at the front of * a sentence) then we should go back one extra sentence-end. */ if (first && offset != para) { count++; } } /* If we hit a paragraph top which occurs on a blank line, then we * need to do a little extra processing because we exited the loop * a little too soon. */ if (offset == para && cp && *cp == '\n') { anynext = anythis; } /* did we find it? */ if (cp) { /* move the cursor to the start of the next sentence */ if (offset > para) { /* found a sentence end -- move the cursor to the to * start of the following sentence. */ do { scannext(&cp); assert(cp); } while (isspace(*cp) || CHARchr(quote, *cp)); marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); } else if (anynext) { /* bumped into a paragraph start after scanning some * sentence text -- move the cursor to the first text * on the next non-blank line. */ while (cp && *cp != '\n') { scannext(&cp); } while (cp && isspace(*cp)) { scannext(&cp); } if (&cp) marksetoffset(win->state->cursor, markoffset(scanmark(&cp))); else { marksetoffset(win->state->cursor, o_bufchars(markbuffer(win->state->cursor)) - 2); if (markoffset(win->state->cursor) < 0) marksetoffset(win->state->cursor, 0); } } else { /* found a paragraph start -- leave the cursor there */ offset = para; marksetoffset(win->state->cursor, para); } result = RESULT_COMPLETE; } else if (count == 0) { /* move the cursor to the first character of the buffer */ marksetoffset(win->state->cursor, 0); result = RESULT_COMPLETE; } else { /* tried to move past beginning of buffer */ result = RESULT_ERROR; } scanfree(&cp); return result; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.