/* vi:ts=4:sw=4 * * VIM - Vi IMproved * * Code Contributions By: Bram Moolenaar mool@oce.nl * Tim Thompson twitch!tjt * Tony Andrews onecom!wldrdg!tony * G. R. (Fred) Walter watmath!watcgl!grwalter */ /* * buffers.c * * manipulations with redo buffer and stuff buffer */ #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 */ u_char 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 *m_keys; /* mapped from */ int m_keylen; /* strlen(m_keys) */ char *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 and @a commands. * The lenght of this part is typemaplen. * After it are characters that come from the terminal. * 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 *typestr = NULL; /* NUL-terminated buffer for typeahead characters */ static char typebuf[MAXMAPLEN + 3]; /* initial typestr */ static int typemaplen = 0; /* number of mapped characters 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 u_char *get_bufcont __ARGS((struct buffheader *, int)); static void add_buff __ARGS((struct buffheader *, char *)); static void add_num_buff __ARGS((struct buffheader *, long)); static void add_char_buff __ARGS((struct buffheader *, int)); static u_char read_stuff __ARGS((int)); static int start_stuff __ARGS((void)); static int read_redo __ARGS((int)); static void gotchars __ARGS((char *, int)); static void init_typestr __ARGS((void)); static u_char 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((char *)p); } buf->bh_first.b_next = NULL; } /* * return the contents of a buffer as a single string */ static u_char * get_bufcont(buffer, dozero) struct buffheader *buffer; int dozero; /* count == zero is not an error */ { u_long count = 0; u_char *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((char *)bp->b_str); if ((count || dozero) && (p = (u_char *)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 */ u_char * get_recorded() { u_char *p; p = get_bufcont(&recordbuff, TRUE); free_buff(&recordbuff); return (p); } /* * return the contents of the redo buffer as a single string */ u_char * 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 *s; { struct bufblock *p; u_long n; u_long 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((char *)buf->bh_first.b_next->b_str, (char *)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, s); buf->bh_space -= n; } else { if (n < MINIMAL_SIZE) len = MINIMAL_SIZE; else len = n; p = (struct bufblock *)lalloc((u_long)(sizeof(struct bufblock) + len), TRUE); if (p == NULL) return; /* no space, just forget it */ buf->bh_space = len - n; strcpy((char *)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 number[32]; sprintf(number, "%ld", n); add_buff(buf, number); } static void add_char_buff(buf, c) struct buffheader *buf; int c; { char 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 u_char read_stuff(advance) int advance; { register u_char 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((char *)curr); stuffbuff.bh_index = 0; } } return c; } /* * prepare stuff buffer for reading (if it contains something) */ static int start_stuff() { if (stuffbuff.bh_first.b_next == NULL) return FALSE; stuffbuff.bh_curr = &(stuffbuff.bh_first); stuffbuff.bh_space = 0; return TRUE; } /* * 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; 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 *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 *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. */ static int read_redo(init) int init; { static struct bufblock *bp; static u_char *p; int c; if (init) { if ((bp = redobuff.bh_first.b_next) == NULL) return TRUE; p = bp->b_str; return FALSE; } 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. */ int start_redo(count) long count; { register int c; if (read_redo(TRUE)) /* init the pointers; return if nothing to redo */ return FALSE; c = read_redo(FALSE); /* copy the buffer name, if present */ if (c == '"') { add_buff(&stuffbuff, "\""); 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 = Curpos; 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); } #ifdef JPFEP /* forcce turn off Kanji input */ fep_mode_switch(KanjiInput = FALSE); #endif /* copy from the redo buffer into the stuff buffer */ add_char_buff(&stuffbuff, c); copy_redo(); return TRUE; } /* * Repeat the last insert (R, o, O, a, A, i or I command) by stuffing * the redo buffer into the stuffbuff. */ int start_redo_ins() { register u_char c; if (read_redo(TRUE)) return FALSE; 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; } } #ifdef JPFEP /* forcce turn off Kanji input */ fep_mode_switch(KanjiInput = FALSE); #endif /* copy the typed text from the redo buffer into the stuff buffer */ copy_redo(); block_redo = TRUE; return TRUE; } void set_redo_ins() { block_redo = TRUE; } void stop_redo_ins() { block_redo = FALSE; } /* * insert a string in front of the typeahead buffer (for '@' command and vgetorpeek) */ int ins_typestr(str, noremap) char *str; int noremap; { register char *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 -1; } s = alloc(newlen); if (s == NULL) /* out of memory */ return -1; strcpy(s, str); strcat(s, typestr); if (typestr != typebuf) free(typestr); typestr = s; typemaplen += addlen; /* the inserted string is not typed */ 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 0; } /* * 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; 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 *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(); } /* * 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; } } #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. * If advance is TRUE, we really get the character. Otherwise we just look * whether there is a character available. */ static u_char vgetorpeek(advance) int advance; { register int c; int n = 0; /* init for GCC */ int len; #ifdef AMIGA char *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 (if not timed out). */ mp = NULL; if (!timedout && (typemaplen == 0 || (p_remap && (noremaplist.nr_len == 0 || noremaplist.nr_off != 0))) && !((State & (INSERT + CMDLINE)) && p_paste)) { 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 && (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) < 0) { c = -1; break; } continue; } } /* * special case: if we get an 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 . * 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 (Curscol) /* move cursor one left if possible */ --Curscol; else if (p_wrap && Curpos.col != 0 && Cursrow) { --Cursrow; Curscol = 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; } } else { if (mode_deleted) /* character entered after ESC */ { showmode(); mode_deleted = FALSE; } } } /* for (;;) */ } /* if (!character from stuffbuf) */ /* if advance is FALSE don't loop on NULs */ } while (c < 0 || (advance && c == NUL)); /* delete "INSERT" message if we return an ESC */ if (c == ESC && p_smd && !mode_deleted && (State & INSERT)) delmode(); return (u_char) c; } u_char vgetc() { return (vgetorpeek(TRUE)); } u_char vpeekc() { return (vgetorpeek(FALSE)); } /* * 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 *keys; int mode; { struct mapblock *mp, *mprev; char *arg; char *p; int n = 0; /* init for GCC */ int len = 0; /* init for GCC */ char *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 */ p = keys; while (*p && *p != ' ' && *p != '\t') { 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 with not one argument */ if (maptype == 1 && (!haskey || hasarg)) 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') *(u_char *)keys = K_F10; else *keys += K_F1 - '1'; } len = strlen(keys); if (len > MAXMAPLEN) /* maximum lenght of MAXMAPLEN chars */ return 2; } if (haskey && hasarg && abbrev) /* will add an abbreviation */ no_abbr = FALSE; #ifdef AMIGA if (!haskey || (maptype != 1 && !hasarg)) settmode(0); /* set cooked mode so output can be halted */ #endif /* * 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; ++round) { for (mp = maplist.m_next, mprev = &maplist; mp; 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 */ { 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 */ { #ifdef AMIGA settmode(1); #endif if (did_it) wait_return(TRUE); 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; if ((mp->m_mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) outstr("! "); else if (mp->m_mode & INSERT) outstr("i "); else if (mp->m_mode & CMDLINE) outstr("c "); len = outtrans(mp->m_keys, -1); /* get length of what we have written */ do { outchar(' '); /* padd with blanks */ ++len; } while (len < 12); if (mp->m_noremap) outchar('*'); else outchar(' '); outtrans(mp->m_str, -1); outchar('\n'); flushbuf(); } /* * 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. */ int check_abbr(c, ptr, col, mincol) int c; char *ptr; int col; int mincol; { int len; int j; char tb[3]; struct mapblock *mp; for (len = col; len > 0 && 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; ins_typestr(tb, TRUE); /* insert the last typed char */ ins_typestr(mp->m_str, mp->m_noremap); /* insert the to string */ while (len--) ins_typestr("\b", TRUE); /* delete the from string */ return TRUE; } } return FALSE; } /* * Write map commands for the current mappings to an .exrc file. * Return 1 on error. */ int makemap(fd) FILE *fd; { struct mapblock *mp; char c1; char *p; for (mp = maplist.m_next; mp; mp = mp->m_next) { c1 = NUL; p = "map"; switch (mp->m_mode) { case NORMAL: break; case CMDLINE + INSERT: p = "map!"; break; case CMDLINE: c1 = 'c'; break; case INSERT: c1 = 'i'; break; case INSERT + CMDLINE + ABBREV: p = "abbr"; break; case CMDLINE + ABBREV: c1 = 'c'; p = "abbr"; break; case INSERT + ABBREV: c1 = 'i'; p = "abbr"; break; default: emsg("makemap: Illegal mode"); return 1; } if (c1 && putc(c1, fd) < 0) return 1; if (mp->m_noremap && fprintf(fd, "nore") < 0) return 1; if (fprintf(fd, p) < 0) return 1; if ( putc(' ', fd) < 0 || putescstr(fd, mp->m_keys, FALSE) < 0 || putc(' ', fd) < 0 || putescstr(fd, mp->m_str, FALSE) < 0 || #ifdef MSDOS putc('\r', fd) < 0 || #endif putc('\n', fd) < 0) return 1; } return 0; } int putescstr(fd, str, set) FILE *fd; char *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 -1; } else if ((set && *str == ' ') || *str == '|') { if (putc('\\', fd) < 0) return -1; } if (putc(*str, fd) < 0) return -1; } return 0; }