585 lines
10 KiB
C
585 lines
10 KiB
C
/* move1.c */
|
|
|
|
/* Author:
|
|
* Steve Kirkendall
|
|
* 14407 SW Teal Blvd. #C
|
|
* Beaverton, OR 97005
|
|
* kirkenda@cs.pdx.edu
|
|
*/
|
|
|
|
|
|
/* This file contains most movement functions */
|
|
|
|
#include "config.h"
|
|
#include "vi.h"
|
|
#include "ctype.h"
|
|
|
|
MARK m_updnto(m, cnt, cmd)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument */
|
|
char cmd; /* the command character */
|
|
{
|
|
DEFAULT(cmd == 'G' ? nlines : 1L);
|
|
|
|
/* move up or down 'cnt' lines */
|
|
switch (cmd)
|
|
{
|
|
case ctrl('P'):
|
|
case '-':
|
|
case 'k':
|
|
m -= MARK_AT_LINE(cnt);
|
|
break;
|
|
|
|
case 'G':
|
|
if (cnt < 1L || cnt > nlines)
|
|
{
|
|
msg("Only %ld lines", nlines);
|
|
return MARK_UNSET;
|
|
}
|
|
m = MARK_AT_LINE(cnt);
|
|
break;
|
|
|
|
case '_':
|
|
cnt--;
|
|
/* fall through... */
|
|
|
|
default:
|
|
m += MARK_AT_LINE(cnt);
|
|
}
|
|
|
|
/* if that left us screwed up, then fail */
|
|
if (m < MARK_FIRST || markline(m) > nlines)
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_right(m, cnt, key, prevkey)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument */
|
|
int key; /* movement keystroke */
|
|
int prevkey;/* operator keystroke, or 0 if none */
|
|
{
|
|
int idx; /* index of the new cursor position */
|
|
|
|
DEFAULT(1);
|
|
|
|
/* If used with an operator, then move 1 less character, since the 'l'
|
|
* command includes the character that it moves onto.
|
|
*/
|
|
if (prevkey != '\0')
|
|
{
|
|
cnt--;
|
|
}
|
|
|
|
/* move to right, if that's OK */
|
|
pfetch(markline(m));
|
|
idx = markidx(m) + cnt;
|
|
if (idx < plen)
|
|
{
|
|
m += cnt;
|
|
}
|
|
else
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_left(m, cnt)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument */
|
|
{
|
|
DEFAULT(1);
|
|
|
|
/* move to the left, if that's OK */
|
|
if (markidx(m) >= cnt)
|
|
{
|
|
m -= cnt;
|
|
}
|
|
else
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_tocol(m, cnt, cmd)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument */
|
|
int cmd; /* either ctrl('X') or '|' */
|
|
{
|
|
char *text; /* text of the line */
|
|
int col; /* column number */
|
|
int idx; /* index into the line */
|
|
|
|
|
|
/* if doing ^X, then adjust for sideways scrolling */
|
|
if (cmd == ctrl('X'))
|
|
{
|
|
DEFAULT(*o_columns & 0xff);
|
|
cnt += leftcol;
|
|
}
|
|
else
|
|
{
|
|
DEFAULT(1);
|
|
}
|
|
|
|
/* internally, columns are numbered 0..COLS-1, not 1..COLS */
|
|
cnt--;
|
|
|
|
/* if 0, that's easy */
|
|
if (cnt == 0)
|
|
{
|
|
m &= ~(BLKSIZE - 1);
|
|
return m;
|
|
}
|
|
|
|
/* find that column within the line */
|
|
pfetch(markline(m));
|
|
text = ptext;
|
|
for (col = idx = 0; col < cnt && *text; text++, idx++)
|
|
{
|
|
if (*text == '\t' && !*o_list)
|
|
{
|
|
col += *o_tabstop;
|
|
col -= col % *o_tabstop;
|
|
}
|
|
else if (UCHAR(*text) < ' ' || *text == '\177')
|
|
{
|
|
col += 2;
|
|
}
|
|
#ifndef NO_CHARATTR
|
|
else if (text[0] == '\\' && text[1] == 'f' && text[2] && *o_charattr)
|
|
{
|
|
text += 2; /* plus one more as part of for loop */
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
col++;
|
|
}
|
|
}
|
|
if (!*text)
|
|
{
|
|
/* the desired column was past the end of the line, so
|
|
* act like the user pressed "$" instead.
|
|
*/
|
|
return m | (BLKSIZE - 1);
|
|
}
|
|
else
|
|
{
|
|
m = (m & ~(BLKSIZE - 1)) + idx;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_front(m, cnt)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument (ignored) */
|
|
{
|
|
char *scan;
|
|
|
|
/* move to the first non-whitespace character */
|
|
pfetch(markline(m));
|
|
scan = ptext;
|
|
m &= ~(BLKSIZE - 1);
|
|
while (*scan == ' ' || *scan == '\t')
|
|
{
|
|
scan++;
|
|
m++;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_rear(m, cnt)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument (ignored) */
|
|
{
|
|
/* Try to move *EXTREMELY* far to the right. It is fervently hoped
|
|
* that other code will convert this to a more reasonable MARK before
|
|
* anything tries to actually use it. (See adjmove() in vi.c)
|
|
*/
|
|
return m | (BLKSIZE - 1);
|
|
}
|
|
|
|
#ifndef NO_SENTENCE
|
|
static int isperiod(ptr)
|
|
char *ptr; /* pointer to possible sentence-ender */
|
|
{
|
|
/* if not '.', '?', or '!', then it isn't a sentence ender */
|
|
if (*ptr != '.' && *ptr != '?' && *ptr != '!')
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/* skip any intervening ')', ']', or '"' characters */
|
|
do
|
|
{
|
|
ptr++;
|
|
} while (*ptr == ')' || *ptr == ']' || *ptr == '"');
|
|
|
|
/* do we have two spaces or EOL? */
|
|
if (!*ptr || ptr[0] == ' ' && ptr[1] == ' ')
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_sentence(m, cnt, cmd)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument */
|
|
int cmd; /* either '(' or ')' */
|
|
{
|
|
REG char *text;
|
|
REG long l;
|
|
|
|
DEFAULT(1);
|
|
|
|
/* If '(' command, then move back one word, so that if we hit '(' at
|
|
* the start of a sentence we don't simply stop at the end of the
|
|
* previous sentence and bounce back to the start of this one again.
|
|
*/
|
|
if (cmd == '(')
|
|
{
|
|
m = m_bword(m, 1L, 'b');
|
|
if (!m)
|
|
{
|
|
return m;
|
|
}
|
|
}
|
|
|
|
/* get the current line */
|
|
l = markline(m);
|
|
pfetch(l);
|
|
text = ptext + markidx(m);
|
|
|
|
/* for each requested sentence... */
|
|
while (cnt-- > 0)
|
|
{
|
|
/* search forward for one of [.?!] followed by spaces or EOL */
|
|
do
|
|
{
|
|
if (cmd == ')')
|
|
{
|
|
/* move forward, wrap at end of line */
|
|
if (!text[0])
|
|
{
|
|
if (l >= nlines)
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
l++;
|
|
pfetch(l);
|
|
text = ptext;
|
|
}
|
|
else
|
|
{
|
|
text++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* move backward, wrap at beginning of line */
|
|
if (text == ptext)
|
|
{
|
|
do
|
|
{
|
|
if (l <= 1)
|
|
{
|
|
return MARK_FIRST;
|
|
}
|
|
l--;
|
|
pfetch(l);
|
|
} while (!*ptext);
|
|
text = ptext + plen - 1;
|
|
}
|
|
else
|
|
{
|
|
text--;
|
|
}
|
|
}
|
|
} while (!isperiod(text));
|
|
}
|
|
|
|
/* construct a mark for this location */
|
|
m = buildmark(text);
|
|
|
|
/* move forward to the first word of the next sentence */
|
|
m = m_fword(m, 1L, 'w', '\0');
|
|
|
|
return m;
|
|
}
|
|
#endif
|
|
|
|
MARK m_paragraph(m, cnt, cmd)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument */
|
|
int cmd; /* either '{' or '}' */
|
|
{
|
|
char *text; /* text of the current line */
|
|
char *pscn; /* used to scan thru value of "paragraphs" option */
|
|
long l, ol; /* current line number, original line number */
|
|
int dir; /* -1 if we're moving up, or 1 if down */
|
|
char col0; /* character to expect in column 0 */
|
|
#ifndef NO_SENTENCE
|
|
# define SENTENCE(x) (x)
|
|
char *list; /* either o_sections or o_paragraph */
|
|
#else
|
|
# define SENTENCE(x)
|
|
#endif
|
|
|
|
DEFAULT(1);
|
|
|
|
/* set the direction, based on the command */
|
|
switch (cmd)
|
|
{
|
|
case '{':
|
|
dir = -1;
|
|
col0 = '\0';
|
|
SENTENCE(list = o_paragraphs);
|
|
break;
|
|
|
|
case '}':
|
|
dir = 1;
|
|
col0 = '\0';
|
|
SENTENCE(list = o_paragraphs);
|
|
break;
|
|
|
|
case '[':
|
|
if (getkey(0) != '[')
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
dir = -1;
|
|
col0 = '{';
|
|
SENTENCE(list = o_sections);
|
|
break;
|
|
|
|
case ']':
|
|
if (getkey(0) != ']')
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
dir = 1;
|
|
col0 = '{';
|
|
SENTENCE(list = o_sections);
|
|
break;
|
|
}
|
|
ol = l = markline(m);
|
|
|
|
/* for each paragraph that we want to travel through... */
|
|
while (l > 0 && l <= nlines && cnt-- > 0)
|
|
{
|
|
/* skip blank lines between paragraphs */
|
|
while (l > 0 && l <= nlines && col0 == *(text = fetchline(l)))
|
|
{
|
|
l += dir;
|
|
}
|
|
|
|
/* skip non-blank lines that aren't paragraph separators
|
|
*/
|
|
do
|
|
{
|
|
#ifndef NO_SENTENCE
|
|
if (*text == '.' && l != ol)
|
|
{
|
|
for (pscn = list; pscn[0] && pscn[1]; pscn += 2)
|
|
{
|
|
if (pscn[0] == text[1] && pscn[1] == text[2])
|
|
{
|
|
pscn = (char *)0;
|
|
goto BreakBreak;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
l += dir;
|
|
} while (l > 0 && l <= nlines && col0 != *(text = fetchline(l)));
|
|
BreakBreak: ;
|
|
}
|
|
|
|
if (l > nlines)
|
|
{
|
|
m = MARK_LAST;
|
|
}
|
|
else if (l <= 0)
|
|
{
|
|
m = MARK_FIRST;
|
|
}
|
|
else
|
|
{
|
|
m = MARK_AT_LINE(l);
|
|
}
|
|
return m;
|
|
}
|
|
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_match(m, cnt)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* a numeric argument (normally 0) */
|
|
{
|
|
long l;
|
|
REG char *text;
|
|
REG char match;
|
|
REG char nest;
|
|
REG int count;
|
|
|
|
#ifndef NO_EXTENSIONS
|
|
/* if we're given a number, then treat it as a percentage of the file */
|
|
if (cnt > 0)
|
|
{
|
|
/* make sure it is a reasonable number */
|
|
if (cnt > 100)
|
|
{
|
|
msg("can only be from 1%% to 100%%");
|
|
return MARK_UNSET;
|
|
}
|
|
|
|
/* return the appropriate line number */
|
|
l = (nlines - 1L) * cnt / 100L + 1L;
|
|
return MARK_AT_LINE(l);
|
|
}
|
|
#endif /* undef NO_EXTENSIONS */
|
|
|
|
/* get the current line */
|
|
l = markline(m);
|
|
pfetch(l);
|
|
text = ptext + markidx(m);
|
|
|
|
/* search forward within line for one of "[](){}" */
|
|
for (match = '\0'; !match && *text; text++)
|
|
{
|
|
/* tricky way to recognize 'em in ASCII */
|
|
nest = *text;
|
|
if ((nest & 0xdf) == ']' || (nest & 0xdf) == '[')
|
|
{
|
|
match = nest ^ ('[' ^ ']');
|
|
}
|
|
else if ((nest & 0xfe) == '(')
|
|
{
|
|
match = nest ^ ('(' ^ ')');
|
|
}
|
|
else
|
|
{
|
|
match = 0;
|
|
}
|
|
}
|
|
if (!match)
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
text--;
|
|
|
|
/* search forward or backward for match */
|
|
if (match == '(' || match == '[' || match == '{')
|
|
{
|
|
/* search backward */
|
|
for (count = 1; count > 0; )
|
|
{
|
|
/* wrap at beginning of line */
|
|
if (text == ptext)
|
|
{
|
|
do
|
|
{
|
|
if (l <= 1L)
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
l--;
|
|
pfetch(l);
|
|
} while (!*ptext);
|
|
text = ptext + plen - 1;
|
|
}
|
|
else
|
|
{
|
|
text--;
|
|
}
|
|
|
|
/* check the char */
|
|
if (*text == match)
|
|
count--;
|
|
else if (*text == nest)
|
|
count++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* search forward */
|
|
for (count = 1; count > 0; )
|
|
{
|
|
/* wrap at end of line */
|
|
if (!*text)
|
|
{
|
|
if (l >= nlines)
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
l++;
|
|
pfetch(l);
|
|
text = ptext;
|
|
}
|
|
else
|
|
{
|
|
text++;
|
|
}
|
|
|
|
/* check the char */
|
|
if (*text == match)
|
|
count--;
|
|
else if (*text == nest)
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/* construct a mark for this place */
|
|
m = buildmark(text);
|
|
return m;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
MARK m_tomark(m, cnt, key)
|
|
MARK m; /* movement is relative to this mark */
|
|
long cnt; /* (ignored) */
|
|
int key; /* keystroke - the mark to move to */
|
|
{
|
|
/* mark '' is a special case */
|
|
if (key == '\'' || key == '`')
|
|
{
|
|
if (mark[26] == MARK_UNSET)
|
|
{
|
|
return MARK_FIRST;
|
|
}
|
|
else
|
|
{
|
|
return mark[26];
|
|
}
|
|
}
|
|
|
|
/* if not a valid mark number, don't move */
|
|
if (key < 'a' || key > 'z')
|
|
{
|
|
return MARK_UNSET;
|
|
}
|
|
|
|
/* return the selected mark -- may be MARK_UNSET */
|
|
if (!mark[key - 'a'])
|
|
{
|
|
msg("mark '%c is unset", key);
|
|
}
|
|
return mark[key - 'a'];
|
|
}
|
|
|