This is getchar.c in view mode; [Download] [Up]
/* vi:ts=4:sw=4 * * VIM - Vi IMproved by Bram Moolenaar * * Read the file "credits.txt" for a list of people who contributed. * Read the file "uganda.txt" for copying and usage conditions. */ /* * getchar.c * * functions related with getting a character from the user/mapping/redo/... * * manipulations with redo buffer and stuff buffer * mappings and abbreviations */ #include "vim.h" #include "globals.h" #include "proto.h" #include "param.h" /* * structure used to store one block of the stuff/redo/macro buffers */ struct bufblock { struct bufblock *b_next; /* pointer to next bufblock */ char_u b_str[1]; /* contents (actually longer) */ }; #define MINIMAL_SIZE 20 /* minimal size for b_str */ /* * header used for the stuff buffer and the redo buffer */ struct buffheader { struct bufblock bh_first; /* first (dummy) block of list */ struct bufblock *bh_curr; /* bufblock for appending */ int bh_index; /* index for reading */ int bh_space; /* space in bh_curr for appending */ }; static struct buffheader stuffbuff = {{NULL, {NUL}}, NULL, 0, 0}; static struct buffheader redobuff = {{NULL, {NUL}}, NULL, 0, 0}; static struct buffheader recordbuff = {{NULL, {NUL}}, NULL, 0, 0}; /* * when block_redo is TRUE redo buffer will not be changed * used by edit() to repeat insertions and 'V' command for redoing */ static int block_redo = FALSE; /* * structure used for mapping */ struct mapblock { struct mapblock *m_next; /* next mapblock */ char_u *m_keys; /* mapped from */ int m_keylen; /* strlen(m_keys) */ char_u *m_str; /* mapped to */ int m_mode; /* valid mode */ int m_noremap; /* if non-zero no re-mapping for m_str */ }; static struct mapblock maplist = {NULL, NULL, 0, NULL, 0, 0}; /* first dummy entry in maplist */ /* * variables used by vgetorpeek() and flush_buffers() * * typestr contains all characters that are not consumed yet. * The part in front may contain the result of mappings, abbreviations and * @a commands. The lenght of this part is typemaplen. * After it are characters that come from the terminal. * no_abbr_cnt is the number of characters in typestr that should not be * considered for abbreviations. * Some parts of typestr may not be mapped. These parts are remembered in * the noremaplist. */ #define MAXMAPLEN 50 /* maximum length of key sequence to be mapped */ /* must be able to hold an Amiga resize report */ static char_u *typestr = NULL; /* NUL-terminated buffer for typeahead characters */ static char_u typebuf[MAXMAPLEN + 3]; /* initial typestr */ static int typemaplen = 0; /* number of mapped characters in typestr */ static int no_abbr_cnt = 0; /* number of chars without abbrev. in typestr */ /* * parts int typestr that should not be mapped are remembered with a list * of noremap structs. Noremaplist is the first. */ struct noremap { int nr_off; /* offset to not remappable chars */ int nr_len; /* number of not remappable chars */ struct noremap *nr_next; /* next entry in the list */ }; static struct noremap noremaplist = {0, 0, NULL}; static void free_buff __ARGS((struct buffheader *)); static char_u *get_bufcont __ARGS((struct buffheader *, int)); static void add_buff __ARGS((struct buffheader *, char_u *)); static void add_num_buff __ARGS((struct buffheader *, long)); static void add_char_buff __ARGS((struct buffheader *, int)); static int read_stuff __ARGS((int)); static void start_stuff __ARGS((void)); static int read_redo __ARGS((int)); static void init_typestr __ARGS((void)); static void gotchars __ARGS((char_u *, int)); static int vgetorpeek __ARGS((int)); static void showmap __ARGS((struct mapblock *)); /* * free and clear a buffer */ static void free_buff(buf) struct buffheader *buf; { register struct bufblock *p, *np; for (p = buf->bh_first.b_next; p != NULL; p = np) { np = p->b_next; free(p); } buf->bh_first.b_next = NULL; } /* * return the contents of a buffer as a single string */ static char_u * get_bufcont(buffer, dozero) struct buffheader *buffer; int dozero; /* count == zero is not an error */ { long_u count = 0; char_u *p = NULL; struct bufblock *bp; /* compute the total length of the string */ for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) count += STRLEN(bp->b_str); if ((count || dozero) && (p = lalloc(count + 1, TRUE)) != NULL) { *p = NUL; for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) strcat((char *)p, (char *)bp->b_str); } return (p); } /* * return the contents of the record buffer as a single string * and clear the record buffer */ char_u * get_recorded() { char_u *p; p = get_bufcont(&recordbuff, TRUE); free_buff(&recordbuff); return (p); } /* * return the contents of the redo buffer as a single string */ char_u * get_inserted() { return(get_bufcont(&redobuff, FALSE)); } /* * add string "s" after the current block of buffer "buf" */ static void add_buff(buf, s) register struct buffheader *buf; char_u *s; { struct bufblock *p; long_u n; long_u len; if ((n = STRLEN(s)) == 0) /* don't add empty strings */ return; if (buf->bh_first.b_next == NULL) /* first add to list */ { buf->bh_space = 0; buf->bh_curr = &(buf->bh_first); } else if (buf->bh_curr == NULL) /* buffer has already been read */ { EMSG("Add to read buffer"); return; } else if (buf->bh_index != 0) STRCPY(buf->bh_first.b_next->b_str, buf->bh_first.b_next->b_str + buf->bh_index); buf->bh_index = 0; if (buf->bh_space >= n) { strcat((char *)buf->bh_curr->b_str, (char *)s); buf->bh_space -= n; } else { if (n < MINIMAL_SIZE) len = MINIMAL_SIZE; else len = n; p = (struct bufblock *)lalloc((long_u)(sizeof(struct bufblock) + len), TRUE); if (p == NULL) return; /* no space, just forget it */ buf->bh_space = len - n; STRCPY(p->b_str, s); p->b_next = buf->bh_curr->b_next; buf->bh_curr->b_next = p; buf->bh_curr = p; } return; } static void add_num_buff(buf, n) struct buffheader *buf; long n; { char_u number[32]; sprintf((char *)number, "%ld", n); add_buff(buf, number); } static void add_char_buff(buf, c) struct buffheader *buf; int c; { char_u temp[2]; temp[0] = c; temp[1] = NUL; add_buff(buf, temp); } /* * get one character from the stuff buffer * If advance == TRUE go to the next char. */ static int read_stuff(advance) int advance; { register char_u c; register struct bufblock *curr; if (stuffbuff.bh_first.b_next == NULL) /* buffer is empty */ return NUL; curr = stuffbuff.bh_first.b_next; c = curr->b_str[stuffbuff.bh_index]; if (advance) { if (curr->b_str[++stuffbuff.bh_index] == NUL) { stuffbuff.bh_first.b_next = curr->b_next; free(curr); stuffbuff.bh_index = 0; } } return c; } /* * prepare stuff buffer for reading (if it contains something) */ static void start_stuff() { if (stuffbuff.bh_first.b_next != NULL) { stuffbuff.bh_curr = &(stuffbuff.bh_first); stuffbuff.bh_space = 0; } } /* * check if the stuff buffer is empty */ int stuff_empty() { return (stuffbuff.bh_first.b_next == NULL); } /* * Remove the contents of the stuff buffer and the mapped characters in the * typeahead buffer (used in case of an error). If 'typeahead' is true, * flush all typeahead characters (used when interrupted by a CTRL-C). */ void flush_buffers(typeahead) int typeahead; { struct noremap *p; init_typestr(); start_stuff(); while (read_stuff(TRUE) != NUL) ; if (typeahead) /* remove all typeahead */ { /* * We have to get all characters, because we may delete the first * part of an escape sequence. * In an xterm we get one char at a time and we have to get them all. */ while (inchar(typestr, MAXMAPLEN, 10)) ; *typestr = NUL; } else /* remove mapped characters only */ STRCPY(typestr, typestr + typemaplen); typemaplen = 0; no_abbr_cnt = 0; noremaplist.nr_len = 0; noremaplist.nr_off = 0; while (noremaplist.nr_next) { p = noremaplist.nr_next->nr_next; free(noremaplist.nr_next); noremaplist.nr_next = p; } } void ResetRedobuff() { if (!block_redo) free_buff(&redobuff); } void AppendToRedobuff(s) char_u *s; { if (!block_redo) add_buff(&redobuff, s); } void AppendCharToRedobuff(c) int c; { if (!block_redo) add_char_buff(&redobuff, c); } void AppendNumberToRedobuff(n) long n; { if (!block_redo) add_num_buff(&redobuff, n); } void stuffReadbuff(s) char_u *s; { add_buff(&stuffbuff, s); } void stuffcharReadbuff(c) int c; { add_char_buff(&stuffbuff, c); } void stuffnumReadbuff(n) long n; { add_num_buff(&stuffbuff, n); } /* * Read a character from the redo buffer. * The redo buffer is left as it is. * if init is TRUE, prepare for redo, return FAIL if nothing to redo, OK otherwise */ static int read_redo(init) int init; { static struct bufblock *bp; static char_u *p; int c; if (init) { if ((bp = redobuff.bh_first.b_next) == NULL) return FAIL; p = bp->b_str; return OK; } if ((c = *p) != NUL) { if (*++p == NUL && bp->b_next != NULL) { bp = bp->b_next; p = bp->b_str; } } return c; } /* * copy the rest of the redo buffer into the stuff buffer (could be done faster) */ void copy_redo() { register int c; while ((c = read_redo(FALSE)) != NUL) stuffcharReadbuff(c); } extern int redo_Visual_busy; /* this is in normal.c */ /* * Stuff the redo buffer into the stuffbuff. * Insert the redo count into the command. * return FAIL for failure, OK otherwise */ int start_redo(count) long count; { register int c; if (read_redo(TRUE) == FAIL) /* init the pointers; return if nothing to redo */ return FAIL; c = read_redo(FALSE); /* copy the buffer name, if present */ if (c == '"') { add_buff(&stuffbuff, (char_u *)"\""); c = read_redo(FALSE); /* if a numbered buffer is used, increment the number */ if (c >= '1' && c < '9') ++c; add_char_buff(&stuffbuff, c); c = read_redo(FALSE); } if (c == 'v') /* redo Visual */ { VIsual = curwin->w_cursor; redo_Visual_busy = TRUE; c = read_redo(FALSE); } /* try to enter the count (in place of a previous count) */ if (count) { while (isdigit(c)) /* skip "old" count */ c = read_redo(FALSE); add_num_buff(&stuffbuff, count); } /* copy from the redo buffer into the stuff buffer */ add_char_buff(&stuffbuff, c); copy_redo(); return OK; } /* * Repeat the last insert (R, o, O, a, A, i or I command) by stuffing * the redo buffer into the stuffbuff. * return FAIL for failure, OK otherwise */ int start_redo_ins() { register int c; if (read_redo(TRUE) == FAIL) return FAIL; start_stuff(); /* skip the count and the command character */ while ((c = read_redo(FALSE)) != NUL) { c = TO_UPPER(c); if (strchr("AIRO", c) != NULL) { if (c == 'O') stuffReadbuff(NL_STR); break; } } /* copy the typed text from the redo buffer into the stuff buffer */ copy_redo(); block_redo = TRUE; return OK; } void set_redo_ins() { block_redo = TRUE; } void stop_redo_ins() { block_redo = FALSE; } /* * Initialize typestr to point to typebuf. * Alloc() cannot be used here: In out-of-memory situations it would * be impossible to type anything. */ static void init_typestr() { if (typestr == NULL) { typestr = typebuf; typebuf[0] = NUL; } } /* * insert a string in front of the typeahead buffer (for '@' command and vgetorpeek) * return FAIL for failure, OK otherwise */ int ins_typestr(str, noremap) char_u *str; int noremap; { register char_u *s; register int newlen; register int addlen; init_typestr(); /* * In typestr there must always be room for MAXMAPLEN + 3 characters */ addlen = STRLEN(str); newlen = STRLEN(typestr) + addlen + MAXMAPLEN + 3; if (newlen < 0) /* string is getting too long */ { emsg(e_toocompl); /* also calls flush_buffers */ setcursor(); return FAIL; } s = alloc(newlen); if (s == NULL) /* out of memory */ return FAIL; STRCPY(s, str); STRCAT(s, typestr); if (typestr != typebuf) free(typestr); typestr = s; typemaplen += addlen; /* the inserted string is not typed */ if (no_abbr_cnt) /* and not used for abbreviations */ no_abbr_cnt += addlen; if (noremap) { if (noremaplist.nr_off == 0) noremaplist.nr_len += addlen; else { struct noremap *p; p = (struct noremap *)alloc((int)sizeof(struct noremap)); if (p != NULL) { p->nr_next = noremaplist.nr_next; p->nr_off = noremaplist.nr_off; p->nr_len = noremaplist.nr_len; noremaplist.nr_next = p; noremaplist.nr_len = addlen; noremaplist.nr_off = 0; } } } else if (noremaplist.nr_len) noremaplist.nr_off += addlen; return OK; } /* * remove "len" characters from the front of typestr */ void del_typestr(len) int len; { struct noremap *p; STRCPY(typestr, typestr + len); /* remove chars from the buffer */ if ((typemaplen -= len) < 0) /* adjust typemaplen */ typemaplen = 0; if ((no_abbr_cnt -= len) < 0) /* adjust no_abbr_cnt */ no_abbr_cnt = 0; while (len) /* adjust noremaplist */ { if (noremaplist.nr_off >= len) { noremaplist.nr_off -= len; break; } len -= noremaplist.nr_off; noremaplist.nr_off = 0; if (noremaplist.nr_len > len) { noremaplist.nr_len -= len; break; } len -= noremaplist.nr_len; p = noremaplist.nr_next; if (p == NULL) { noremaplist.nr_len = 0; break; } noremaplist.nr_next = p->nr_next; noremaplist.nr_len = p->nr_len; noremaplist.nr_off = p->nr_off; free(p); } } extern int arrow_used; /* this is in edit.c */ /* * Write typed characters to script file. * If recording is on put the character in the recordbuffer. */ static void gotchars(s, len) char_u *s; int len; { while (len--) { updatescript(*s & 255); if (Recording) add_char_buff(&recordbuff, (*s & 255)); ++s; } /* do not sync in insert mode, unless cursor key has been used */ if (!(State & (INSERT + CMDLINE)) || arrow_used) u_sync(); } /* * open new script file for ":so!" command * return OK on success, FAIL on error */ int openscript(name) char_u *name; { int oldcurscript; if (curscript + 1 == NSCRIPT) { emsg(e_nesting); return FAIL; } else { if (scriptin[curscript] != NULL) /* already reading script */ ++curscript; if ((scriptin[curscript] = fopen((char *)name, READBIN)) == NULL) { emsg2(e_notopen, name); if (curscript) --curscript; return FAIL; } /* * With command ":g/pat/so! file" we have to execute the * commands from the file now. */ if (global_busy) { State = NORMAL; oldcurscript = curscript; do { normal(); vpeekc(); /* check for end of file */ } while (scriptin[oldcurscript]); State = CMDLINE; } } return OK; } /* * updatescipt() is called when a character can be written into the script file * or when we have waited some time for a character (c == 0) * * All the changed memfiles are synced if c == 0 or when the number of typed * characters reaches 'updatecount'. */ void updatescript(c) int c; { static int count = 0; if (c && scriptout) putc(c, scriptout); if (c == 0 || ++count >= p_uc) { ml_sync_all(c == 0); count = 0; } } #define NEEDMORET 9999 /* value for incomplete mapping or key-code */ /* * get a character: 1. from the stuffbuffer * 2. from the typeahead buffer * 3. from the user * * KeyTyped is set to TRUE in the case the user typed the key. * vgetc() (advance is TRUE): really get the character. * vpeekc() (advance is FALSE): just look whether there is a character available. */ int vgetc() { return (vgetorpeek(TRUE)); } int vpeekc() { return (vgetorpeek(FALSE)); } static int vgetorpeek(advance) int advance; { register int c; int n = 0; /* init for GCC */ int len; #ifdef AMIGA char_u *s; #endif register struct mapblock *mp; int timedout = FALSE; /* waited for more than 1 second for mapping to complete */ int mapdepth = 0; /* check for recursive mapping */ int mode_deleted = FALSE; /* set when mode has been deleted */ init_typestr(); start_stuff(); if (typemaplen == 0) Exec_reg = FALSE; do { c = read_stuff(advance); if (c != NUL && !got_int) KeyTyped = FALSE; else { /* * Loop until we either find a matching mapped key, or we * are sure that it is not a mapped key. * If a mapped key sequence is found we go back to the start to * try re-mapping. */ for (;;) { len = STRLEN(typestr); breakcheck(); /* check for CTRL-C */ if (got_int) { c = inchar(typestr, MAXMAPLEN, 0); /* flush all input */ /* * If inchar returns TRUE (script file was active) or we are * inside a mapping, get out of insert mode. * Otherwise we behave like having gotten a CTRL-C. * As a result typing CTRL-C in insert mode will * really insert a CTRL-C. */ if ((c || typemaplen) && (State & (INSERT + CMDLINE))) c = ESC; else c = Ctrl('C'); flush_buffers(TRUE); /* flush all typeahead */ break; } else if (len > 0) /* see if we have a mapped key sequence */ { /* * walk through the maplist until we find an * entry that matches. * * Don't look for mappings if: * - timed out * - typestr[0] should not be remapped * - in insert or cmdline mode and 'paste' option set * - waiting for "hit return to continue" and CR or SPACE typed */ mp = NULL; if (!timedout && (typemaplen == 0 || (p_remap && (noremaplist.nr_len == 0 || noremaplist.nr_off != 0))) && !((State & (INSERT + CMDLINE)) && p_paste) && !(State == HITRETURN && (typestr[0] == CR || typestr[0] == ' '))) { for (mp = maplist.m_next; mp; mp = mp->m_next) { if ((mp->m_mode & ABBREV) || !(mp->m_mode & State)) continue; n = mp->m_keylen; if (noremaplist.nr_off != 0 && n > noremaplist.nr_off) continue; if (!STRNCMP(mp->m_keys, typestr, (size_t)(n > len ? len : n))) break; } } if (mp == NULL) /* no match found */ { /* * check if we have a terminal code, when * mapping is allowed, * keys have not been mapped, * and not an ESC sequence, not in insert mode or * p_ek is on, * and when not timed out, */ if (State != NOMAPPING && /* typemaplen == 0 && */ /* allow mapped keys anyway */ (typestr[0] != ESC || p_ek || !(State & INSERT)) && !timedout) n = check_termcode(typestr); else n = 0; if (n == 0) /* no matching terminal code */ { #ifdef AMIGA /* check for window bounds report */ if (typemaplen == 0 && (typestr[0] & 0xff) == CSI) { for (s = typestr + 1; isdigit(*s) || *s == ';' || *s == ' '; ++s) ; if (*s == 'r' || *s == '|') /* found one */ { STRCPY(typestr, s + 1); set_winsize(0, 0, FALSE); /* get size and redraw screen */ continue; } if (*s == NUL) /* need more characters */ n = -1; } if (n != -1) /* got a single character */ #endif { c = typestr[0] & 255; if (typemaplen) KeyTyped = FALSE; else { KeyTyped = TRUE; if (advance) /* write char to script file(s) */ gotchars(typestr, 1); } if (advance) /* remove chars from typestr */ del_typestr(1); break; /* got character, break for loop */ } } if (n > 0) /* full matching terminal code */ continue; /* try mapping again */ /* partial match: get some more characters */ n = NEEDMORET; } if (n <= len) /* complete match */ { if (n > typemaplen) /* write chars to script file(s) */ gotchars(typestr + typemaplen, n - typemaplen); del_typestr(n); /* remove the mapped keys */ /* * Put the replacement string in front of mapstr. * The depth check catches ":map x y" and ":map y x". */ if (++mapdepth == 1000) { EMSG("recursive mapping"); if (State == CMDLINE) redrawcmdline(); else setcursor(); flush_buffers(FALSE); mapdepth = 0; /* for next one */ c = -1; break; } if (ins_typestr(mp->m_str, mp->m_noremap) == FAIL) { c = -1; break; } continue; } } /* * special case: if we get an <ESC> in insert mode and there are * no more characters at once, we pretend to go out of insert mode. * This prevents the one second delay after typing an <ESC>. * If we get something after all, we may have to redisplay the * mode. That the cursor is in the wrong place does not matter. */ c = 0; if (advance && len == 1 && typestr[0] == ESC && typemaplen == 0 && (State & INSERT) && (p_timeout || (n == NEEDMORET && p_ttimeout)) && (c = inchar(typestr + len, 2, 0)) == 0) { if (p_smd) { delmode(); mode_deleted = TRUE; } if (curwin->w_cursor.col != 0) /* move cursor one left if possible */ { if (curwin->w_col) { if (did_ai) { if (curwin->w_p_nu) curwin->w_col = 8; else curwin->w_col = 0; } else --curwin->w_col; } else if (curwin->w_p_wrap && curwin->w_row) { --curwin->w_row; curwin->w_col = Columns - 1; } } setcursor(); flushbuf(); } len += c; if (len >= typemaplen + MAXMAPLEN) /* buffer full, don't map */ { timedout = TRUE; continue; } c = inchar(typestr + len, typemaplen + MAXMAPLEN - len, !advance ? 0 : ((len == 0 || !(p_timeout || (p_ttimeout && n == NEEDMORET))) ? -1 : (int)p_tm)); if (c <= NUL) /* no character available */ { if (!advance) break; if (len) /* timed out */ { timedout = TRUE; continue; } } } /* for (;;) */ } /* if (!character from stuffbuf) */ /* if advance is FALSE don't loop on NULs */ } while (c < 0 || (advance && c == NUL)); /* * The "INSERT" message is taken care of here: * if we return an ESC the message is deleted * if we don't return an ESC but deleted the message before, redisplay it */ if (p_smd && (State & INSERT)) { if (c == ESC && !mode_deleted) delmode(); else if (c != ESC && mode_deleted) showmode(); } return c; } /* * map[!] : show all key mappings * map[!] {lhs} : show key mapping for {lhs} * map[!] {lhs} {rhs} : set key mapping for {lhs} to {rhs} * noremap[!] {lhs} {rhs} : same, but no remapping for {rhs} * unmap[!] {lhs} : remove key mapping for {lhs} * abbr : show all abbreviations * abbr {lhs} : show abbreviations for {lhs} * abbr {lhs} {rhs} : set abbreviation for {lhs} to {rhs} * noreabbr {lhs} {rhs} : same, but no remapping for {rhs} * unabbr {lhs} : remove abbreviation for {lhs} * * maptype == 1 for unmap command, 2 for noremap command. * * keys is pointer to any arguments. * * for :map mode is NORMAL * for :map! mode is INSERT + CMDLINE * for :cmap mode is CMDLINE * for :imap mode is INSERT * for :abbr mode is INSERT + CMDLINE + ABBREV * for :iabbr mode is INSERT + ABBREV * for :cabbr mode is CMDLINE + ABBREV * * Return 0 for success * 1 for invalid arguments * 2 for no match * 3 for ambiguety * 4 for out of mem */ int domap(maptype, keys, mode) int maptype; char_u *keys; int mode; { struct mapblock *mp, *mprev; char_u *arg; char_u *p; int n = 0; /* init for GCC */ int len = 0; /* init for GCC */ char_u *newstr; int hasarg; int haskey; int did_it = FALSE; int abbrev = 0; int round; if (mode & ABBREV) /* not a mapping but an abbreviation */ { abbrev = ABBREV; mode &= ~ABBREV; } /* * find end of keys and remove CTRL-Vs in it * with :unmap white space is included in the keys, no argument possible */ p = keys; while (*p && (maptype == 1 || !iswhite(*p))) { if (*p == Ctrl('V') && p[1] != NUL) STRCPY(p, p + 1); /* remove CTRL-V */ ++p; } if (*p != NUL) *p++ = NUL; skipspace(&p); arg = p; hasarg = (*arg != NUL); haskey = (*keys != NUL); /* check for :unmap without argument */ if (maptype == 1 && !haskey) return 1; /* * remove CTRL-Vs from argument */ while (*p) { if (*p == Ctrl('V') && p[1] != NUL) STRCPY(p, p + 1); /* remove CTRL-V */ ++p; } /* * check arguments and translate function keys */ if (haskey) { if (*keys == '#' && isdigit(*(keys + 1))) /* function key */ { if (*++keys == '0') *keys = K_F10; else *keys += K_F1 - '1'; } len = STRLEN(keys); if (len > MAXMAPLEN) /* maximum lenght of MAXMAPLEN chars */ return 1; /* * abbreviation must end in id-char * rest must be all id-char or all non-id-char */ if (abbrev) { if (!isidchar(*(keys + len - 1))) /* does not end in id char */ return 1; for (n = 0; n < len - 2; ++n) if (isidchar(*(keys + n)) != isidchar(*(keys + len - 2))) return 1; } } if (haskey && hasarg && abbrev) /* if we will add an abbreviation */ no_abbr = FALSE; /* reset flag that indicates there are no abbreviations */ if (!haskey || (maptype != 1 && !hasarg)) msg_start(); /* * Find an entry in the maplist that matches. * For :unmap we may loop two times: once to try to unmap an entry with a * matching 'from' part, a second time, if the first fails, to unmap an * entry with a matching 'to' part. This was done to allow ":ab foo bar" to be * unmapped by typing ":unab foo", where "foo" will be replaced by "bar" because * of the abbreviation. */ for (round = 0; (round == 0 || maptype == 1) && round <= 1 && !did_it && !got_int; ++round) { for (mp = maplist.m_next, mprev = &maplist; mp && !got_int; mprev = mp, mp = mp->m_next) { /* skip entries with wrong mode */ if (!(mp->m_mode & mode) || (mp->m_mode & ABBREV) != abbrev) continue; if (!haskey) /* show all entries */ { showmap(mp); did_it = TRUE; } else /* do we have a match? */ { if (round) /* second round: try 'to' string for unmap */ { n = STRLEN(mp->m_str); p = mp->m_str; } else { n = mp->m_keylen; p = mp->m_keys; } if (!STRNCMP(p, keys, (size_t)(n < len ? n : len))) { if (maptype == 1) /* delete entry */ { if (n != len) /* not a full match */ continue; /* * We reset the indicated mode bits. If nothing is left the * entry is deleted below. */ mp->m_mode &= (~mode | ABBREV); did_it = TRUE; /* remember that we did something */ } else if (!hasarg) /* show matching entry */ { showmap(mp); did_it = TRUE; } else if (n != len) /* new entry is ambigious */ { if (abbrev) /* for abbreviations that's ok */ continue; return 3; } else { mp->m_mode &= (~mode | ABBREV); /* remove mode bits */ if (!(mp->m_mode & ~ABBREV) && !did_it) /* reuse existing entry */ { newstr = strsave(arg); if (newstr == NULL) return 4; /* no mem */ free(mp->m_str); mp->m_str = newstr; mp->m_noremap = maptype; mp->m_mode = mode + abbrev; did_it = TRUE; } } if (!(mp->m_mode & ~ABBREV)) /* entry can be deleted */ { free(mp->m_keys); free(mp->m_str); mprev->m_next = mp->m_next; free(mp); mp = mprev; /* continue with next entry */ } } } } } if (maptype == 1) /* delete entry */ { if (did_it) return 0; /* removed OK */ else return 2; /* no match */ } if (!haskey || !hasarg) /* print entries */ { if (did_it) msg_end(); else if (abbrev) MSG("No abbreviation found"); else MSG("No mapping found"); return 0; /* listing finished */ } if (did_it) /* have added the new entry already */ return 0; /* * get here when we have to add a new entry */ /* allocate a new entry for the maplist */ mp = (struct mapblock *)alloc((unsigned)sizeof(struct mapblock)); if (mp == NULL) return 4; /* no mem */ mp->m_keys = strsave(keys); mp->m_str = strsave(arg); if (mp->m_keys == NULL || mp->m_str == NULL) { free(mp->m_keys); free(mp->m_str); free(mp); return 4; /* no mem */ } mp->m_keylen = STRLEN(mp->m_keys); mp->m_noremap = maptype; mp->m_mode = mode + abbrev; /* add the new entry in front of the maplist */ mp->m_next = maplist.m_next; maplist.m_next = mp; return 0; /* added OK */ } static void showmap(mp) struct mapblock *mp; { int len; msg_pos(-1, 0); /* always start in column 0 */ if ((mp->m_mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) msg_outstr((char_u *)"! "); else if (mp->m_mode & INSERT) msg_outstr((char_u *)"i "); else if (mp->m_mode & CMDLINE) msg_outstr((char_u *)"c "); len = msg_outtrans(mp->m_keys, -1); /* get length of what we have written */ do { msg_outchar(' '); /* padd with blanks */ ++len; } while (len < 12); if (mp->m_noremap) msg_outchar('*'); else msg_outchar(' '); msg_outtrans(mp->m_str, -1); msg_outchar('\n'); flushbuf(); /* show one line at a time */ } /* * Check for an abbreviation. * Cursor is at ptr[col]. When inserting, mincol is where insert started. * "c" is the character typed before check_abbr was called. * * Historic vi practice: The last character of an abbreviation must be an id * character ([a-zA-Z0-9_]). The characters in front of it must be all id * characters or all non-id characters. This allows for abbr. "#i" to "#include". * * return TRUE if there is an abbreviation, FALSE if not */ int check_abbr(c, ptr, col, mincol) int c; char_u *ptr; int col; int mincol; { int len; int j; char_u tb[3]; struct mapblock *mp; int is_id = TRUE; if (no_abbr_cnt) /* abbrev. are not recursive */ return FALSE; if (col == 0 || !isidchar(ptr[col - 1])) /* cannot be an abbr. */ return FALSE; if (col > 1) is_id = isidchar(ptr[col - 2]); for (len = col - 1; len > 0 && !isspace(ptr[len - 1]) && is_id == isidchar(ptr[len - 1]); --len) ; if (len < mincol) len = mincol; if (len < col) /* there is a word in front of the cursor */ { ptr += len; len = col - len; for (mp = maplist.m_next; mp; mp = mp->m_next) { /* find entries with right mode and keys */ if ((mp->m_mode & ABBREV) == ABBREV && (mp->m_mode & State) && mp->m_keylen == len && !STRNCMP(mp->m_keys, ptr, (size_t)len)) break; } if (mp) /* found a match */ { j = 0; if (c < 0x100 && (c < ' ' || c > '~')) tb[j++] = Ctrl('V'); /* special char needs CTRL-V */ tb[j++] = c; tb[j] = NUL; (void)ins_typestr(tb, TRUE); /* insert the last typed char */ (void)ins_typestr(mp->m_str, mp->m_noremap); /* insert the to string */ no_abbr_cnt += STRLEN(mp->m_str) + j; /* no abbrev. for these chars */ while (len--) (void)ins_typestr((char_u *)"\b", TRUE); /* delete the from string */ return TRUE; } } return FALSE; } /* * Write map commands for the current mappings to an .exrc file. * Return FAIL on error, OK otherwise. */ int makemap(fd) FILE *fd; { struct mapblock *mp; char_u c1; char_u *p; for (mp = maplist.m_next; mp; mp = mp->m_next) { c1 = NUL; p = (char_u *)"map"; switch (mp->m_mode) { case NORMAL: break; case CMDLINE + INSERT: p = (char_u *)"map!"; break; case CMDLINE: c1 = 'c'; break; case INSERT: c1 = 'i'; break; case INSERT + CMDLINE + ABBREV: p = (char_u *)"abbr"; break; case CMDLINE + ABBREV: c1 = 'c'; p = (char_u *)"abbr"; break; case INSERT + ABBREV: c1 = 'i'; p = (char_u *)"abbr"; break; default: EMSG("makemap: Illegal mode"); return FAIL; } if (c1 && putc(c1, fd) < 0) return FAIL; if (mp->m_noremap && fprintf(fd, "nore") < 0) return FAIL; if (fprintf(fd, (char *)p) < 0) return FAIL; if ( putc(' ', fd) < 0 || putescstr(fd, mp->m_keys, FALSE) == FAIL || putc(' ', fd) < 0 || putescstr(fd, mp->m_str, FALSE) == FAIL || #ifdef MSDOS putc('\r', fd) < 0 || #endif putc('\n', fd) < 0) return FAIL; } return OK; } /* * write escape string to file * * return FAIL for failure, OK otherwise */ int putescstr(fd, str, set) FILE *fd; char_u *str; int set; /* TRUE for makeset, FALSE for makemap */ { for ( ; *str; ++str) { /* * some characters have to be escaped with CTRL-V to * prevent them from misinterpreted in DoOneCmd(). * A space has to be escaped with a backslash to * prevent it to be misinterpreted in doset(). */ if (*str < ' ' || *str > '~' || (*str == ' ' && !set)) { if (putc(Ctrl('V'), fd) < 0) return FAIL; } else if ((set && *str == ' ') || *str == '|') { if (putc('\\', fd) < 0) return FAIL; } if (putc(*str, fd) < 0) return FAIL; } return OK; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.