/* input.c */ /* Author: * Steve Kirkendall * 14407 SW Teal Blvd. #C * Beaverton, OR 97005 * kirkenda@cs.pdx.edu */ /* This file contains the input() function, which implements vi's INPUT mode. * It also contains the code that supports digraphs. */ #include "config.h" #include "ctype.h" #include "vi.h" #ifndef NO_DIGRAPH static struct _DIG { struct _DIG *next; char key1; char key2; char dig; char save; } *digs; char digraph(key1, key2) char key1; /* the underlying character */ char key2; /* the second character */ { int newkey; REG struct _DIG *dp; /* if digraphs are disabled, then just return the new char */ if (!*o_digraph) { return key2; } /* remember the new key, so we can return it if this isn't a digraph */ newkey = key2; /* sort key1 and key2, so that their original order won't matter */ if (key1 > key2) { key2 = key1; key1 = newkey; } /* scan through the digraph chart */ for (dp = digs; dp && (dp->key1 != key1 || dp->key2 != key2); dp = dp->next) { } /* if this combination isn't in there, just use the new key */ if (!dp) { return newkey; } /* else use the digraph key */ return dp->dig; } /* this function lists or defines digraphs */ void do_digraph(bang, extra) int bang; char extra[]; { int dig; REG struct _DIG *dp; struct _DIG *prev; static int user_defined = FALSE; /* boolean: are all later digraphs user-defined? */ char listbuf[8]; /* if "extra" is NULL, then we've reached the end of the built-ins */ if (!extra) { user_defined = TRUE; return; } /* if no args, then display the existing digraphs */ if (*extra < ' ') { listbuf[0] = listbuf[1] = listbuf[2] = listbuf[5] = ' '; listbuf[7] = '\0'; for (dig = 0, dp = digs; dp; dp = dp->next) { if (dp->save || bang) { dig += 7; if (dig >= COLS) { addch('\n'); exrefresh(); dig = 7; } listbuf[3] = dp->key1; listbuf[4] = dp->key2; listbuf[6] = dp->dig; qaddstr(listbuf); } } addch('\n'); exrefresh(); return; } /* make sure we have at least two characters */ if (!extra[1]) { msg("Digraphs must be composed of two characters"); return; } /* sort key1 and key2, so that their original order won't matter */ if (extra[0] > extra[1]) { dig = extra[0]; extra[0] = extra[1]; extra[1] = dig; } /* locate the new digraph character */ for (dig = 2; extra[dig] == ' ' || extra[dig] == '\t'; dig++) { } dig = extra[dig]; if (!bang && dig) { dig |= 0x80; } /* search for the digraph */ for (prev = (struct _DIG *)0, dp = digs; dp && (dp->key1 != extra[0] || dp->key2 != extra[1]); prev = dp, dp = dp->next) { } /* deleting the digraph? */ if (!dig) { if (!dp) { #ifndef CRUNCH msg("%c%c not a digraph", extra[0], extra[1]); #endif return; } if (prev) prev->next = dp->next; else digs = dp->next; free(dp); return; } /* if necessary, create a new digraph struct for the new digraph */ if (dig && !dp) { dp = (struct _DIG *)malloc(sizeof *dp); if (!dp) { msg("Out of space in the digraph table"); return; } if (prev) prev->next = dp; else digs = dp; dp->next = (struct _DIG *)0; } /* assign it the new digraph value */ dp->key1 = extra[0]; dp->key2 = extra[1]; dp->dig = dig; dp->save = user_defined; } # ifndef NO_MKEXRC void savedigs(fd) int fd; { static char buf[] = "digraph! XX Y\n"; REG struct _DIG *dp; for (dp = digs; dp; dp = dp->next) { if (dp->save) { buf[9] = dp->key1; buf[10] = dp->key2; buf[12] = dp->dig; write(fd, buf, (unsigned)14); } } } # endif #endif /* This function allows the user to replace an existing (possibly zero-length) * chunk of text with typed-in text. It returns the MARK of the last character * that the user typed in. */ MARK input(from, to, when, above) MARK from; /* where to start inserting text */ MARK to; /* extent of text to delete */ int when; /* either WHEN_VIINP or WHEN_VIREP */ int above; /* boolean: take indentation from lower line? */ { char key[2]; /* key char followed by '\0' char */ char *build; /* used in building a newline+indent string */ char *scan; /* used while looking at the indent chars of a line */ MARK m; /* some place in the text */ #ifndef NO_EXTENSIONS int quit = FALSE; /* boolean: are we exiting after this? */ int inchg; /* boolean: have we done a "beforedo()" yet? */ #endif #ifdef DEBUG /* if "from" and "to" are reversed, complain */ if (from > to) { msg("ERROR: input(%ld:%d, %ld:%d)", markline(from), markidx(from), markline(to), markidx(to)); return MARK_UNSET; } #endif key[1] = 0; /* if we're replacing text with new text, save the old stuff */ /* (Alas, there is no easy way to save text for replace mode) */ if (from != to) { cut(from, to); } /* if doing a dot command, then reuse the previous text */ if (doingdot) { ChangeText { /* delete the text that's there now */ if (from != to) { delete(from, to); } /* insert the previous text */ cutname('.'); cursor = paste(from, FALSE, TRUE) + 1L; } } else /* interactive version */ { /* assume that whoever called this already did a beforedo() */ #ifndef NO_EXTENSIONS inchg = TRUE; #endif /* if doing a change within the line... */ if (from != to && markline(from) == markline(to)) { /* mark the end of the text with a "$" */ change(to - 1, to, "$"); } else { /* delete the old text right off */ if (from != to) { delete(from, to); } to = from; } /* handle autoindent of the first line, maybe */ cursor = from; m = (above ? (cursor + BLKSIZE) : (cursor - BLKSIZE)); if (*o_autoindent && markidx(m) == 0 && markline(m) >= 1L && markline(m) <= nlines) { /* Only autoindent blank lines. */ pfetch(markline(cursor)); if (plen == 0) { /* Okay, we really want to autoindent */ pfetch(markline(m)); for (scan = ptext, build = tmpblk.c; *scan == ' ' || *scan == '\t'; ) { *build++ = *scan++; } if (build > tmpblk.c) { *build = '\0'; add(cursor, tmpblk.c); cursor += (build - tmpblk.c); if (cursor > to) to = cursor; } } } /* repeatedly add characters from the user */ for (;;) { /* Get a character */ redraw(cursor, TRUE); #ifdef DEBUG2 msg("cursor=%ld.%d, to=%ld.%d", markline(cursor), markidx(cursor), markline(to), markidx(to)); #endif #ifndef NO_ABBR pfetch(markline(cursor)); build = ptext; if (pline == markline(from)) build += markidx(from); for (scan = ptext + markidx(cursor); --scan >= build && isalnum(*scan); ) { } scan++; key[0] = getabkey(when, scan, (int)(ptext + markidx(cursor) - scan)); #else key[0] = getkey(when); #endif #ifndef NO_VISIBLE if (key[0] != '\0' && V_from != MARK_UNSET) { msg("Can't modify text during a selection"); beep(); continue; } #endif #ifndef NO_EXTENSIONS if (key[0] == ctrl('O')) { if (inchg) { if (cursor < to) { delete(cursor, to); redraw(cursor, TRUE); } afterdo(); inchg = FALSE; } } else if (key[0] != ctrl('[')) { if (!inchg) { beforedo(FALSE); inchg = TRUE; } } #endif #ifndef CRUNCH /* if wrapmargin is set & we're past the * warpmargin, then change the last whitespace * characters on line into a newline */ if (*o_wrapmargin != 0) { pfetch(markline(cursor)); if (idx2col(cursor, ptext, TRUE) > COLS - (*o_wrapmargin & 0xff)) { build = tmpblk.c; *build++ = '\n'; if (*o_autoindent) { /* figure out indent for next line */ for (scan = ptext; *scan == ' ' || *scan == '\t'; ) { *build++ = *scan++; } } *build = '\0'; scan = ptext + plen; m = cursor & ~(BLKSIZE - 1); while (ptext < scan) { scan--; if (*scan != ' ' && *scan != '\t') continue; /*break up line, and we do autoindent if needed*/ change(m + (scan - ptext), m + (scan - ptext) + 1, tmpblk.c); cursor = (cursor & ~(BLKSIZE - 1)) + BLKSIZE + strlen(tmpblk.c) - 1 + plen - (scan - ptext) - 1; /*remove trailing spaces on previous line*/ pfetch(markline(m)); scan = ptext + plen; while (ptext < scan) { scan--; if (*scan != ' ' && *scan != '\t') break; } delete(m + (scan-ptext) + 1, m + plen); break; } } } #endif /* !CRUNCH */ /* process it */ switch (*key) { #ifndef NO_EXTENSIONS case ctrl('O'): /* special movement mapped keys */ *key = getkey(0); switch (*key) { case 'h': m = m_left(cursor, 0L); break; case 'j': case 'k': m = m_updnto(cursor, 0L, *key); break; case 'l': m = cursor + 1; break; case 'B': case 'b': m = m_bword(cursor, 0L, *key); break; case 'W': case 'w': m = m_fword(cursor, 0L, *key, '\0'); break; case '^': m = m_front(cursor, 0L); break; case '$': m = m_rear(cursor, 0L); break; case ctrl('B'): case ctrl('F'): m = m_scroll(cursor, 0L, *key); break; case 'x': #ifndef NO_VISIBLE if (V_from) beep(); else #endif ChangeText { m = v_xchar(cursor, 0L, 'x'); } break; case 'i': m = to = from = cursor; when = WHEN_VIINP + WHEN_VIREP - when; break; case 'K': pfetch(markline(cursor)); changes++; /* <- after this, we can alter ptext */ ptext[markidx(cursor)] = 0; for (scan = ptext + markidx(cursor) - 1; scan >= ptext && isalnum(*scan); scan--) { } scan++; m = (*scan ? v_keyword(scan, cursor, 0L) : cursor); break; # ifndef NO_VISIBLE case 'v': case 'V': if (V_from) V_from = MARK_UNSET; else V_from = cursor; m = from = to = cursor; V_linemd = (*key == 'V'); break; case 'd': case 'y': case '\\': /* do nothing if unmarked */ if (!V_from) { msg("You must mark the text first"); beep(); break; } /* "from" must come before "to" */ if (V_from < cursor) { from = V_from; to = cursor; } else { from = cursor; to = V_from; } /* we don't need V_from anymore */ V_from = MARK_UNSET; if (V_linemd) { /* adjust for line mode */ from &= ~(BLKSIZE - 1); to |= (BLKSIZE - 1); } else { /* in character mode, we must * worry about deleting the newline * at the end of the last line */ pfetch(markline(to)); if (markidx(to) == plen) to |= (BLKSIZE - 1); } to++; switch (*key) { case 'y': cut(from, to); break; case 'd': ChangeText { cut(from, to); delete(from, to); } cursor = from; break; #ifndef NO_POPUP case '\\': ChangeText { cursor = v_popup(from, to); } break; #endif } m = from = to = cursor; break; case 'p': case 'P': V_from = MARK_UNSET; ChangeText { m = from = to = cursor = paste(cursor, (*key == 'p'), FALSE); } break; # endif /* !NO_VISIBLE */ default: m = MARK_UNSET; } /* adjust the moved cursor */ if (m != cursor) { m = adjmove(cursor, m, (*key == 'j' || *key == 'k' ? 0x20 : 0)); if (*key == '$' || (*key == 'l' && m <= cursor)) { m++; } } /* if the cursor is reasonable, use it */ if (m == MARK_UNSET) { beep(); } else { from = to = cursor = m; } break; case ctrl('Z'): if (getkey(0) == ctrl('Z')) { quit = TRUE; goto BreakBreak; } break; #endif case ctrl('['): /* if last line contains only whitespace, then remove whitespace */ if (*o_autoindent) { pfetch(markline(cursor)); for (scan = ptext; isspace(*scan); scan++) { } if (scan > ptext && !*scan) { cursor &= ~(BLKSIZE - 1L); if (to < cursor + plen) { to = cursor + plen; } } } goto BreakBreak; case ctrl('U'): if (markline(cursor) == markline(from)) { cursor = from; } else { cursor &= ~(BLKSIZE - 1); } break; case ctrl('D'): case ctrl('T'): if (to > cursor) { delete(cursor, to); } mark[27] = cursor; cmd_shift(cursor, cursor, *key == ctrl('D') ? CMD_SHIFTL : CMD_SHIFTR, TRUE, ""); if (mark[27]) { cursor = mark[27]; } else { cursor = m_front(cursor, 0L); } to = cursor; break; case '\b': if (cursor <= from) { beep(); } else if (markidx(cursor) == 0) { cursor -= BLKSIZE; pfetch(markline(cursor)); cursor += plen; } else { cursor--; } break; case ctrl('W'): m = m_bword(cursor, 1L, 'b'); if (markline(m) == markline(cursor) && m >= from) { cursor = m; if (from > cursor) { from = cursor; } } else { beep(); } break; case '\n': #if OSK case '\l': #else case '\r': #endif build = tmpblk.c; *build++ = '\n'; if (*o_autoindent) { /* figure out indent for next line */ pfetch(markline(cursor)); for (scan = ptext; *scan == ' ' || *scan == '\t'; ) { *build++ = *scan++; } /* remove indent from this line, if blank */ if ((scan - ptext) >= markidx(cursor) && plen > 0) { to = cursor &= ~(BLKSIZE - 1); delete(cursor, cursor + plen); } } *build = 0; if (cursor >= to && when != WHEN_VIREP) { add(cursor, tmpblk.c); } else { change(cursor, to, tmpblk.c); } redraw(cursor, TRUE); to = cursor = (cursor & ~(BLKSIZE - 1)) + BLKSIZE + (int)(build - tmpblk.c) - 1; break; case ctrl('A'): case ctrl('P'): if (cursor < to) { delete(cursor, to); } if (*key == ctrl('A')) { cutname('.'); } to = cursor = paste(cursor, FALSE, TRUE) + 1L; break; case ctrl('V'): if (cursor >= to && when != WHEN_VIREP) { add(cursor, "^"); } else { change(cursor, to, "^"); to = cursor + 1; } redraw(cursor, TRUE); *key = getkey(0); if (*key == '\n') { /* '\n' too hard to handle */ #if OSK *key = '\l'; #else *key = '\r'; #endif } change(cursor, cursor + 1, key); cursor++; if (cursor > to) { to = cursor; } break; case ctrl('L'): case ctrl('R'): redraw(MARK_UNSET, FALSE); break; default: if (cursor >= to && when != WHEN_VIREP) { add(cursor, key); cursor++; to = cursor; } else { pfetch(markline(cursor)); if (markidx(cursor) == plen) { add(cursor, key); } else { #ifndef NO_DIGRAPH *key = digraph(ptext[markidx(cursor)], *key); #endif change(cursor, cursor + 1, key); } cursor++; } #ifndef NO_SHOWMATCH /* show matching "({[" if necessary */ if (*o_showmatch && strchr(")}]", *key)) { redraw(cursor, TRUE); m = m_match(cursor - 1, 0L); if (markline(m) >= topline && markline(m) <= botline) { redraw(m, TRUE); refresh(); sleep(1); } } #endif } /* end switch(*key) */ } /* end for(;;) */ BreakBreak:; /* delete any excess characters */ if (cursor < to) { #ifndef NO_EXTENSIONS /* if we aren't in the middle of a change, start one! */ if (!inchg) { beforedo(FALSE); inchg = TRUE; } #endif delete(cursor, to); } } /* end if doingdot else */ /* put the new text into a cut buffer for possible reuse */ if (!doingdot) { blksync(); cutname('.'); cut(from, cursor); } /* move to last char that we inputted, unless it was newline */ if (markidx(cursor) != 0) { cursor--; } redraw(cursor, FALSE); #ifndef NO_EXTENSIONS if (quit) { /* if this is a nested "do", then cut it short */ abortdo(); /* exit, unless we can't write out the file */ cursor = v_xit(cursor, 0L, 'Z'); } #endif rptlines = 0L; return cursor; }